From 89f5d8e5821d4e2513274163149f529192f1cff3 Mon Sep 17 00:00:00 2001 From: Miodec Date: Mon, 29 Aug 2022 02:24:28 +0200 Subject: [PATCH 01/14] restyled configure api button --- frontend/src/styles/core.scss | 6 ++++-- frontend/src/ts/ready.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/styles/core.scss b/frontend/src/styles/core.scss index c74afcb7c..55075a107 100644 --- a/frontend/src/styles/core.scss +++ b/frontend/src/styles/core.scss @@ -470,13 +470,15 @@ key { .configureAPI.button { position: fixed; - left: 2rem; - bottom: 2rem; + left: 0; + top: 10rem; display: grid; grid-auto-flow: column; gap: 0.5rem; text-decoration: none; z-index: 999999999; + border-radius: 0 1rem 1rem 0; + padding: 1rem; } .avatar { diff --git a/frontend/src/ts/ready.ts b/frontend/src/ts/ready.ts index 31932ebd5..93446d92a 100644 --- a/frontend/src/ts/ready.ts +++ b/frontend/src/ts/ready.ts @@ -16,7 +16,7 @@ if (window.location.hostname === "localhost") { $("#bottom .version .text").text("localhost"); $("#bottom .version").css("opacity", 1); $("body").prepend( - `Configure Server` + `` ); } else { Misc.getReleasesFromGitHub().then((v) => { From 6d4ec0182b2b68d1c1b0b70532615bcf41560cfe Mon Sep 17 00:00:00 2001 From: Miodec Date: Mon, 29 Aug 2022 22:53:04 +0200 Subject: [PATCH 02/14] fixed poetry soft locking accounts --- frontend/src/ts/test/poetry.ts | 8 +++----- frontend/src/ts/test/test-logic.ts | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/ts/test/poetry.ts b/frontend/src/ts/test/poetry.ts index 1f8855cf7..71b411c38 100644 --- a/frontend/src/ts/test/poetry.ts +++ b/frontend/src/ts/test/poetry.ts @@ -45,12 +45,11 @@ interface PoemObject { author: string; } -export async function getPoem(): Promise { +export async function getPoem(): Promise { console.log("Getting poem"); - const response = await axios.get(apiURL); - try { + const response = await axios.get(apiURL); const poemObj: PoemObject = response.data[0]; const words: string[] = []; @@ -64,7 +63,6 @@ export async function getPoem(): Promise { return new Poem(poemObj.title, poemObj.author, words); } catch (e) { console.log(e); + return false; } - - return; } diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index d3d53e472..e727489fd 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -947,7 +947,18 @@ export async function init(): Promise { ? await Wikipedia.getSection(Config.language) : await Poetry.getPoem(); + if (Config.funbox == "poetry" && section === false) { + Notifications.add( + "Error while getting poetry. Please try again later", + -1 + ); + UpdateConfig.setFunbox("none"); + restart(); + return; + } + if (section === undefined) continue; + if (section === false) continue; for (const word of section.words) { if (wordCount >= Config.words && Config.mode == "words") { @@ -1156,7 +1167,19 @@ export async function addWord(): Promise { ? await Wikipedia.getSection(Config.language) : await Poetry.getPoem(); + if (Config.funbox == "poetry" && section === false) { + Notifications.add( + "Error while getting poetry. Please try again later", + -1 + ); + UpdateConfig.setFunbox("none"); + restart(); + return; + } + if (section === undefined) return; + if (section === false) return; + let wordCount = 0; for (const word of section.words) { if (wordCount >= Config.words && Config.mode == "words") { From cd1c6aabb690f81ce263f87866cd39fb22ce31a7 Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 30 Aug 2022 14:18:55 +0200 Subject: [PATCH 03/14] allowing capitals in custom --- frontend/src/ts/test/test-logic.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index e727489fd..366aa2256 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -763,7 +763,9 @@ async function getNextWord( let regenarationCount = 0; //infinite loop emergency stop button while ( regenarationCount < 100 && - ((/[A-Z]/.test(randomWord) && !Config.punctuation) || + ((Config.mode !== "custom" && + /[A-Z]/.test(randomWord) && + !Config.punctuation) || previousWord == randomWord || previousWord2 == randomWord || (Config.mode !== "custom" && From 3fbc9ca4639992a99205e0cc80622ff895fa7f81 Mon Sep 17 00:00:00 2001 From: FarisDaffa <65797160+Faris0520@users.noreply.github.com> Date: Tue, 30 Aug 2022 19:32:42 +0700 Subject: [PATCH 04/14] Add words and quotes to indonesian language (#3455) faris0520 * Update indonesian.json * Update indonesian.json * Update indonesian.json --- frontend/static/languages/indonesian.json | 8 ++++++++ frontend/static/quotes/indonesian.json | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/frontend/static/languages/indonesian.json b/frontend/static/languages/indonesian.json index 7ea7a4366..8e186b47c 100644 --- a/frontend/static/languages/indonesian.json +++ b/frontend/static/languages/indonesian.json @@ -82,6 +82,7 @@ "demikian", "dengan", "depan", + "desa", "detik", "di", "dia", @@ -89,6 +90,7 @@ "dua", "dulu", "ganti", + "gila", "habis", "hanya", "hari", @@ -107,6 +109,7 @@ "jarak", "jika", "jumlah", + "jari", "kalah", "kalau", "kalimat", @@ -123,6 +126,7 @@ "kemarin", "kembali", "kemudian", + "kemana", "keras", "kerja", "kertas", @@ -154,6 +158,7 @@ "membuat", "memerlukan", "memiliki", + "mencuri", "menang", "mengapa", "mengatakan", @@ -191,6 +196,7 @@ "radius", "ragam", "ras", + "rasa", "rasio", "rasional", "rayu", @@ -261,6 +267,7 @@ "tinggi", "tua", "tumbuh", + "tunas", "tunggu", "turun", "tutup", @@ -268,6 +275,7 @@ "untuk", "wahai", "wajah", + "wajar", "waktu", "wanita", "yaitu", diff --git a/frontend/static/quotes/indonesian.json b/frontend/static/quotes/indonesian.json index 8e93982c6..b88fa152f 100644 --- a/frontend/static/quotes/indonesian.json +++ b/frontend/static/quotes/indonesian.json @@ -1260,6 +1260,18 @@ "source": "Wa Brontok, Hansip Sukrawetan Indramayu", "length": 209, "id": 210 + }, + { + "text": "Saya manusia biasa, makan nasi.", + "source": "Joko Widodo", + "length": 31, + "id": 211 + }, + { + "text": "Kok kamu tanya begitu, siapa yang suruh?", + "source": "Soeharto", + "length": 40, + "id": 212 } ] } From 59be665418975ea8d8c005b014c75cb106d1d6f6 Mon Sep 17 00:00:00 2001 From: Sat Naing Date: Tue, 30 Aug 2022 19:08:50 +0630 Subject: [PATCH 05/14] add Myanmar(Burmese) language (#3471) satnaing --- frontend/package-lock.json | 10 + frontend/static/languages/_groups.json | 4 + frontend/static/languages/_list.json | 1 + .../static/languages/myanmar_burmese.json | 208 ++++++++++++++++++ 4 files changed, 223 insertions(+) create mode 100644 frontend/static/languages/myanmar_burmese.json diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 76b5779e1..8616541c3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7188,6 +7188,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", @@ -20123,6 +20128,11 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", diff --git a/frontend/static/languages/_groups.json b/frontend/static/languages/_groups.json index 286e24e87..90a7f9148 100644 --- a/frontend/static/languages/_groups.json +++ b/frontend/static/languages/_groups.json @@ -292,6 +292,10 @@ "name": "armenian", "languages": ["armenian_western", "armenian_western_1k"] }, + { + "name": "myanmar", + "languages": ["myanmar_burmese"] + }, { "name": "japanese", "languages": ["japanese_hiragana", "japanese_katakana"] diff --git a/frontend/static/languages/_list.json b/frontend/static/languages/_list.json index 4b3b282e1..f19c46ea3 100644 --- a/frontend/static/languages/_list.json +++ b/frontend/static/languages/_list.json @@ -170,6 +170,7 @@ ,"shona_1k" ,"armenian_western" ,"armenian_western_1k" + ,"myanmar_burmese" ,"japanese_hiragana" ,"japanese_katakana" ,"sinhala" diff --git a/frontend/static/languages/myanmar_burmese.json b/frontend/static/languages/myanmar_burmese.json new file mode 100644 index 000000000..04f8535a3 --- /dev/null +++ b/frontend/static/languages/myanmar_burmese.json @@ -0,0 +1,208 @@ +{ + "name": "myanmar_burmese", + "leftToRight": true, + "noLazyMode": true, + "bcp47": "my-MY", + "words": [ + "အဆိုပါ", + "ဖြစ်သည်", + "ဝေဖန်", + "နှင့်", + "တစ်ခု", + "အပြင်သို့", + "အထဲသို့", + "သူ", + "တည်ရှိ", + "ထိုအရာ", + "ဝတ်ဆင်", + "အတွက်", + "သူတို့", + "ကျွန်တော်", + "နှင့်အတူ", + "အဖက်လုပ်", + "မဟုတ်ဘူး", + "အရွဲ့တိုက်", + "သတင်းစကား", + "ထာဝရ", + "ငလျင်လှုပ်", + "အသေးစိတ်", + "ကျွန်မ", + "ခင်ဗျား", + "ပုရွက်ဆိတ်", + "ဒါပေမယ့်", + "ပြည်သူ", + "သံသရာ", + "ချစ်ကျွမ်းဝင်", + "တကယ်တမ်း", + "ဖက်လဲတကင်း", + "အားလုံး", + "သေတမ်းစာ", + "ဟိုနေရာမှာ", + "ပြောဆို", + "မုန့်ဟင်းခါး", + "မုန်းတီး", + "သောအခါ", + "ဒယိမ်းဒယိုင်", + "ဒလန်", + "ဓားစာခံ", + "ညီအစ်မ", + "အမျိုးသား", + "ပယောဂ", + "အခြား", + "ထို့နောက်", + "ပစ်ခတ်", + "အချိန်", + "မှတစ်ပါး", + "သွားလာ", + "အကြောင်း", + "နှောင်ကြိုး", + "ဖက်ဆစ်", + "တော်လှန်ရေး", + "ပြည်နယ်", + "ငြိမ်းချမ်းရေး", + "အသစ်", + "ခုနှစ်", + "ဖောက်ပြန်", + "ဘိလပ်မြေ", + "ဆောင်ရွက်", + "ရေတံခွန်", + "လူရည်လည်", + "အမြင်", + "အသုံးပြု", + "ရယူခြင်း", + "ကြိုက်နှစ်သက်", + "ထိုအခါ", + "ပထမ", + "တစ်ခုခု", + "အလုပ်", + "လောလောဆယ်", + "မိခင်", + "ဖခင်", + "ပေးဝေ", + "ပြီးဆုံး", + "တွေးတော", + "အများဆုံး", + "စုစည်း", + "ရှာဖွေ", + "နေ့ရက်", + "နှစ်ကာလ", + "ပြီးနောက်", + "နည်းလမ်း", + "များစွာ", + "မဖြစ်မနေ", + "အသွင်အပြင်", + "ဟင်းလျာ", + "ကြီးမား", + "လက်လုပ်လက်စား", + "မှတဆင့်", + "ရှည်လျား", + "ဘယ်မှာ", + "အများကြီး", + "လုပ်သင့်လုပ်ထိုက်", + "ရေတွင်း", + "လူထု", + "ဆင်းလျက်", + "ကိုယ်ပိုင်", + "အချစ်", + "အဘယ်ကြောင့်", + "ကောင်းတယ်", + "ခါးသက်", + "ကြည်လင်", + "ခံစားမှု", + "လည်းကောင်း", + "ဘယ်လို", + "မြင့်မားသော", + "မြန်မာ", + "နေရာ", + "နည်းနည်း", + "ကမ္ဘာ", + "အလွန်", + "ငြိမ်သက်", + "နိုင်ငံ", + "လက်", + "အဟောင်း", + "ဘဝ", + "ဝန်ကြီးချုပ်", + "စာအရေးအသား", + "ဖြစ်လာသည်", + "ဒီမှာ", + "ပြဇာတ်", + "စာစီစာကုံး", + "လုံချည်", + "အကြား", + "လိုအပ်", + "စိတ်လိုက်မာန်ပါ", + "အလည်အပတ်", + "ဖွံ့ဖြိုးတိုးတက်", + "အောက်မေ့", + "နောက်ဆုံး", + "မှန်ကန်", + "လှုပ်ရှား", + "အရာရှိ", + "အထွေထွေ", + "ကျောင်း", + "ဘယ်တော့မှ", + "တူရိယာ", + "အကြွင်းမဲ့", + "အစပြု", + "အချုပ်အခြာ", + "နံပါတ်", + "အစိတ်အပိုင်း", + "အလှည့်", + "အစစ်အမှန်", + "ထွက်သွား", + "တန်ခိုး", + "အလိုရှိ", + "အမှတ်", + "ပုံစံ", + "အချင်းများ", + "ကလေး", + "အနည်းငယ်", + "သေးငယ်", + "ကတည်းက", + "ဆန့်ကျင်ဘက်", + "မေးမြန်း", + "နောက်ကျ", + "အိမ်", + "အကျိုးစီးပွား", + "ကြီးကျယ်", + "လူ", + "ပိတ်သည်", + "ဖွင့်သည်", + "အများသူငှာ", + "လိုက်နာ", + "ကာလအတွင်း", + "လက်ရှိ", + "ကောင်းမွန်", + "ထပ်မံ၍", + "ကိုင်ဆုပ်", + "အုပ်ချုပ်", + "ပတ်ပတ်လည်", + "ဖြစ်နိုင်သော", + "ဦးခေါင်း", + "စဉ်းစားဆင်ခြင်", + "စကားလုံး", + "ဆွဲဆောင်", + "ပြဿနာ", + "သို့သော်", + "ခေါင်းဆောင်", + "စနစ်", + "သတ်မှတ်", + "အမိန့်", + "မျက်လုံး", + "အစီအစဉ်", + "ပြေးသည်", + "စောင့်ရှောက်", + "မျက်နှာ", + "အချက်အလက်", + "အစုအဖွဲ့", + "ကစား", + "ရပ်နေ", + "တိုးမြှင့်", + "စောစော", + "သင်တန်း", + "ပြောင်းလဲမှု", + "အကူအညီ", + "မျဉ်းကြောင်း" + ] +} From 9897d4d8bbb17b0c79c5918e5dceedb8ed71b8b2 Mon Sep 17 00:00:00 2001 From: Nishu Murmu Date: Tue, 30 Aug 2022 18:31:24 +0530 Subject: [PATCH 06/14] fix(commandline): adding toggle functionality for ctrl+shift+p command (#3475) nishu-murmu * adding toggle functionality for ctrl+shift+p * prettier, ctrl shift p always works regardless of the quick restart setting Co-authored-by: Miodec --- frontend/src/ts/elements/commandline.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/ts/elements/commandline.ts b/frontend/src/ts/elements/commandline.ts index 5a1cf4921..a72f0bc3a 100644 --- a/frontend/src/ts/elements/commandline.ts +++ b/frontend/src/ts/elements/commandline.ts @@ -392,6 +392,9 @@ $(document).ready(() => { // opens command line if escape or ctrl/cmd + shift + p if ( ((event.key === "Escape" && Config.quickRestart !== "esc") || + (event.key.toLowerCase() === "p" && + (event.metaKey || event.ctrlKey) && + event.shiftKey) || (event.key === "Tab" && Config.quickRestart === "esc")) && !$("#commandLineWrapper").hasClass("hidden") ) { From 5e8f1b01a0e31a59a414aecb039ea6ad362ddf3d Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 30 Aug 2022 15:04:07 +0200 Subject: [PATCH 07/14] allowing user select closes #3452 --- frontend/src/styles/profile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/styles/profile.scss b/frontend/src/styles/profile.scss index 43ff0faf4..b89d13fd9 100644 --- a/frontend/src/styles/profile.scss +++ b/frontend/src/styles/profile.scss @@ -38,7 +38,7 @@ .pbsWords, .pbsTime, .details { - user-select: none; + // user-select: none; background: var(--sub-alt-color); padding: 1rem; border-radius: var(--roundness); From 3a055aaae7659f9cb3e1447bea0ec9272550f266 Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 30 Aug 2022 15:07:16 +0200 Subject: [PATCH 08/14] using anochor tags instead closes #3451 --- frontend/src/ts/controllers/route-controller.ts | 8 ++------ frontend/src/ts/elements/leaderboards.ts | 4 +++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/src/ts/controllers/route-controller.ts b/frontend/src/ts/controllers/route-controller.ts index c28edd2b9..16fba205c 100644 --- a/frontend/src/ts/controllers/route-controller.ts +++ b/frontend/src/ts/controllers/route-controller.ts @@ -167,10 +167,6 @@ $("#top .logo").on("click", () => { navigate("/"); }); -$(document).on("click", "#leaderboards .entryName", (e) => { - const uid = $(e.target).attr("uid"); - if (uid) { - navigate(`/profile/${uid}`); - Leaderboards.hide(); - } +$(document).on("click", "#leaderboards a.entryName", () => { + Leaderboards.hide(); }); diff --git a/frontend/src/ts/elements/leaderboards.ts b/frontend/src/ts/elements/leaderboards.ts index 43e5c440d..b504d0441 100644 --- a/frontend/src/ts/elements/leaderboards.ts +++ b/frontend/src/ts/elements/leaderboards.ts @@ -322,7 +322,9 @@ async function fillTable(lb: LbKey, prepend?: number): Promise { }
${avatar} - ${entry.name} + ${entry.name} ${entry.badgeId ? getBadgeHTMLbyId(entry.badgeId) : ""}
From f5435ce789c51b37f672a246f2001cb40ca738b9 Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 30 Aug 2022 15:10:03 +0200 Subject: [PATCH 09/14] changed name of custom attribute --- frontend/src/styles/core.scss | 2 +- frontend/src/ts/controllers/route-controller.ts | 2 +- frontend/src/ts/elements/leaderboards.ts | 2 +- frontend/static/html/pages/404.html | 2 +- frontend/static/html/pages/test.html | 2 +- frontend/static/html/top.html | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/src/styles/core.scss b/frontend/src/styles/core.scss index 55075a107..555a9739b 100644 --- a/frontend/src/styles/core.scss +++ b/frontend/src/styles/core.scss @@ -84,7 +84,7 @@ a { } } -a[data-link] * { +a[router-link] * { pointer-events: none; } diff --git a/frontend/src/ts/controllers/route-controller.ts b/frontend/src/ts/controllers/route-controller.ts index 16fba205c..1b3c9f8da 100644 --- a/frontend/src/ts/controllers/route-controller.ts +++ b/frontend/src/ts/controllers/route-controller.ts @@ -156,7 +156,7 @@ window.addEventListener("popstate", () => { document.addEventListener("DOMContentLoaded", () => { document.body.addEventListener("click", (e) => { const target = e?.target as HTMLLinkElement; - if (target.matches("[data-link]") && target?.href) { + if (target.matches("[router-link]") && target?.href) { e.preventDefault(); navigate(target.href); } diff --git a/frontend/src/ts/elements/leaderboards.ts b/frontend/src/ts/elements/leaderboards.ts index b504d0441..be82a638f 100644 --- a/frontend/src/ts/elements/leaderboards.ts +++ b/frontend/src/ts/elements/leaderboards.ts @@ -324,7 +324,7 @@ async function fillTable(lb: LbKey, prepend?: number): Promise {
${avatar} ${entry.name} + } router-link>${entry.name} ${entry.badgeId ? getBadgeHTMLbyId(entry.badgeId) : ""}
diff --git a/frontend/static/html/pages/404.html b/frontend/static/html/pages/404.html index f010c8851..3b1f3a38f 100644 --- a/frontend/static/html/pages/404.html +++ b/frontend/static/html/pages/404.html @@ -2,7 +2,7 @@
Ooops! Looks like you found a page that doesn't exist.
- + Go Home diff --git a/frontend/static/html/pages/test.html b/frontend/static/html/pages/test.html index 03a332515..c140aeabd 100644 --- a/frontend/static/html/pages/test.html +++ b/frontend/static/html/pages/test.html @@ -179,7 +179,7 @@
- Sign in + Sign in to save your result
diff --git a/frontend/static/html/top.html b/frontend/static/html/top.html index 84c963d03..5edc30218 100644 --- a/frontend/static/html/top.html +++ b/frontend/static/html/top.html @@ -50,7 +50,7 @@ tabindex="2" href="/" onclick="this.blur();" - data-link + router-link >
@@ -70,7 +70,7 @@ tabindex="2" href="/about" onclick="this.blur();" - data-link + router-link >
@@ -93,7 +93,7 @@ tabindex="2" href="/settings" onclick="this.blur();" - data-link + router-link >
@@ -104,7 +104,7 @@ tabindex="2" href="/account" onclick="this.blur();" - data-link + router-link >
@@ -125,7 +125,7 @@ tabindex="2" href="/login" onclick="this.blur();" - data-link + router-link >
From 59fa77fe18c98bf73dac06071d875668242ec377 Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 30 Aug 2022 15:12:06 +0200 Subject: [PATCH 10/14] using optional chaining --- frontend/src/ts/elements/commandline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/elements/commandline.ts b/frontend/src/ts/elements/commandline.ts index a72f0bc3a..84c6a0319 100644 --- a/frontend/src/ts/elements/commandline.ts +++ b/frontend/src/ts/elements/commandline.ts @@ -392,7 +392,7 @@ $(document).ready(() => { // opens command line if escape or ctrl/cmd + shift + p if ( ((event.key === "Escape" && Config.quickRestart !== "esc") || - (event.key.toLowerCase() === "p" && + (event.key?.toLowerCase() === "p" && (event.metaKey || event.ctrlKey) && event.shiftKey) || (event.key === "Tab" && Config.quickRestart === "esc")) && From 4b49900d576503e2201fc59c3900347b17eff09c Mon Sep 17 00:00:00 2001 From: Bruce Berrios <58147810+Bruception@users.noreply.github.com> Date: Tue, 30 Aug 2022 09:19:26 -0400 Subject: [PATCH 11/14] Add inbox (#3458) Bruception * Add basic inbox * Changes * Remove export * Move condition * Use for each --- backend/__tests__/dal/user.spec.ts | 28 +++++ .../utils/daily-leaderboards.spec.ts | 1 + backend/__tests__/utils/monkey-mail.spec.ts | 22 ++++ backend/src/api/controllers/user.ts | 41 ++++++- backend/src/api/routes/users.ts | 31 +++++ backend/src/constants/base-configuration.ts | 25 ++++ backend/src/dal/user.ts | 110 +++++++++++++++++- .../src/jobs/announce-daily-leaderboards.ts | 42 ++++++- backend/src/middlewares/rate-limit.ts | 14 +++ backend/src/types/types.d.ts | 32 +++++ backend/src/utils/monkey-mail.ts | 20 ++++ 11 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 backend/__tests__/utils/monkey-mail.spec.ts create mode 100644 backend/src/utils/monkey-mail.ts diff --git a/backend/__tests__/dal/user.spec.ts b/backend/__tests__/dal/user.spec.ts index 15aa2bd9b..dda26520e 100644 --- a/backend/__tests__/dal/user.spec.ts +++ b/backend/__tests__/dal/user.spec.ts @@ -489,4 +489,32 @@ describe("UserDal", () => { expect(resetUser.bananas).toStrictEqual(0); expect(resetUser.xp).toStrictEqual(0); }); + + it("getInbox should return the user's inbox", async () => { + await UserDAL.addUser("test name", "test email", "TestID"); + + const emptyInbox = await UserDAL.getInbox("TestID"); + + expect(emptyInbox).toStrictEqual([]); + + await UserDAL.addToInbox( + "TestID", + [ + { + getTemplate: (user) => ({ + subject: `Hello ${user.name}!`, + }), + } as any, + ], + 0 + ); + + const inbox = await UserDAL.getInbox("TestID"); + + expect(inbox).toStrictEqual([ + { + subject: "Hello test name!", + }, + ]); + }); }); diff --git a/backend/__tests__/utils/daily-leaderboards.spec.ts b/backend/__tests__/utils/daily-leaderboards.spec.ts index 294801e52..737a4b531 100644 --- a/backend/__tests__/utils/daily-leaderboards.spec.ts +++ b/backend/__tests__/utils/daily-leaderboards.spec.ts @@ -21,6 +21,7 @@ const dailyLeaderboardsConfig = { ], dailyLeaderboardCacheSize: 3, topResultsToAnnounce: 3, + xpReward: 0, }; describe("Daily Leaderboards", () => { diff --git a/backend/__tests__/utils/monkey-mail.spec.ts b/backend/__tests__/utils/monkey-mail.spec.ts new file mode 100644 index 000000000..4746c731c --- /dev/null +++ b/backend/__tests__/utils/monkey-mail.spec.ts @@ -0,0 +1,22 @@ +import { buildMonkeyMail } from "../../src/utils/monkey-mail"; + +describe("Monkey Mail", () => { + it("should properly create a mail object", () => { + const mailConfig = { + subject: "", + body: "", + timestamp: Date.now(), + getTemplate: (): any => ({}), + }; + + const mail = buildMonkeyMail(mailConfig) as any; + + expect(mail.id).toBeDefined(); + expect(mail.subject).toBe(""); + expect(mail.body).toBe(""); + expect(mail.timestamp).toBeDefined(); + expect(mail.read).toBe(false); + expect(mail.rewards).toEqual([]); + expect(mail.getTemplate).toBeDefined(); + }); +}); diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts index 441037f9c..49adc9ad8 100644 --- a/backend/src/api/controllers/user.ts +++ b/backend/src/api/controllers/user.ts @@ -117,6 +117,19 @@ export async function updateEmail( return new MonkeyResponse("Email updated"); } +function getRelevantUserInfo( + user: MonkeyTypes.User +): Partial { + return _.omit(user, [ + "bananas", + "lbPersonalBests", + "quoteMod", + "inbox", + "nameHistory", + "lastNameChange", + ]); +} + export async function getUser( req: MonkeyTypes.Request ): Promise { @@ -142,7 +155,12 @@ export async function getUser( const agentLog = buildAgentLog(req); Logger.logToDb("user_data_requested", agentLog, uid); - return new MonkeyResponse("User data retrieved", userInfo); + const userData = { + ...getRelevantUserInfo(userInfo), + inboxUnreadSize: _.filter(userInfo.inbox, { read: false }).length, + }; + + return new MonkeyResponse("User data retrieved", userData); } export async function linkDiscord( @@ -487,3 +505,24 @@ export async function updateProfile( return new MonkeyResponse("Profile updated"); } + +export async function getInbox( + req: MonkeyTypes.Request +): Promise { + const { uid } = req.ctx.decodedToken; + + const inbox = await UserDAL.getInbox(uid); + + return new MonkeyResponse("Inbox retrieved", inbox); +} + +export async function updateInbox( + req: MonkeyTypes.Request +): Promise { + const { uid } = req.ctx.decodedToken; + const { mailIdsToMarkRead, mailIdsToDelete } = req.body; + + await UserDAL.updateInbox(uid, mailIdsToMarkRead, mailIdsToDelete); + + return new MonkeyResponse("Inbox updated"); +} diff --git a/backend/src/api/routes/users.ts b/backend/src/api/routes/users.ts index 5152b3197..a36f849ad 100644 --- a/backend/src/api/routes/users.ts +++ b/backend/src/api/routes/users.ts @@ -470,4 +470,35 @@ router.patch( asyncHandler(UserController.updateProfile) ); +const mailIdSchema = joi.array().items(joi.string().guid()).min(1).default([]); + +const requireInboxEnabled = validateConfiguration({ + criteria: (configuration) => { + return configuration.users.inbox.enabled; + }, + invalidMessage: "Your inbox is not available at this time.", +}); + +router.get( + "/inbox", + requireInboxEnabled, + authenticateRequest(), + RateLimit.userMailGet, + asyncHandler(UserController.getInbox) +); + +router.patch( + "/inbox", + requireInboxEnabled, + authenticateRequest(), + RateLimit.userMailUpdate, + validateRequest({ + body: { + mailIdsToDelete: mailIdSchema, + mailIdsToMarkRead: mailIdSchema, + }, + }), + asyncHandler(UserController.updateInbox) +); + export default router; diff --git a/backend/src/constants/base-configuration.ts b/backend/src/constants/base-configuration.ts index 4b4d82fdd..dbf060892 100644 --- a/backend/src/constants/base-configuration.ts +++ b/backend/src/constants/base-configuration.ts @@ -47,6 +47,10 @@ export const BASE_CONFIGURATION: MonkeyTypes.Configuration = { maxDailyBonus: 0, minDailyBonus: 0, }, + inbox: { + enabled: false, + maxMail: 0, + }, }, rateLimiting: { badAuthentication: { @@ -63,6 +67,7 @@ export const BASE_CONFIGURATION: MonkeyTypes.Configuration = { // GOTCHA! MUST ATLEAST BE 1, LRUCache module will make process crash and die dailyLeaderboardCacheSize: 1, topResultsToAnnounce: 1, // This should never be 0. Setting to zero will announce all results. + xpReward: 0, }, }; @@ -255,6 +260,21 @@ export const CONFIGURATION_FORM_SCHEMA: ObjectSchema = { }, }, }, + inbox: { + type: "object", + label: "Inbox", + fields: { + enabled: { + type: "boolean", + label: "Enabled", + }, + maxMail: { + type: "number", + label: "Max Messages", + min: 0, + }, + }, + }, profiles: { type: "object", label: "User Profiles", @@ -347,6 +367,11 @@ export const CONFIGURATION_FORM_SCHEMA: ObjectSchema = { label: "Top Results To Announce", min: 1, }, + xpReward: { + type: "number", + label: "XP Reward", + min: 0, + }, }, }, }, diff --git a/backend/src/dal/user.ts b/backend/src/dal/user.ts index 7f0e01d9f..837034322 100644 --- a/backend/src/dal/user.ts +++ b/backend/src/dal/user.ts @@ -4,9 +4,10 @@ import { updateUserEmail } from "../utils/auth"; import { checkAndUpdatePb } from "../utils/pb"; import * as db from "../init/db"; import MonkeyError from "../utils/error"; -import { Collection, ObjectId, WithId, Long } from "mongodb"; +import { Collection, ObjectId, WithId, Long, UpdateFilter } from "mongodb"; import Logger from "../utils/logger"; import { flattenObjectDeep } from "../utils/misc"; +import { MonkeyMailWithTemplate } from "../utils/monkey-mail"; const SECONDS_PER_HOUR = 3600; @@ -732,3 +733,110 @@ export async function updateProfile( } ); } + +export async function getInbox( + uid: string +): Promise { + const user = await getUser(uid, "get inventory"); + return user.inbox ?? []; +} + +export async function addToInbox( + uid: string, + mail: MonkeyMailWithTemplate[], + maxInboxSize: number +): Promise { + const user = await getUser(uid, "add to inbox"); + + const inbox = user.inbox ?? []; + + for (let i = 0; i < inbox.length + mail.length - maxInboxSize; i++) { + inbox.pop(); + } + + const evaluatedMail: MonkeyTypes.MonkeyMail[] = mail.map((mail) => { + return _.omit( + { + ...mail, + ...(mail.getTemplate && mail.getTemplate(user)), + }, + "getTemplate" + ); + }); + + inbox.unshift(...evaluatedMail); + const newInbox = inbox.sort((a, b) => b.timestamp - a.timestamp); + + await getUsersCollection().updateOne( + { uid }, + { + $set: { + inbox: newInbox, + }, + } + ); +} + +function buildRewardUpdates( + rewards: MonkeyTypes.AllRewards[] +): UpdateFilter> { + let totalXp = 0; + const newBadges: MonkeyTypes.Badge[] = []; + + rewards.forEach((reward) => { + if (reward.type === "xp") { + totalXp += reward.item; + } else if (reward.type === "badge") { + newBadges.push(reward.item); + } + }); + + return { + $inc: { + xp: totalXp, + }, + $push: { + "inventory.badges": { $each: newBadges }, + }, + }; +} + +export async function updateInbox( + uid: string, + mailToRead: string[], + mailToDelete: string[] +): Promise { + const user = await getUser(uid, "update inbox"); + + const inbox = user.inbox ?? []; + + const mailToReadSet = new Set(mailToRead); + const mailToDeleteSet = new Set(mailToDelete); + + const allRewards: MonkeyTypes.AllRewards[] = []; + + const newInbox = inbox + .filter((mail) => { + const { id, rewards } = mail; + + if (mailToReadSet.has(id) && !mail.read) { + mail.read = true; + if (rewards.length > 0) { + allRewards.push(...rewards); + } + } + + return !mailToDeleteSet.has(id); + }) + .sort((a, b) => b.timestamp - a.timestamp); + + const baseUpdate = { + $set: { + inbox: newInbox, + }, + }; + const rewardUpdates = buildRewardUpdates(allRewards); + const mergedUpdates = _.merge(baseUpdate, rewardUpdates); + + await getUsersCollection().updateOne({ uid }, mergedUpdates); +} diff --git a/backend/src/jobs/announce-daily-leaderboards.ts b/backend/src/jobs/announce-daily-leaderboards.ts index 784332915..81728bc4a 100644 --- a/backend/src/jobs/announce-daily-leaderboards.ts +++ b/backend/src/jobs/announce-daily-leaderboards.ts @@ -3,6 +3,8 @@ import { getCurrentDayTimestamp } from "../utils/misc"; import { getCachedConfiguration } from "../init/configuration"; import { DailyLeaderboard } from "../utils/daily-leaderboards"; import { announceDailyLeaderboardTopResults } from "../tasks/george"; +import { addToInbox } from "../dal/user"; +import { buildMonkeyMail } from "../utils/monkey-mail"; const CRON_SCHEDULE = "1 0 * * *"; // At 00:01. const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000; @@ -24,7 +26,8 @@ async function announceDailyLeaderboard( language: string, mode: string, mode2: string, - dailyLeaderboardsConfig: MonkeyTypes.Configuration["dailyLeaderboards"] + dailyLeaderboardsConfig: MonkeyTypes.Configuration["dailyLeaderboards"], + inboxConfig: MonkeyTypes.Configuration["users"]["inbox"] ): Promise { const yesterday = getCurrentDayTimestamp() - ONE_DAY_IN_MILLISECONDS; const dailyLeaderboard = new DailyLeaderboard( @@ -43,6 +46,29 @@ async function announceDailyLeaderboard( return; } + if (inboxConfig.enabled) { + const { xpReward } = dailyLeaderboardsConfig; + + const inboxPromises = topResults.map(async (entry) => { + const mail = buildMonkeyMail({ + rewards: [ + { + type: "xp", + item: xpReward, + }, + ], + getTemplate: (user) => ({ + subject: `${xpReward} XP for top placement in the daily leaderboard!`, + body: `Congratulations ${user.name} on placing top ${entry.rank} in the ${language} ${mode} ${mode2} daily leaderboard! Claim your ${xpReward} xp!`, + }), + }); + + return await addToInbox(entry.uid, [mail], inboxConfig.maxMail); + }); + + await Promise.allSettled(inboxPromises); + } + const leaderboardId = `${mode} ${mode2} ${language}`; await announceDailyLeaderboardTopResults( leaderboardId, @@ -52,14 +78,24 @@ async function announceDailyLeaderboard( } async function announceDailyLeaderboards(): Promise { - const { dailyLeaderboards, maintenance } = await getCachedConfiguration(); + const { + dailyLeaderboards, + users: { inbox }, + maintenance, + } = await getCachedConfiguration(); if (!dailyLeaderboards.enabled || maintenance) { return; } await Promise.allSettled( leaderboardsToAnnounce.map(({ language, mode, mode2 }) => { - return announceDailyLeaderboard(language, mode, mode2, dailyLeaderboards); + return announceDailyLeaderboard( + language, + mode, + mode2, + dailyLeaderboards, + inbox + ); }) ); } diff --git a/backend/src/middlewares/rate-limit.ts b/backend/src/middlewares/rate-limit.ts index 43c8dedf9..93bf3ca24 100644 --- a/backend/src/middlewares/rate-limit.ts +++ b/backend/src/middlewares/rate-limit.ts @@ -439,6 +439,20 @@ export const userProfileUpdate = rateLimit({ handler: customHandler, }); +export const userMailGet = rateLimit({ + windowMs: ONE_HOUR_MS, + max: 60 * REQUEST_MULTIPLIER, + keyGenerator: getKeyWithUid, + handler: customHandler, +}); + +export const userMailUpdate = rateLimit({ + windowMs: ONE_HOUR_MS, + max: 60 * REQUEST_MULTIPLIER, + keyGenerator: getKeyWithUid, + handler: customHandler, +}); + // ApeKeys Routing export const apeKeysGet = rateLimit({ windowMs: ONE_HOUR_MS, diff --git a/backend/src/types/types.d.ts b/backend/src/types/types.d.ts index 0646ecf7c..7f86dfcb2 100644 --- a/backend/src/types/types.d.ts +++ b/backend/src/types/types.d.ts @@ -46,6 +46,10 @@ declare namespace MonkeyTypes { maxDailyBonus: number; minDailyBonus: number; }; + inbox: { + enabled: boolean; + maxMail: number; + }; }; apeKeys: { endpointsEnabled: boolean; @@ -68,6 +72,7 @@ declare namespace MonkeyTypes { validModeRules: ValidModeRule[]; dailyLeaderboardCacheSize: number; topResultsToAnnounce: number; + xpReward: number; }; } @@ -98,6 +103,32 @@ declare namespace MonkeyTypes { }; } + interface Reward { + type: string; + item: T; + } + + interface XpReward extends Reward { + type: "xp"; + item: number; + } + + interface BadgeReward extends Reward { + type: "badge"; + item: Badge; + } + + type AllRewards = XpReward | BadgeReward; + + interface MonkeyMail { + id: string; + subject: string; + body: string; + timestamp: number; + read: boolean; + rewards: AllRewards[]; + } + interface User { autoBanTimestamps?: number[]; addedAt: number; @@ -128,6 +159,7 @@ declare namespace MonkeyTypes { profileDetails?: UserProfileDetails; inventory?: UserInventory; xp?: number; + inbox?: MonkeyMail[]; } interface UserInventory { diff --git a/backend/src/utils/monkey-mail.ts b/backend/src/utils/monkey-mail.ts new file mode 100644 index 000000000..4e669536c --- /dev/null +++ b/backend/src/utils/monkey-mail.ts @@ -0,0 +1,20 @@ +import { v4 } from "uuid"; + +type MonkeyMailOptions = Partial>; +export interface MonkeyMailWithTemplate extends MonkeyTypes.MonkeyMail { + getTemplate?: (user: MonkeyTypes.User) => MonkeyMailOptions; +} + +export function buildMonkeyMail( + options: MonkeyMailOptions +): MonkeyMailWithTemplate { + return { + id: v4(), + subject: options.subject || "", + body: options.body || "", + timestamp: options.timestamp || Date.now(), + read: false, + rewards: options.rewards || [], + getTemplate: options.getTemplate, + }; +} From 45948de9be3d9ff62361bdb924d2bf23913565d8 Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 30 Aug 2022 15:22:08 +0200 Subject: [PATCH 12/14] reordered lines --- frontend/src/ts/test/test-logic.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 366aa2256..f97affdbd 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -1274,8 +1274,10 @@ export async function retrySavingResult(): Promise { Result.hideCrown(); if (response.status !== 200) { + console.log("Error saving result", completedEvent); retrySaving.canRetry = true; $("#retrySavingResultButton").removeClass("hidden"); + retrySaving.completedEvent = completedEvent; return Notifications.add("Result not saved. " + response.message, -1); } @@ -1323,6 +1325,17 @@ export async function retrySavingResult(): Promise { ); } + if (response.data.dailyLeaderboardRank) { + Notifications.add( + `New ${completedEvent.language} ${completedEvent.mode} ${completedEvent.mode2} rank: ` + + Misc.getPositionString(response.data.dailyLeaderboardRank), + 1, + 10, + "Daily Leaderboard", + "list-ol" + ); + } + $("#retrySavingResultButton").addClass("hidden"); Notifications.add("Result saved", 1); } @@ -1678,15 +1691,13 @@ export async function finish(difficultyFailed = false): Promise { const response = await Ape.results.save(completedEvent); AccountButton.loading(false); + Result.hideCrown(); if (response.status !== 200) { console.log("Error saving result", completedEvent); - $("#retrySavingResultButton").removeClass("hidden"); - if (response.message === "Incorrect result hash") { - console.log(completedEvent); - } - retrySaving.completedEvent = completedEvent; retrySaving.canRetry = true; + $("#retrySavingResultButton").removeClass("hidden"); + retrySaving.completedEvent = completedEvent; return Notifications.add("Failed to save result: " + response.message, -1); } @@ -1701,8 +1712,6 @@ export async function finish(difficultyFailed = false): Promise { DB.addXp(response.data.xp); } - Result.hideCrown(); - completedEvent._id = response.data.insertedId; if (response.data.isPb) { completedEvent.isPb = true; From 5fa48b54d3dd2b45b81deac75decc88c53346651 Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 30 Aug 2022 15:22:41 +0200 Subject: [PATCH 13/14] updated error message --- frontend/src/ts/test/test-logic.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index f97affdbd..4ef0b2d22 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -1278,7 +1278,7 @@ export async function retrySavingResult(): Promise { retrySaving.canRetry = true; $("#retrySavingResultButton").removeClass("hidden"); retrySaving.completedEvent = completedEvent; - return Notifications.add("Result not saved. " + response.message, -1); + return Notifications.add("Failed to save result: " + response.message, -1); } if (response.data.xp) { From cfa08151f66e82f7310c0301d6262d293fe36c5b Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 30 Aug 2022 15:25:47 +0200 Subject: [PATCH 14/14] removed duplicating code closes #3464 --- frontend/src/ts/test/test-logic.ts | 85 +++++------------------------- 1 file changed, 14 insertions(+), 71 deletions(-) diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 4ef0b2d22..0f6502679 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -1268,76 +1268,7 @@ export async function retrySavingResult(): Promise { Notifications.add("Retrying to save..."); - const response = await Ape.results.save(completedEvent); - - AccountButton.loading(false); - Result.hideCrown(); - - if (response.status !== 200) { - console.log("Error saving result", completedEvent); - retrySaving.canRetry = true; - $("#retrySavingResultButton").removeClass("hidden"); - retrySaving.completedEvent = completedEvent; - return Notifications.add("Failed to save result: " + response.message, -1); - } - - if (response.data.xp) { - const snapxp = DB.getSnapshot().xp; - AccountButton.updateXpBar( - snapxp, - response.data.xp, - response.data.dailyXpBonus, - response.data.xpBreakdown - ); - DB.addXp(response.data.xp); - } - - completedEvent._id = response.data.insertedId; - if (response.data.isPb) { - completedEvent.isPb = true; - } - - DB.saveLocalResult(completedEvent); - DB.updateLocalStats( - TestStats.restartCount + 1, - completedEvent.testDuration + - completedEvent.incompleteTestSeconds - - completedEvent.afkDuration - ); - - AnalyticsController.log("testCompleted"); - - if (response.data.isPb) { - //new pb - Result.showCrown(); - Result.updateCrown(); - DB.saveLocalPB( - Config.mode, - completedEvent.mode2, - Config.punctuation, - Config.language, - Config.difficulty, - Config.lazyMode, - completedEvent.wpm, - completedEvent.acc, - completedEvent.rawWpm, - completedEvent.consistency - ); - } - - if (response.data.dailyLeaderboardRank) { - Notifications.add( - `New ${completedEvent.language} ${completedEvent.mode} ${completedEvent.mode2} rank: ` + - Misc.getPositionString(response.data.dailyLeaderboardRank), - 1, - 10, - "Daily Leaderboard", - "list-ol" - ); - } - - $("#retrySavingResultButton").addClass("hidden"); - Notifications.add("Result saved", 1); + saveResult(completedEvent, true); } function buildCompletedEvent(difficultyFailed: boolean): CompletedEvent { @@ -1688,6 +1619,13 @@ export async function finish(difficultyFailed = false): Promise { completedEvent.hash = objectHash(completedEvent); + saveResult(completedEvent, false); +} + +async function saveResult( + completedEvent: CompletedEvent, + isRetrying: boolean +): Promise { const response = await Ape.results.save(completedEvent); AccountButton.loading(false); @@ -1697,7 +1635,9 @@ export async function finish(difficultyFailed = false): Promise { console.log("Error saving result", completedEvent); retrySaving.canRetry = true; $("#retrySavingResultButton").removeClass("hidden"); - retrySaving.completedEvent = completedEvent; + if (!isRetrying) { + retrySaving.completedEvent = completedEvent; + } return Notifications.add("Failed to save result: " + response.message, -1); } @@ -1757,6 +1697,9 @@ export async function finish(difficultyFailed = false): Promise { } $("#retrySavingResultButton").addClass("hidden"); + if (isRetrying) { + Notifications.add("Result saved", 1); + } } export function fail(reason: string): void {