diff --git a/package-lock.json b/package-lock.json index e0a5d6b..ae41cbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,10 @@ "license": "ISC", "dependencies": { "colors": "^1.4.0", + "cookie-parser": "^1.4.5", "express": "^4.17.1", "express-handlebars": "^4.0.4", + "express-locale": "^2.0.0", "fs-extra": "^9.1.0", "ioredis": "^4.26.0", "morgan": "^1.10.0", @@ -156,6 +158,18 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", + "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", + "dependencies": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -287,6 +301,11 @@ "node": ">=0.10" } }, + "node_modules/express-locale": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/express-locale/-/express-locale-2.0.0.tgz", + "integrity": "sha512-SUwsgOTV8lplAkgyfKSwKH3ZVSKUrAa+b48acPGXpwWX211WZ/BYWX2WcZO57+xLOqPYb6rSNpWCFnhGrt0moA==" + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -1114,6 +1133,15 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, + "cookie-parser": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", + "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6" + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -1224,6 +1252,11 @@ "promise": "^8.1.0" } }, + "express-locale": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/express-locale/-/express-locale-2.0.0.tgz", + "integrity": "sha512-SUwsgOTV8lplAkgyfKSwKH3ZVSKUrAa+b48acPGXpwWX211WZ/BYWX2WcZO57+xLOqPYb6rSNpWCFnhGrt0moA==" + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", diff --git a/package.json b/package.json index 1ccbbd2..cb99e41 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,10 @@ "homepage": "https://github.com/PretendoNetwork/website#readme", "dependencies": { "colors": "^1.4.0", + "cookie-parser": "^1.4.5", "express": "^4.17.1", "express-handlebars": "^4.0.4", + "express-locale": "^2.0.0", "fs-extra": "^9.1.0", "ioredis": "^4.26.0", "morgan": "^1.10.0", diff --git a/public/assets/css/dropdown.css b/public/assets/css/dropdown.css new file mode 100644 index 0000000..1d842fa --- /dev/null +++ b/public/assets/css/dropdown.css @@ -0,0 +1,106 @@ +.select-box { + display: flex; + width: 200px; + flex-direction: column; + position: relative; + user-select: none; +} + +.select-box > * { + box-sizing: border-box; +} + +.select-box .options-container { + max-height: 0; + width: calc(100% - 12px); + opacity: 0; + transition: all 0.4s; + overflow: hidden; + border-radius: 5px; + background-color: var(--btn-secondary); + order: 1; + position: absolute; + top: 50px; +} + +.selected-locale { + margin-bottom: 8px; + position: relative; + width: 188px; + height: 45px; + border-radius: 5px; + display: flex; + align-items: center; + background-color: var(--btn-secondary); + color: white; + order: 0; +} + +.selected-locale::after { + content: ""; + width: 1.2rem; + height: 1.2rem; + background: url("/assets/icons/down-arrow.svg"); + position: absolute; + right: 15px; + top: 50%; + transition: transform 150ms; + transform: translateY(-50%); + background-size: contain; + background-position: center; +} + +.select-box .option .item { + color: #afb5dd; +} + +.select-box .lang { + width: 1.3rem; + height: 1rem; + margin-right: .2rem; + display: inline-block; +} + +.select-box .options-container.active { + max-height: 240px; + opacity: 1; + overflow-y: auto; +} + +.select-box .options-container.active + .selected-locale::after { + transform: translateY(-50%) rotateX(180deg); +} + +.select-box .options-container::-webkit-scrollbar { + width: 8px; + background: #0d141f; + background: #81878f; + background: #f1f2f3; + border-radius: 0 5px 5px 0; +} + +.select-box .options-container::-webkit-scrollbar-thumb { + background: #525861; + background: #81878f; + border-radius: 0 5px 5px 0; +} +.select-box .option, +.selected-locale { + padding: 12px 15px; + cursor: pointer; +} + +.select-box .option:hover { + background: #2f345b; +} +.select-box .option:hover .item { + color: white; +} + +.select-box label { + cursor: pointer; +} + +.select-box .option .radio { + display: none; +} diff --git a/public/assets/css/main.css b/public/assets/css/main.css index c659843..2f0eb88 100644 --- a/public/assets/css/main.css +++ b/public/assets/css/main.css @@ -4,6 +4,7 @@ --text: white; --text-secondary: #A1A8D9; --btn: #673DB6; + --btn-secondary: #333960; --theme: var(--btn); --text-secondary-2: #8990C1; } @@ -80,6 +81,10 @@ header nav a { text-decoration: none; } +.locale-dropdown { + margin-left: auto; + z-index: 2; +} /* Misc */ .purple-circle { @@ -114,7 +119,7 @@ header nav a { left: 50%; transform: translate(-50%, -50%); margin-left: -40px; - z-index: 3; + z-index: 1; } .light-purple-circle .deco { position: absolute; @@ -124,6 +129,15 @@ header nav a { z-index: -2; } +/* Twemoji */ + +img.emoji { + height: 1em; + width: 1em; + margin: 0 .05em 0 .1em; + vertical-align: -0.1em; + } + /* Title business */ .sect .title[id] { padding-top: 100px; @@ -217,7 +231,7 @@ header nav a { background: var(--btn); } .button.secondary { - background: #333960; + background: var(--btn-secondary); } .button.secondary.discord svg { margin-bottom: -1px; diff --git a/public/assets/icons/down-arrow.svg b/public/assets/icons/down-arrow.svg new file mode 100644 index 0000000..619f62a --- /dev/null +++ b/public/assets/icons/down-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/js/locale-dropdown-handler.js b/public/assets/js/locale-dropdown-handler.js new file mode 100644 index 0000000..7e8dc6d --- /dev/null +++ b/public/assets/js/locale-dropdown-handler.js @@ -0,0 +1,56 @@ +function setDefaultDropdownLocale(localeString) { + const selected = document.querySelector(".selected-locale"); + selected.innerHTML = document.querySelector(`label[for=${localeString}`).innerHTML +} + +function localeDropdownHandler(selectedLocale) { + document.cookie = `preferredLocale=${selectedLocale};max-age=31536000`; + window.location.reload(); +} + +const dropdowns = document.querySelectorAll("*[data-dropdown]"); +const dropdownOptions = document.querySelectorAll( + "*[data-dropdown] .options-container" +); + +dropdowns.forEach((el) => { + const selected = el.querySelector(".selected-locale"); + const optionsContainer = el.querySelector(".options-container"); + const optionsList = el.querySelectorAll(".option"); + + // click dropdown element will open dropdown + selected.addEventListener("click", () => { + optionsContainer.classList.toggle("active"); + }); + + // clicking on any option will close dropdown and change value + optionsList.forEach((option) => { + option.addEventListener("click", () => { + selected.innerHTML = option.querySelector("label").innerHTML; + optionsContainer.classList.remove("active"); + const selectedLocale = option.querySelector("label").getAttribute("for"); + localeDropdownHandler(selectedLocale); + }); + }); +}); + +// close all dropdowns on scroll +document.addEventListener("scroll", () => { + dropdownOptions.forEach((el) => el.classList.remove("active")); +}); + +// click outside of dropdown will close all dropdowns +document.addEventListener("click", (e) => { + const targetElement = e.target; + + // check if target is from a dropdown + let found = false; + dropdowns.forEach((v) => { + if (v == targetElement || v.contains(targetElement)) found = true; + }); + + if (found) return; + + // click outside of dropdowns + dropdownOptions.forEach((el) => el.classList.remove("active")); +}); diff --git a/src/routers/home.js b/src/routers/home.js index 84dba26..8d02c6b 100644 --- a/src/routers/home.js +++ b/src/routers/home.js @@ -5,12 +5,16 @@ const router = new Router(); const { getTrelloCache } = require('../trello'); router.get('/', async (request, response) => { - const tmpLocale = util.getLocale('US', 'en'); + + const reqLocale = request.locale + const locale = util.getLocale(reqLocale.region, reqLocale.language); + const cache = await getTrelloCache(); response.render('home', { layout: 'main', - locale: tmpLocale, + locale, + localeString: reqLocale.toString(), featuredFeatureList: cache.sections[0] }); }); diff --git a/src/routers/progress.js b/src/routers/progress.js index 6d590f4..b7ed205 100644 --- a/src/routers/progress.js +++ b/src/routers/progress.js @@ -5,12 +5,18 @@ const router = new Router(); const { getTrelloCache } = require('../trello'); router.get('/', async (request, response) => { - const tmpLocale = util.getLocale('US', 'en'); + + const reqLocale = request.locale + const locale = util.getLocale(reqLocale.region, reqLocale.language); + + const localeString = reqLocale.toString() + const cache = await getTrelloCache(); response.render('progress', { layout: 'main', - locale: tmpLocale, + locale, + localeString, progressLists: cache }); }); diff --git a/src/server.js b/src/server.js index 9136623..24cf770 100644 --- a/src/server.js +++ b/src/server.js @@ -3,6 +3,8 @@ process.title = 'Pretendo - Website'; const express = require('express'); const handlebars = require('express-handlebars'); const morgan = require('morgan'); +const expressLocale = require('express-locale'); +const cookieParser = require('cookie-parser') const logger = require('./logger'); const util = require('./util'); const config = require('../config.json'); @@ -23,6 +25,28 @@ const routers = { progress: require('./routers/progress') }; +app.use(cookieParser()) + +// Locale express middleware setup +app.use(expressLocale({ + "priority": ["cookie", "accept-language", "map", "default"], + cookie: {name: 'preferredLocale'}, + // fallbacks for browsers that don't send a region code, but only a language + map: { + de: 'de-DE', + es: 'es-ES', + fr: 'fr-FR', + it: 'it-IT', + ko: 'ko-KR', + ru: 'ru-RU', + tr: 'tr-TR' + }, + "default": "en-US" +})) + + + + app.use('/', routers.home); app.use('/faq', routers.faq); app.use('/progress', routers.progress); diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 267ebab..eb50001 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -28,11 +28,20 @@