On the way to problems: saving state [not functional yet]
authorBenjamin Auder <benjamin.auder@somewhere>
Sat, 15 Dec 2018 14:35:59 +0000 (15:35 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sat, 15 Dec 2018 14:35:59 +0000 (15:35 +0100)
21 files changed:
.gitattributes
db/create.sql [new file with mode: 0644]
db/vchess.sqlite [new file with mode: 0644]
package-lock.json
package.json
public/javascripts/components/game.js
public/javascripts/components/problemSummary.js [new file with mode: 0644]
public/javascripts/components/problems.js
public/javascripts/components/rules.js
public/javascripts/utils/ajax.js [new file with mode: 0644]
public/javascripts/utils/printDiagram.js [new file with mode: 0644]
public/stylesheets/variant.sass
routes/all.js
sockets.js
variants.js [deleted file]
views/index.pug
views/rules/Chess960.pug
views/rules/Crazyhouse.pug
views/rules/Switching.pug
views/rules/Ultima.pug
views/variant.pug

index d268ba5..ac4ca11 100644 (file)
@@ -4,3 +4,4 @@
 *.wav filter=fat
 *.mp3 filter=fat
 *.png filter=fat
+*.sqlite filter=fat
diff --git a/db/create.sql b/db/create.sql
new file mode 100644 (file)
index 0000000..aa2f3ae
--- /dev/null
@@ -0,0 +1,29 @@
+create table Variants (
+       name varchar primary key,
+       description text
+);
+insert into Variants values
+       ('Checkered', 'Shared pieces'),
+       ('Zen', 'Reverse captures'),
+       ('Atomic', 'Explosive captures'),
+       ('Chess960', 'Standard rules'),
+       ('Antiking', 'Keep antiking in check'),
+       ('Magnetic', 'Laws of attraction'),
+       ('Alice', 'Both sides of the mirror'),
+       ('Grand', 'Big board'),
+       ('Wildebeest', 'Balanced sliders & leapers'),
+       ('Loser', 'Lose all pieces'),
+       ('Crazyhouse', 'Captures reborn'),
+       ('Switching', 'Exchange pieces positions'),
+       ('Extinction', 'Capture all of a kind'),
+       ('Ultima', 'Exotic captures');
+
+create table Problems (
+       added datetime,
+       variant varchar,
+       fen varchar,
+       instructions text,
+       solution text,
+       foreign key (variant) references Variants(name)
+);
+--PRAGMA foreign_keys = ON;
diff --git a/db/vchess.sqlite b/db/vchess.sqlite
new file mode 100644 (file)
index 0000000..9d7e4a2
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 25d1d22208da0bc58bf3076bfe684bbcb98894b2                16384
index 88d9fd8..783e409 100644 (file)
@@ -1,6 +1,6 @@
 {
-  "name": "vc",
-  "version": "0.0.0",
+  "name": "vchess",
+  "version": "0.1.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
         }
       }
     },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
+    },
     "array-unique": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
         "upath": "^1.0.5"
       }
     },
+    "chownr": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
+      "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
+    },
     "ci-info": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
       "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-      "dev": true,
       "requires": {
         "color-name": "1.1.3"
       }
     "color-name": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
-      "dev": true
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
     "color-support": {
       "version": "1.1.3",
       "dev": true
     },
     "colors": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz",
-      "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==",
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
+      "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
       "dev": true
     },
     "combined-stream": {
     "deep-extend": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
-      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
-      "dev": true
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
     },
     "default-compare": {
       "version": "1.0.0",
       "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
       "dev": true
     },
+    "detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
+    },
     "doctypes": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
       "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
     },
+    "dom-serializer": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+      "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
+      "requires": {
+        "domelementtype": "~1.1.1",
+        "entities": "~1.1.1"
+      },
+      "dependencies": {
+        "domelementtype": {
+          "version": "1.1.3",
+          "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+          "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
+        }
+      }
+    },
+    "domelementtype": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+      "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
+    },
+    "domhandler": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+      "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+      "requires": {
+        "domelementtype": "1"
+      }
+    },
+    "domutils": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
+      "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+      "requires": {
+        "dom-serializer": "0",
+        "domelementtype": "1"
+      }
+    },
     "dot-prop": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
         "is-obj": "^1.0.0"
       }
     },
-    "duplexer": {
-      "version": "0.1.1",
-      "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
-      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
-      "dev": true
-    },
     "duplexer3": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
         "once": "^1.4.0"
       }
     },
+    "entities": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+      "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
+    },
     "error-ex": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
       "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
     },
-    "event-stream": {
-      "version": "3.3.6",
-      "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz",
-      "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==",
-      "dev": true,
-      "requires": {
-        "duplexer": "^0.1.1",
-        "flatmap-stream": "^0.1.0",
-        "from": "^0.1.7",
-        "map-stream": "0.0.7",
-        "pause-stream": "^0.0.11",
-        "split": "^1.0.1",
-        "stream-combiner": "^0.2.2",
-        "through": "^2.3.8"
-      }
-    },
     "execa": {
       "version": "0.7.0",
       "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
     },
     "fancy-log": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz",
-      "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=",
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
+      "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==",
       "dev": true,
       "requires": {
         "ansi-gray": "^0.1.1",
         "color-support": "^1.1.3",
+        "parse-node-version": "^1.0.0",
         "time-stamp": "^1.0.0"
       }
     },
       }
     },
     "fined": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz",
-      "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.1.tgz",
+      "integrity": "sha512-jQp949ZmEbiYHk3gkbdtpJ0G1+kgtLQBNdP5edFP7Fh+WAYceLQz6yO1SBj72Xkg8GVyTB3bBzAYrHJVh5Xd5g==",
       "dev": true,
       "requires": {
         "expand-tilde": "^2.0.2",
       }
     },
     "flagged-respawn": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz",
-      "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=",
-      "dev": true
-    },
-    "flatmap-stream": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.1.tgz",
-      "integrity": "sha512-lAq4tLbm3sidmdCN8G3ExaxH7cUCtP5mgDvrYowsx84dcYkJJ4I28N7gkxA6+YlSXzaGLJYIDEi9WGfXzMiXdw==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
+      "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==",
       "dev": true
     },
     "flush-write-stream": {
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
       "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
     },
-    "from": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
-      "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
-      "dev": true
+    "fs-minipass": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
+      "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
+      "requires": {
+        "minipass": "^2.2.1"
+      }
     },
     "fs-mkdirp-stream": {
       "version": "1.0.0",
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.2.0",
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "minipass": {
           "version": "2.2.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
         "safe-buffer": {
           "version": "5.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "safer-buffer": {
           "version": "2.1.2",
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "yallist": {
           "version": "3.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
       }
     },
     "glob-watcher": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.1.tgz",
-      "integrity": "sha512-fK92r2COMC199WCyGUblrZKhjra3cyVMDiypDdqg1vsSDmexnbYivK1kNR4QItiNXLKmGlqan469ks67RtNa2g==",
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz",
+      "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==",
       "dev": true,
       "requires": {
+        "anymatch": "^2.0.0",
         "async-done": "^1.2.0",
         "chokidar": "^2.0.0",
+        "is-negated-glob": "^1.0.0",
         "just-debounce": "^1.0.0",
         "object.defaults": "^1.1.0"
       }
       }
     },
     "glogg": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz",
-      "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz",
+      "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==",
       "dev": true,
       "requires": {
         "sparkles": "^1.0.0"
       }
     },
     "gulp-nodemon": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/gulp-nodemon/-/gulp-nodemon-2.4.1.tgz",
-      "integrity": "sha512-IZMEfUZggvXvYDCCbb4jq8xMSsS24OltlgL0KhumhDAbZJUHMm3tHEolHbNLLL9704Wm/DgZ5bdFqCb2hXG0bQ==",
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/gulp-nodemon/-/gulp-nodemon-2.4.2.tgz",
+      "integrity": "sha512-r8ShC9yzL3lK5qUsTStMeZRwqLG6t2m4lEBVcfUYzVkiYSeYXu9xYXG5rfvzBOPZOZ2dWugTKr+zeWbnMnzWDA==",
       "dev": true,
       "requires": {
         "colors": "^1.2.1",
-        "event-stream": "^3.3.4",
         "gulp": "^4.0.0",
-        "nodemon": "^1.17.5"
+        "nodemon": "^1.18.7"
       }
     },
     "gulplog": {
     "has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
-      "dev": true
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
     },
     "has-symbols": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
       "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w=="
     },
+    "htmlparser2": {
+      "version": "3.10.0",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz",
+      "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==",
+      "requires": {
+        "domelementtype": "^1.3.0",
+        "domhandler": "^2.3.0",
+        "domutils": "^1.5.1",
+        "entities": "^1.1.1",
+        "inherits": "^2.0.1",
+        "readable-stream": "^3.0.6"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.0.6",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz",
+          "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
+      }
+    },
     "http-errors": {
       "version": "1.7.1",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz",
       "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
       "dev": true
     },
+    "ignore-walk": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
+      "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
+      "requires": {
+        "minimatch": "^3.0.4"
+      }
+    },
     "import-lazy": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
     "ini": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
-      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
-      "dev": true
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
     },
     "interpret": {
       "version": "1.1.0",
       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
       "dev": true
     },
+    "lodash.escaperegexp": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+      "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c="
+    },
+    "lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+    },
+    "lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+    },
     "lodash.mergewith": {
       "version": "4.6.1",
       "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
       "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
       "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
     },
-    "map-stream": {
-      "version": "0.0.7",
-      "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
-      "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=",
-      "dev": true
-    },
     "map-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
       "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
       "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
     },
+    "minipass": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
+      "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
+      "requires": {
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.0"
+      },
+      "dependencies": {
+        "yallist": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
+        }
+      }
+    },
+    "minizlib": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
+      "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
+      "requires": {
+        "minipass": "^2.2.1"
+      }
+    },
     "mixin-deep": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
         }
       }
     },
+    "needle": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz",
+      "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==",
+      "requires": {
+        "debug": "^2.1.2",
+        "iconv-lite": "^0.4.4",
+        "sax": "^1.2.4"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
     "negotiator": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
         }
       }
     },
+    "node-pre-gyp": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz",
+      "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==",
+      "requires": {
+        "detect-libc": "^1.0.2",
+        "mkdirp": "^0.5.1",
+        "needle": "^2.2.1",
+        "nopt": "^4.0.1",
+        "npm-packlist": "^1.1.6",
+        "npmlog": "^4.0.2",
+        "rc": "^1.2.7",
+        "rimraf": "^2.6.1",
+        "semver": "^5.3.0",
+        "tar": "^4"
+      },
+      "dependencies": {
+        "nopt": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+          "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+          "requires": {
+            "abbrev": "1",
+            "osenv": "^0.1.4"
+          }
+        },
+        "tar": {
+          "version": "4.4.8",
+          "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
+          "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
+          "requires": {
+            "chownr": "^1.1.1",
+            "fs-minipass": "^1.2.5",
+            "minipass": "^2.3.4",
+            "minizlib": "^1.1.1",
+            "mkdirp": "^0.5.0",
+            "safe-buffer": "^5.1.2",
+            "yallist": "^3.0.2"
+          }
+        },
+        "yallist": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
+        }
+      }
+    },
     "node-sass": {
       "version": "4.10.0",
       "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.10.0.tgz",
       }
     },
     "nodemon": {
-      "version": "1.18.6",
-      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.6.tgz",
-      "integrity": "sha512-4pHQNYEZun+IkIC2jCaXEhkZnfA7rQe73i8RkdRyDJls/K+WxR7IpI5uNUsAvQ0zWvYcCDNGD+XVtw2ZG86/uQ==",
+      "version": "1.18.9",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.9.tgz",
+      "integrity": "sha512-oj/eEVTEI47pzYAjGkpcNw0xYwTl4XSTUQv2NPQI6PpN3b75PhpuYk3Vb3U80xHCyM2Jm+1j68ULHXl4OR3Afw==",
       "dev": true,
       "requires": {
         "chokidar": "^2.0.4",
         "debug": "^3.1.0",
         "ignore-by-default": "^1.0.1",
         "minimatch": "^3.0.4",
-        "pstree.remy": "^1.1.0",
+        "pstree.remy": "^1.1.6",
         "semver": "^5.5.0",
         "supports-color": "^5.2.0",
         "touch": "^3.1.0",
         "undefsafe": "^2.0.2",
-        "update-notifier": "^2.3.0"
+        "update-notifier": "^2.5.0"
       },
       "dependencies": {
         "debug": {
         "once": "^1.3.2"
       }
     },
+    "npm-bundled": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz",
+      "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g=="
+    },
+    "npm-packlist": {
+      "version": "1.1.12",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz",
+      "integrity": "sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==",
+      "requires": {
+        "ignore-walk": "^3.0.1",
+        "npm-bundled": "^1.0.1"
+      }
+    },
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
         "error-ex": "^1.2.0"
       }
     },
+    "parse-node-version": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.0.tgz",
+      "integrity": "sha512-02GTVHD1u0nWc20n2G7WX/PgdhNFG04j5fi1OkaJzPWLTcf6vh6229Lta1wTmXG/7Dg42tCssgkccVt7qvd8Kg==",
+      "dev": true
+    },
     "parse-passwd": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
         "pinkie-promise": "^2.0.0"
       }
     },
-    "pause-stream": {
-      "version": "0.0.11",
-      "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
-      "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
-      "dev": true,
-      "requires": {
-        "through": "~2.3"
-      }
-    },
     "performance-now": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
       "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
       "dev": true
     },
+    "postcss": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.6.tgz",
+      "integrity": "sha512-Nq/rNjnHFcKgCDDZYO0lNsl6YWe6U7tTy+ESN+PnLxebL8uBtYX59HZqvrj7YLK5UCyll2hqDsJOo3ndzEW8Ug==",
+      "requires": {
+        "chalk": "^2.4.1",
+        "source-map": "^0.6.1",
+        "supports-color": "^5.5.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "chalk": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+          "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+          "requires": {
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
+          }
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        },
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
     "prepend-http": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
         "ipaddr.js": "1.8.0"
       }
     },
-    "ps-tree": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz",
-      "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=",
-      "dev": true,
-      "requires": {
-        "event-stream": "~3.3.0"
-      }
-    },
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
       "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
     },
     "pstree.remy": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz",
-      "integrity": "sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==",
-      "dev": true,
-      "requires": {
-        "ps-tree": "^1.1.0"
-      }
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.6.tgz",
+      "integrity": "sha512-NdF35+QsqD7EgNEI5mkI/X+UwaxVEbQaz9f4IooEmMUv6ZPmlTQYGjBPJGgrlzNdjSvIy4MWMg6Q6vCgBO2K+w==",
+      "dev": true
     },
     "pug": {
       "version": "2.0.3",
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
       "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
-      "dev": true,
       "requires": {
         "deep-extend": "^0.6.0",
         "ini": "~1.3.0",
         "minimist": {
           "version": "1.2.0",
           "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
-          "dev": true
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
         }
       }
     },
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
+    "sanitize-html": {
+      "version": "1.19.3",
+      "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.19.3.tgz",
+      "integrity": "sha512-QpIjbF1rhUSQj9V7Wey/gv4DPqOso8KTebaI4rC97p0WCLnTpmhf7BJZUhS83MTtqRvUo8MuXH316CW2Nzd48w==",
+      "requires": {
+        "chalk": "^2.4.1",
+        "htmlparser2": "^3.10.0",
+        "lodash.clonedeep": "^4.5.0",
+        "lodash.escaperegexp": "^4.1.2",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.mergewith": "^4.6.1",
+        "postcss": "^7.0.5",
+        "srcset": "^1.0.0",
+        "xtend": "^4.0.1"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "chalk": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+          "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+          "requires": {
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
+          }
+        },
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
     "sass-graph": {
       "version": "2.2.4",
       "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
         "yargs": "^7.0.0"
       }
     },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+    },
     "scss-tokenizer": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
       "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz",
       "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg=="
     },
-    "split": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
-      "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
-      "dev": true,
-      "requires": {
-        "through": "2"
-      }
-    },
     "split-string": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
         "extend-shallow": "^3.0.0"
       }
     },
+    "sqlite3": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.4.tgz",
+      "integrity": "sha512-CO8vZMyUXBPC+E3iXOCc7Tz2pAdq5BWfLcQmOokCOZW5S5sZ/paijiPOCdvzpdP83RroWHYa5xYlVqCxSqpnQg==",
+      "requires": {
+        "nan": "~2.10.0",
+        "node-pre-gyp": "^0.10.3",
+        "request": "^2.87.0"
+      },
+      "dependencies": {
+        "nan": {
+          "version": "2.10.0",
+          "resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
+          "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
+        }
+      }
+    },
+    "srcset": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz",
+      "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=",
+      "requires": {
+        "array-uniq": "^1.0.2",
+        "number-is-nan": "^1.0.0"
+      }
+    },
     "sshpk": {
       "version": "1.15.2",
       "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
         "readable-stream": "^2.0.1"
       }
     },
-    "stream-combiner": {
-      "version": "0.2.2",
-      "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
-      "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
-      "dev": true,
-      "requires": {
-        "duplexer": "~0.1.1",
-        "through": "~2.3.4"
-      }
-    },
     "stream-exhaust": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz",
     "strip-json-comments": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
-      "dev": true
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
     },
     "supports-color": {
       "version": "2.0.0",
         "execa": "^0.7.0"
       }
     },
-    "through": {
-      "version": "2.3.8",
-      "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
-      "dev": true
-    },
     "through2": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
       }
     },
     "ws": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz",
-      "integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==",
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz",
+      "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==",
       "requires": {
         "async-limiter": "~1.0.0"
       }
     "xtend": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
-      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
-      "dev": true
+      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
     },
     "y18n": {
       "version": "3.2.1",
index 6209227..72a326f 100644 (file)
     "morgan": "~1.9.1",
     "node-sass-middleware": "~0.11.0",
     "pug": "~2.0.3",
+    "sanitize-html": "^1.19.3",
     "serve-favicon": "~2.5.0",
-    "ws": "~6.1.0"
+    "sqlite3": "^4.0.4",
+    "ws": "^6.1.2"
   },
   "devDependencies": {
-    "gulp-nodemon": "~2.4.1"
+    "gulp-nodemon": "^2.4.2"
   }
 }
index 533bc0e..278e078 100644 (file)
@@ -409,7 +409,7 @@ Vue.component('my-game', {
                                        }),
                                h('div',
                                        {
-                                               attrs: { "role": "dialog", "aria-labelledby": "modal-eog" },
+                                               attrs: { "role": "dialog", "aria-labelledby": "eogMessage" },
                                        },
                                        [
                                                h('div',
@@ -425,6 +425,7 @@ Vue.component('my-game', {
                                                                ),
                                                                h('h3',
                                                                        {
+                                                                               attrs: { "id": "eogMessage" },
                                                                                "class": { "section": true },
                                                                                domProps: { innerHTML: eogMessage },
                                                                        }
@@ -445,7 +446,7 @@ Vue.component('my-game', {
                                }),
                        h('div',
                                {
-                                       attrs: { "role": "dialog", "aria-labelledby": "modal-newgame" },
+                                       attrs: { "role": "dialog", "aria-labelledby": "newGameTxt" },
                                },
                                [
                                        h('div',
@@ -461,6 +462,7 @@ Vue.component('my-game', {
                                                        ),
                                                        h('h3',
                                                                {
+                                                                       attrs: { "id": "newGameTxt" },
                                                                        "class": { "section": true },
                                                                        domProps: { innerHTML: "New game" },
                                                                }
@@ -485,7 +487,7 @@ Vue.component('my-game', {
                                }),
                        h('div',
                                {
-                                       attrs: { "role": "dialog", "aria-labelledby": "modal-fenedit" },
+                                       attrs: { "role": "dialog", "aria-labelledby": "titleFenedit" },
                                },
                                [
                                        h('div',
@@ -501,6 +503,7 @@ Vue.component('my-game', {
                                                        ),
                                                        h('h3',
                                                                {
+                                                                       attrs: { "id": "titleFenedit" },
                                                                        "class": { "section": true },
                                                                        domProps: { innerHTML: "Position + flags (FEN):" },
                                                                }
@@ -551,7 +554,7 @@ Vue.component('my-game', {
                                }),
                        h('div',
                                {
-                                       attrs: { "role": "dialog", "aria-labelledby": "modal-settings" },
+                                       attrs: { "role": "dialog", "aria-labelledby": "settingsTitle" },
                                },
                                [
                                        h('div',
@@ -567,6 +570,7 @@ Vue.component('my-game', {
                                                        ),
                                                        h('h3',
                                                                {
+                                                                       attrs: { "id": "settingsTitle" },
                                                                        "class": { "section": true },
                                                                        domProps: { innerHTML: "Preferences" },
                                                                }
diff --git a/public/javascripts/components/problemSummary.js b/public/javascripts/components/problemSummary.js
new file mode 100644 (file)
index 0000000..6003f08
--- /dev/null
@@ -0,0 +1,17 @@
+// Show a problem summary on variant page
+Vue.component('my-problem-summary', {
+       props: ['prob'],
+       template: `
+               <div class="problem col-sm-12">
+                       <div class="diagram">
+                               {{ getDiagram(prob.fen) }}
+                       </div>
+                       <div class="problem-instructions">
+                               {{ prob.instructions.substr(0,32) }}
+                       </div>
+                       <div class="problem-time">
+                               {{ prob.added }}
+                       </div>
+               </div>
+       `,
+})
index dc32052..594d6e6 100644 (file)
@@ -1,23 +1,81 @@
-//TODO: list problems as FEN (quickly rendered), by date, with possible filtering per variant(?)
-//click on a problem ==> land on variant page with mode==friend, FEN prefilled... ok
-
-// get 10 first problems, and buttons next<>previous send date + "before" or "after"
-// need database: sqlite !
-
-// form "new problem" fen(position/turn/flags[guess]), instructions, solution (mandatory)
-// ==> upload on server in sandbox
-//
-// Atomic rules, atomic game, atomic problems(list drawn position
-//   + summary(first chars of instructions) + timedate)... one big Vue ? with components
-//
-// click on problem ==> masque problems, affiche game tab, launch new game Friend with
-//   FEN + turn + flags + rappel instructions / solution on click sous l'échiquier
-
 Vue.component('my-problems', {
-       //props: ['vobj'],
+       data: function () {
+               return {
+                       problems: problemArray //initial value
+               };
+       },
        template: `
-               <div class="variant col-sm-12">
-                       <p>Hello</p>
+               <div>
+                       <button>Previous</button>
+                       <button>Next</button>
+                       <button @click="showNewproblemModal">New</button>
+                       <my-problem-summary
+                               v-for="(p,idx) in sortedProblems",
+                               v-bind:prob="p",
+                               v-bind:key="idx",
+                               @click="showProblem(p)"
+                       </my-problem-summary>
+                       <input type="checkbox" id="modal-newproblem" class="modal">
+                       <div role="dialog" aria-labelledby="newProblemTxt">
+                               <div class="card newproblem">
+                                       <label for="modal-newproblem" class="modal-close"></label>
+                                       <h3 id="newProblemTxt">Add problem</h3>
+                                       <form @submit.prevent="postNewProblem">
+                                               <fieldset>
+                                                       <label for="newpbFen">Fen</label>
+                                                       <input type="text" id="newpbFen" placeholder="Position [+ flags [+ turn]]"/>
+                                               </fieldset>
+                                               <fieldset>
+                                                       <p class="emphasis">
+                                                               Allowed HTML tags:
+                                                               &lt;p&gt;,&lt;br&gt;,&lt,ul&gt;,&lt;ol&gt;,&lt;li&gt;
+                                                       </p>
+                                                       <label for="newpbInstructions">Instructions</label>
+                                                       <textarea id="newpbInstructions" placeholder="Explain the problem here"/>
+                                                       <label for="newpbSolution">Solution</label>
+                                                       <textarea id="newpbSolution" placeholder="How to solve the problem?"/>
+                                                       <button class="center-btn">Send</button>
+                                               </fieldset>
+                                               <p class="mistake-newproblem">
+                                                       Note: if you made a mistake, please let me know at
+                                                       <a :href="mailErrProblem">contact@vchess.club</a>
+                                               </p>
+                                       </form>
+                               </div>
+                       </div>
                </div>
        `,
+       computed: {
+               sortedProblems: function() {
+                       // Newest problem first
+                       return problems.sort((p1,p2) => { return p2.added - p1.added; });
+               },
+               mailErrProblem: function() {
+                       return "mailto:contact@vchess.club?subject=[" + variant + " problems] error";
+               },
+       },
+       methods: {
+               fetchProblems: function(direction) {
+                       // TODO: ajax call return list of max 10 problems
+                       // Do not do anything if no older problems (and store this result in cache!)
+                       // TODO: ajax call return list of max 10 problems
+                       // Do not do anything if no newer problems
+               },
+               showProblem: function(prob) {
+                       //TODO: send event with object prob.fen, prob.instructions, prob.solution
+                       //Event should propagate to game, which set mode=="problem" + other variables
+                       //click on a problem ==> land on variant page with mode==friend, FEN prefilled... ok
+                       // click on problem ==> masque problems, affiche game tab, launch new game Friend with
+                       //   FEN + turn + flags + rappel instructions / solution on click sous l'échiquier
+               },
+               showNewproblemModal: function() {
+                       document.getElementById("modal-newproblem").checked = true;
+               },
+               postNewProblem: function() {
+                       const fen = document.getElementById("newpbFen").value;
+                       const instructions = document.getElementById("newpbInstructions").value;
+                       const solution = document.getElementById("newpbSolution").value;
+                       
+               },
+       },
 })
index c3107a6..660f0be 100644 (file)
@@ -6,66 +6,22 @@ Vue.component('my-rules', {
        template: `<div v-html="content" class="section-content"></div>`,
        mounted: function() {
                // AJAX request to get rules content (plain text, HTML)
-               let xhr = new XMLHttpRequest();
-               let self = this;
-               xhr.onreadystatechange = function() {
-                       if (this.readyState == 4 && this.status == 200)
-                       {
-                               let replaceByDiag = (match, p1, p2) => { return self.drawDiag(p2); };
-                               self.content = xhr.responseText.replace(/(fen:)([^:]*):/g, replaceByDiag);
-                       }
-               };
-               xhr.open("GET", "/rules/" + variant, true);
-               xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
-               xhr.send();
+               ajax("/rules/" + variant, "GET", response => {
+                       let replaceByDiag = (match, p1, p2) => {
+                               const args = self.parseFen(p2);
+                               return getDiagram(args);
+                       };
+                       self.content = response.replace(/(fen:)([^:]*):/g, replaceByDiag);
+               }
        },
        methods: {
-               drawDiag: function(fen) {
-                       let [sizeX,sizeY] = [V.size.x,V.size.y];
-                       let fenParts = fen.split(" ");
-                       // Obtain array of pieces images names
-                       let board = VariantRules.GetBoard(fenParts[0]);
-                       let orientation = "w";
-                       if (fenParts.length >= 2)
-                               orientation = fenParts[1];
-                       let markArray = [];
-                       if (fenParts.length >= 3)
-                       {
-                               let marks_str = fenParts[2];
-                               // Turn (human) marks into coordinates
-                               markArray = doubleArray(sizeX, sizeY, false);
-                               let marks = marks_str.split(",");
-                               for (let i=0; i<marks.length; i++)
-                               {
-                                       var res = /^([a-z]+)([0-9]+)$/i.exec(marks[i]);
-                                       let x = sizeX - parseInt(res[2]); //white at bottom, so counting is reversed
-                                       let y = res[1].charCodeAt(0)-97; //always one char: max 26, big enough
-                                       markArray[x][y] = true;
-                               }
-                       }
-                       let boardDiv = "";
-                       let [startX,startY,inc] = orientation == 'w'
-                               ? [0, 0, 1]
-                               : [sizeX-1, sizeY-1, -1];
-                       for (let i=startX; i>=0 && i<sizeX; i+=inc)
-                       {
-                               boardDiv += "<div class='row'>";
-                               for (let j=startY; j>=0 && j<sizeY; j+=inc)
-                               {
-                                       boardDiv += "<div class='board board" + sizeY + " " +
-                                               ((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") + "'>";
-                                       if (board[i][j] != VariantRules.EMPTY)
-                                       {
-                                               boardDiv += "<img src='/images/pieces/" +
-                                                       VariantRules.getPpath(board[i][j]) + ".svg' class='piece'/>";
-                                       }
-                                       if (markArray.length>0 && markArray[i][j])
-                                               boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
-                                       boardDiv += "</div>";
-                               }
-                               boardDiv += "</div>";
-                       }
-                       return boardDiv;
+               parseFen(fen) {
+                       const fenParts = fen.split(" ");
+                       return {
+                               position: fenParts[0],
+                               marks: fenParts[1],
+                               orientation: fenParts[2],
+                       };
                },
        },
 })
diff --git a/public/javascripts/utils/ajax.js b/public/javascripts/utils/ajax.js
new file mode 100644 (file)
index 0000000..ab288bc
--- /dev/null
@@ -0,0 +1,55 @@
+// From JSON (encoded string values!) to "arg1=...&arg2=..."
+function toQueryString(data)
+{
+       let data_str = "";
+       Object.keys(data).forEach(k => {
+               data_str += k + "=" + data[k] + "&";
+       });
+       return data_str.slice(0, -1); //remove last "&"
+}
+
+// data, error: optional
+function ajax(url, method, data, success, error)
+{
+       let xhr = new XMLHttpRequest();
+       if (typeof(data) === "function") //no data
+       {
+               error = success;
+               success = data;
+               data = {};
+       }
+       if (!error)
+               error = errmsg => { alert(errmsg); };
+
+       xhr.onreadystatechange = function() {
+               if (this.readyState == 4 && this.status == 200)
+               {
+                       try {
+                               let res_json = JSON.parse(xhr.responseText);
+                               if (!res_json.errmsg)
+                                       success(res_json);
+                               else
+                                       error(res_json.errmsg);
+                       } catch (e) {
+                               // Plain text (e.g. for rules retrieval)
+                               success(xhr.responseText);
+                       }
+               }
+       };
+
+       if (["GET","DELETE"].includes(method) && !!data)
+       {
+               // Append query params to URL
+               url += "/?" + toQueryString(data);
+       }
+
+       xhr.open(method, url, true);
+       xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
+       if (["POST","PUT"].includes(method))
+       {
+               xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
+               xhr.send(JSON.stringify(data));
+       }
+       else
+               xhr.send();
+}
diff --git a/public/javascripts/utils/printDiagram.js b/public/javascripts/utils/printDiagram.js
new file mode 100644 (file)
index 0000000..ef75049
--- /dev/null
@@ -0,0 +1,46 @@
+// Assuming V(ariantRules) class is loaded.
+// args: object with position (mandatory), orientation, marks (optional)
+function getDiagram(args)
+{
+       const [sizeX,sizeY] = [V.size.x,V.size.y];
+       // Obtain array of pieces images names
+       const board = VariantRules.GetBoard(args.position);
+       const orientation = args.orientation || "w";
+       let markArray = [];
+       if (!!args.marks)
+       {
+               // Turn (human) marks into coordinates
+               markArray = doubleArray(sizeX, sizeY, false);
+               let squares = args.marks.split(",");
+               for (let i=0; i<squares.length; i++)
+               {
+                       const res = /^([a-z]+)([0-9]+)$/i.exec(squares[i]);
+                       const x = sizeX - parseInt(res[2]); //white at bottom, so counting is reversed
+                       const y = res[1].charCodeAt(0)-97; //always one char: max 26, big enough
+                       markArray[x][y] = true;
+               }
+       }
+       let boardDiv = "";
+       const [startX,startY,inc] = orientation == 'w'
+               ? [0, 0, 1]
+               : [sizeX-1, sizeY-1, -1];
+       for (let i=startX; i>=0 && i<sizeX; i+=inc)
+       {
+               boardDiv += "<div class='row'>";
+               for (let j=startY; j>=0 && j<sizeY; j+=inc)
+               {
+                       boardDiv += "<div class='board board" + sizeY + " " +
+                               ((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") + "'>";
+                       if (board[i][j] != V.EMPTY)
+                       {
+                               boardDiv += "<img src='/images/pieces/" +
+                                       V.getPpath(board[i][j]) + ".svg' class='piece'/>";
+                       }
+                       if (!!args.marks && markArray[i][j])
+                               boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
+                       boardDiv += "</div>";
+               }
+               boardDiv += "</div>";
+       }
+       return boardDiv;
+}
index f5bffc3..f804756 100644 (file)
@@ -245,3 +245,25 @@ ul:not(.browser-default) > li
 #fen-div > p
   margin-left: 0
   margin-right: 0
+
+.newproblem input, .newproblem textarea
+  width: 100%
+
+.emphasis
+  font-style: italic
+
+#newpbInstructions
+  margin-bottom: var(--universal-margin);
+
+.center-btn
+  margin-left: 40%
+
+//TODO?
+.center-inline
+  text-align: center
+.center-block
+  margin-left: auto
+  margin-right: auto
+
+.mistake-newproblem
+  color: #663300
index 3e6dd00..f3e184e 100644 (file)
@@ -1,31 +1,78 @@
-var express = require('express');
-var router = express.Router();
-var createError = require('http-errors');
-
-const Variants = require("../variants");
+let express = require('express');
+let router = express.Router();
+const createError = require('http-errors');
+const sqlite3 = require('sqlite3');//.verbose();
+const db = new sqlite3.Database('db/vchess.sqlite');
+const sanitizeHtml = require('sanitize-html');
 
 // Home
 router.get('/', function(req, res, next) {
-  res.render('index', {
-               title: 'club',
-               variantArray: Variants, //JSON.stringify(Variants)
+       db.serialize(function() {
+               db.all("SELECT * FROM Variants", (err,variants) => {
+                       if (!!err)
+                               return next(err);
+                       res.render('index', {
+                               title: 'club',
+                               variantArray: variants, //JSON.stringify(variants)
+                       });
+               });
        });
 });
 
 // Variant
 router.get("/:vname([a-zA-Z0-9]+)", (req,res,next) => {
        const vname = req.params["vname"];
-       if (!Variants.some(v => { return (v.name == vname); }))
-               return next(createError(404));
-       res.render('variant', {
-               title: vname + ' Variant',
-               variant: vname,
+       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));
+                       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,
+                                       });
+                               }
+                       );
+               });
        });
 });
 
 // Load a rules page (AJAX)
 router.get("/rules/:variant([a-zA-Z0-9]+)", (req,res) => {
-  res.render("rules/" + req.params["variant"]);
+       if (!req.xhr)
+               return res.json({errmsg: "Unauthorized access"});
+       res.render("rules/" + req.params["variant"]);
+});
+
+// Fetch 10 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)
 });
 
+// Upload a problem (AJAX)
+router.post("/problems/:variant([a-zA-Z0-9]+)", (req,res) => {
+       if (!req.xhr)
+               return res.json({errmsg: "Unauthorized access"});
+       const vname = req.params["variant"];
+       
+       // TODO: get parameters and sanitize them
+       sanitizeHtml(req.body["fen"]); // [/a-z0-9 ]*
+       sanitizeHtml(req.body["instructions"]);
+       db.serialize(function() {
+               let stmt = db.prepare("INSERT INTO Problems VALUES (?,?,?,?,?)");
+               stmt.run(timestamp, vname, fen, instructions, solution);
+               stmt.finalize();
+       });
+  res.json({});
+});
+
+
 module.exports = router;
index 675a80e..f1354f3 100644 (file)
 const url = require('url');
-const Variants = require("./variants");
+const sqlite3 = require('sqlite3');
+const db = new sqlite3.Database('db/vchess.sqlite');
 
 module.exports = function(wss) {
-
-       let clients = { "index": {} };
-       let games = {}; //pending games (player sid)
-       for (const v of Variants)
-               clients[v.name] = {};
-
-//     // Safety counter (TODO: is it necessary ?)
-//     setInterval(() => {
-//             Object.keys(clients).forEach(k => {
-//                     Object.keys(clients[k]).forEach(ck => {
-//                             if (!clients[k][ck] || clients[k][ck].readyState != 1)
-//                                     delete clients[k][ck];
-//                     });
-//             });
-//     }, 60000); //every minute (will be lowered if a lot of users...)
-
-       // No-op function as a callback when sending messages
-       const noop = () => { };
-
-       wss.on("connection", (socket, req) => {
-               const params = new URL("http://localhost" + req.url).searchParams;
-               const sid = params.get("sid");
-               const page = params.get("page");
-               // Ignore duplicate connections:
-               if (!!clients[page][sid])
-               {
-                       socket.send(JSON.stringify({code:"duplicate"}));
-                       return;
-               }
-               clients[page][sid] = socket;
-               if (page == "index")
-               {
-                       // Send counting info
-                       const countings = {};
-                       for (const v of Variants)
-                               countings[v.name] = Object.keys(clients[v.name]).length;
-                       socket.send(JSON.stringify({code:"counts",counts:countings}));
-               }
-               else
-               {
-                       // Send to every client connected on index an update message for counts
-                       Object.keys(clients["index"]).forEach( k => {
-                               clients["index"][k].send(JSON.stringify({code:"increase",vname:page}), noop);
-                       });
-                       // Also notify potential opponents: hit all clients which check if sid corresponds
-                       Object.keys(clients[page]).forEach( k => {
-                               clients[page][k].send(JSON.stringify({code:"connect",id:sid}), noop);
-                       });
-                       socket.on("message", objtxt => {
-                               let obj = JSON.parse(objtxt);
-                               switch (obj.code)
+       db.serialize(function() {
+               db.all("SELECT * FROM Variants", (err,variants) => {
+                       let clients = { "index": {} };
+                       let games = {}; //pending games (player sid)
+                       for (const v of variants)
+                               clients[v.name] = {};
+                       // No-op function as a callback when sending messages
+                       const noop = () => { };
+                       wss.on("connection", (socket, req) => {
+                               const params = new URL("http://localhost" + req.url).searchParams;
+                               const sid = params.get("sid");
+                               const page = params.get("page");
+                               // Ignore duplicate connections:
+                               if (!!clients[page][sid])
                                {
-                                       case "newmove":
-                                               if (!!clients[page][obj.oppid])
-                                               {
-                                                       clients[page][obj.oppid].send(
-                                                               JSON.stringify({code:"newmove",move:obj.move}), noop);
-                                               }
-                                               break;
-                                       case "ping":
-                                               if (!!clients[page][obj.oppid])
-                                                       socket.send(JSON.stringify({code:"pong"}));
-                                               break;
-                                       case "lastate":
-                                               if (!!clients[page][obj.oppid])
-                                               {
-                                                       const oppId = obj.oppid;
-                                                       obj.oppid = sid; //I'm oppid for my opponent
-                                                       clients[page][oppId].send(JSON.stringify(obj), noop);
-                                               }
-                                               break;
-                                       case "newgame":
-                                               if (!!games[page])
+                                       socket.send(JSON.stringify({code:"duplicate"}));
+                                       return;
+                               }
+                               clients[page][sid] = socket;
+                               if (page == "index")
+                               {
+                                       // Send counting info
+                                       const countings = {};
+                                       for (const v of variants)
+                                               countings[v.name] = Object.keys(clients[v.name]).length;
+                                       socket.send(JSON.stringify({code:"counts",counts:countings}));
+                               }
+                               else
+                               {
+                                       // Send to every client connected on index an update message for counts
+                                       Object.keys(clients["index"]).forEach( k => {
+                                               clients["index"][k].send(
+                                                       JSON.stringify({code:"increase",vname:page}), noop);
+                                       });
+                                       // Also notify potential opponents:
+                                       // hit all clients which check if sid corresponds
+                                       Object.keys(clients[page]).forEach( k => {
+                                               clients[page][k].send(JSON.stringify({code:"connect",id:sid}), noop);
+                                       });
+                                       socket.on("message", objtxt => {
+                                               let obj = JSON.parse(objtxt);
+                                               switch (obj.code)
                                                {
-                                                       // Start a new game
-                                                       const oppId = games[page]["id"];
-                                                       const fen = games[page]["fen"];
-                                                       delete games[page];
-                                                       const mycolor = Math.random() < 0.5 ? 'w' : 'b';
-                                                       socket.send(
-                                                               JSON.stringify({code:"newgame",fen:fen,oppid:oppId,color:mycolor}));
-                                                       if (!!clients[page][oppId])
-                                                       {
-                                                               clients[page][oppId].send(
-                                                                       JSON.stringify(
-                                                                               {code:"newgame",fen:fen,oppid:sid,color:mycolor=="w"?"b":"w"}),
-                                                                       noop);
-                                                       }
+                                                       case "newmove":
+                                                               if (!!clients[page][obj.oppid])
+                                                               {
+                                                                       clients[page][obj.oppid].send(
+                                                                               JSON.stringify({code:"newmove",move:obj.move}), noop);
+                                                               }
+                                                               break;
+                                                       case "ping":
+                                                               if (!!clients[page][obj.oppid])
+                                                                       socket.send(JSON.stringify({code:"pong"}));
+                                                               break;
+                                                       case "lastate":
+                                                               if (!!clients[page][obj.oppid])
+                                                               {
+                                                                       const oppId = obj.oppid;
+                                                                       obj.oppid = sid; //I'm oppid for my opponent
+                                                                       clients[page][oppId].send(JSON.stringify(obj), noop);
+                                                               }
+                                                               break;
+                                                       case "newgame":
+                                                               if (!!games[page])
+                                                               {
+                                                                       // Start a new game
+                                                                       const oppId = games[page]["id"];
+                                                                       const fen = games[page]["fen"];
+                                                                       delete games[page];
+                                                                       const mycolor = Math.random() < 0.5 ? 'w' : 'b';
+                                                                       socket.send(JSON.stringify(
+                                                                               {code:"newgame",fen:fen,oppid:oppId,color:mycolor}));
+                                                                       if (!!clients[page][oppId])
+                                                                       {
+                                                                               clients[page][oppId].send(
+                                                                                       JSON.stringify(
+                                                                                               {code:"newgame",fen:fen,oppid:sid,color:mycolor=="w"?"b":"w"}),
+                                                                                       noop);
+                                                                       }
+                                                               }
+                                                               else
+                                                                       games[page] = {id:sid, fen:obj.fen}; //wait for opponent
+                                                               break;
+                                                       case "cancelnewgame": //if a user cancel his seek
+                                                               delete games[page];
+                                                               break;
+                                                       case "resign":
+                                                               if (!!clients[page][obj.oppid])
+                                                                       clients[page][obj.oppid].send(JSON.stringify({code:"resign"}), noop);
+                                                               break;
                                                }
-                                               else
-                                                       games[page] = {id:sid, fen:obj.fen}; //wait for opponent
-                                               break;
-                                       case "cancelnewgame": //if a user cancel his seek
-                                               delete games[page];
-                                               break;
-                                       case "resign":
-                                               if (!!clients[page][obj.oppid])
-                                                       clients[page][obj.oppid].send(JSON.stringify({code:"resign"}), noop);
-                                               break;
+                                       });
                                }
-                       });
-               }
-               socket.on("close", () => {
-                       delete clients[page][sid];
-                       // Remove potential pending game
-                       if (!!games[page] && games[page]["id"] == sid)
-                               delete games[page];
-                       if (page != "index")
-                       {
-                               // Send to every client connected on index an update message for counts
-                               Object.keys(clients["index"]).forEach( k => {
-                                       clients["index"][k].send(JSON.stringify({code:"decrease",vname:page}), noop);
+                               socket.on("close", () => {
+                                       delete clients[page][sid];
+                                       // Remove potential pending game
+                                       if (!!games[page] && games[page]["id"] == sid)
+                                               delete games[page];
+                                       if (page != "index")
+                                       {
+                                               // Send to every client connected on index an update message for counts
+                                               Object.keys(clients["index"]).forEach( k => {
+                                                       clients["index"][k].send(
+                                                               JSON.stringify({code:"decrease",vname:page}), noop);
+                                               });
+                                       }
+                                       // Also notify potential opponents:
+                                       // hit all clients which check if sid corresponds
+                                       Object.keys(clients[page]).forEach( k => {
+                                               clients[page][k].send(JSON.stringify({code:"disconnect",id:sid}), noop);
+                                       });
                                });
-                       }
-                       // Also notify potential opponents: hit all clients which check if sid corresponds
-                       Object.keys(clients[page]).forEach( k => {
-                               clients[page][k].send(JSON.stringify({code:"disconnect",id:sid}), noop);
                        });
                });
        });
diff --git a/variants.js b/variants.js
deleted file mode 100644 (file)
index 2a20ffd..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-module.exports = [
-       { "name": "Checkered", "description": "Shared pieces" },
-       { "name": "Zen", "description": "Reverse captures" },
-       { "name": "Atomic", "description": "Explosive captures" },
-       { "name": "Chess960", "description": "Standard rules" },
-       { "name": "Antiking", "description": "Keep antiking in check" },
-       { "name": "Magnetic", "description": "Laws of attraction" },
-       { "name": "Alice", "description": "Both sides of the mirror" },
-       { "name": "Grand", "description": "Big board" },
-       { "name": "Wildebeest", "description": "Balanced sliders & leapers" },
-       { "name": "Loser", "description": "Lose all pieces" },
-       { "name": "Crazyhouse", "description": "Captures reborn" },
-       { "name": "Switching", "description": "Exchange pieces positions" },
-       { "name": "Extinction", "description": "Capture all of a kind" },
-       { "name": "Ultima", "description": "Non-standard captures" },
-];
index 521c125..9ef1967 100644 (file)
@@ -99,7 +99,6 @@ block content
 block javascripts
        script.
                const variantArray = !{JSON.stringify(variantArray)};
-               //JSON.parse("!{variantArray}".replace(/\&quot;/g,'"'));
        script(src="/javascripts/utils/misc.js")
        script(src="/javascripts/utils/socket_url.js")
        script(src="/javascripts/components/variantSummary.js")
index 8b271c2..331bc5a 100644 (file)
@@ -83,7 +83,7 @@ p.
 
 figure.diagram-container
        .diagram
-               | fen:k7/2p5/5q2/2b5/4N3/2R3r1/3P4/7K f6,d6,c5,f2,g3,g5:
+               | fen:k7/2p5/5q2/2b5/4N3/2R3r1/3P4/7K f6,d6,c5,f2,g3,g5:
        figcaption Possible knight moves from e4.
 
 h4 Bishops
index 7a3d91b..004c026 100644 (file)
@@ -16,11 +16,20 @@ h3 Basics
 
 p
        | Orthodox rules apply, with only one change:
-       | every time you capture a piece, your "reserve" of waiting pieces is augmented
-       | with this figure. At every move, you may choose to land one of your reserve
-       | pieces anywhere on the board (except last rank for pawns),
+       | every time a piece is captured, the capturer "reserve" of waiting pieces is
+       | augmented with this figure. At every move, you may choose to land one of your
+       | reserve pieces anywhere on the board (except last rank for pawns),
        | instead of playing a regular move.
 
+p.
+       Move notation: an arobase '@' indicate piece landing. For example B@d4 means
+       a bishop rebirth on d4 square, and @f3 is a pawn landing on f3.
+
+figure.diagram-container
+       .diagram
+               | fen:qnbrkbnr/ppNppp1p/2p2p2/8/8/5N2/P1PPPPPP/BRNQKBNR:
+       figcaption After 1.b3 Nf6 2.Bxf6 gxf6 3.Nf3 c6?? 4.N@c7#
+
 p.
        Note: when a promoted pawn is captured, capturing it put a pawn in the reserve,
        not the promoted piece. This is to allow to gain material on last rank without
index ce2fdbc..8f1af19 100644 (file)
@@ -21,6 +21,10 @@ p
        | Switching must involves two different units.
        | Switching while the king is under check is not allowed.
 
+p.
+       Notation: a switch move is marked by the capital letter 'S' followed by
+       the exchanged squares. Example in diagram: Sb8c8
+
 figure.diagram-container
        .diagram
                | fen:1RB3k1/5ppp/8/8/8/8/8/K7:
index b385627..cf5da24 100644 (file)
@@ -60,7 +60,11 @@ p
        | All other pieces capture passively: they land on a free square and captured
        | units are determined by some characteristics of the movement.
 
-p Note: the immobilizer does not capture.
+p Note 1: the immobilizer does not capture.
+
+p.
+       Note 2: for passive captures, a 'X' is added at the end of the move notation,
+       to indicate that something was taken (replaying the game is necessary to know where).
 
 h4 Pawns/Pincers
 
index a613a0b..37bd5e7 100644 (file)
@@ -25,13 +25,17 @@ block javascripts
        script(src="/javascripts/utils/socket_url.js")
        script(src="/javascripts/utils/array.js")
        script(src="/javascripts/utils/md5.js")
+       script(src="/javascripts/utils/printDiagram.js")
+       script(src="/javascripts/utils/ajax.js")
        script(src="/javascripts/base_rules.js")
        script(src="/javascripts/variants/" + variant + ".js")
        script.
                const VariantRules = #{variant}Rules;
                const V = VariantRules; //because this variable is often used
                const variant = "#{variant}";
+               const problemArray = !{JSON.stringify(problemArray)};
        script(src="/javascripts/components/rules.js")
        script(src="/javascripts/components/game.js")
+       script(src="/javascripts/components/problemSummary.js")
        script(src="/javascripts/components/problems.js")
        script(src="/javascripts/variant.js")