Merge branch 'master' into tribe

This commit is contained in:
Corey Bergeron 2020-10-15 23:40:25 -04:00
commit adcb2b4d09
22 changed files with 1039 additions and 620 deletions

View file

@ -9,14 +9,14 @@
1. [Create a new Firebase project.](https://console.firebase.google.com/u/0/)
- The project name doesn't really matter, but just name it `monkey-type`.
- The project name doesn't really matter, but just name it `monkeytype`.
- Google Analytics is not necessary.
2. [Install the Firebase CLI](https://firebase.google.com/docs/cli)
3. Run `firebase login` on your terminal to log in to the same google account as you just used to create the project.
4. Git clone this project.
5. Run `npm install` in the `functions/` directory
6. Compile `public/css/style.scss` to `public/css/style.min.css` as [described below](https://github.com/Miodec/monkey-type/blob/master/CONTRIBUTING.md#standards--conventions)
6. Compile `public/css/style.scss` to `public/css/style.min.css` as [described below](https://github.com/Miodec/monkeytype/blob/master/CONTRIBUTING.md#standards--conventions)
7. Rename `.firebaserc_example` to `.firebaserc` and change the project name of default to the firebase project id you just created.
- If `.firebaserc_example` does not exist after cloning, create your own with:

View file

@ -1,6 +1,6 @@
# about
Monkey-type is a minimalistic, customisable typing test, featuring many test modes, an account system to save your typing speed history and user configurable features like themes, a smooth caret and more.
Monkeytype is a minimalistic, customisable typing test, featuring many test modes, an account system to save your typing speed history and user configurable features like themes, a smooth caret and more.
# features
@ -36,4 +36,4 @@ If you wish to support further development and feeling extra awesome, you can do
# how to contribute
Refer to [CONTRIBUTING.md](https://github.com/Miodec/monkey-type/blob/master/CONTRIBUTING.md)
Refer to [CONTRIBUTING.md](https://github.com/Miodec/monkeytype/blob/master/CONTRIBUTING.md)

View file

@ -2,13 +2,6 @@
"hosting": {
"public": "public",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"redirects": [
{
"source": "/soon",
"destination": "/",
"type": 301
}
],
"rewrites": [
{
"source": "**",

View file

@ -13,6 +13,7 @@ admin.initializeApp({
});
const db = admin.firestore();
const fetch = require("node-fetch");
async function getAllNames() {
// return admin
@ -261,6 +262,8 @@ function checkIfPB(uid, obj, userdata) {
wpm: obj.wpm,
acc: obj.acc,
raw: obj.rawWpm,
timestamp: Date.now(),
consistency: obj.consistency
},
],
},
@ -284,6 +287,8 @@ function checkIfPB(uid, obj, userdata) {
wpm: obj.wpm,
acc: obj.acc,
raw: obj.rawWpm,
timestamp: Date.now(),
consistency: obj.consistency
},
],
},
@ -315,6 +320,8 @@ function checkIfPB(uid, obj, userdata) {
pb.wpm = obj.wpm;
pb.acc = obj.acc;
pb.raw = obj.rawWpm;
pb.timestamp = Date.now();
pb.consistency = obj.consistency;
toUpdate = true;
} else {
//no pb
@ -331,6 +338,8 @@ function checkIfPB(uid, obj, userdata) {
wpm: obj.wpm,
acc: obj.acc,
raw: obj.rawWpm,
timestamp: Date.now(),
consistency: obj.consistency
});
toUpdate = true;
}
@ -345,6 +354,8 @@ function checkIfPB(uid, obj, userdata) {
wpm: obj.wpm,
acc: obj.acc,
raw: obj.rawWpm,
timestamp: Date.now(),
consistency: obj.consistency
},
];
toUpdate = true;
@ -474,6 +485,55 @@ exports.getPatreons = functions.https.onRequest(async (request, response) => {
}
});
exports.verifyUser = functions.https.onRequest(async (request, response) => {
response.set("Access-Control-Allow-Origin", "*");
response.set("Access-Control-Allow-Headers", "*");
response.set("Access-Control-Allow-Credentials", "true");
if (request.method === "OPTIONS") {
// Send response to OPTIONS requests
response.set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.set("Access-Control-Allow-Headers", "Authorization,Content-Type");
response.set("Access-Control-Max-Age", "3600");
response.status(204).send("");
return;
}
request = request.body.data;
if (request.uid == undefined) {
response.status(200).send({ data: { status: -1, message: "Need to provide uid" } });
return;
}
try {
return fetch("https://discord.com/api/users/@me", {
headers: {
authorization: `${request.tokenType} ${request.accessToken}`,
},
})
.then((res) => res.json())
.then(async (res2) => {
let did = res2.id;
await db.collection('users').doc(request.uid).update({
discordId: did
})
await db.collection("bot-commands").add({
command: "verify",
arguments: [did,request.uid],
executed: false,
requestTimestamp: Date.now(),
});
response.status(200).send({ data: { status: 1, message: "Verified", did: did } });
return;
})
.catch((e) => {
console.error('Something went wrong when trying to verify user ' + e.message);
response.status(200).send({ data: { status: -1, message: e.message } });
return;
});
} catch (e) {
response.status(200).send({ data: { status: -1, message: e } });
return;
}
});
async function incrementTestCounter(uid, userData) {
try {
if (userData.completedTests === undefined) {
@ -651,9 +711,13 @@ exports.testCompleted = functions
return;
}
request = request.body.data;
if (request === undefined) {
response.status(200).send({ data: { resultCode: -999 } });
return;
}
try {
if (request.uid === undefined || request.obj === undefined) {
console.error(`error saving result for ${request.uid} - missing input`);
console.error(`error saving result for - missing input`);
response.status(200).send({ data: { resultCode: -999 } });
return;
}
@ -771,7 +835,7 @@ exports.testCompleted = functions
keySpacing.sd <= 15 ||
keyDuration.sd <= 10 ||
keyDuration.average < 15 ||
(obj.wpm > 200 && obj.consistency < 60)
(obj.wpm > 200 && obj.consistency < 70)
) {
console.error(
`possible bot detected by user (${obj.wpm} ${obj.rawWpm} ${
@ -789,7 +853,7 @@ exports.testCompleted = functions
(keyDuration.average > 15 && keyDuration.average <= 20)
) {
console.error(
`very close to bot threshold by user (${obj.wpm} ${
`very close to bot detected threshold by user (${obj.wpm} ${
obj.rawWpm
} ${obj.acc}) ${
request.uid
@ -1434,143 +1498,138 @@ class Leaderboard {
}
}
exports.generatePairingCode = functions
.runWith({
timeoutSeconds: 100,
memory: "2GB",
})
.https.onRequest((request, response) => {
response.set("Access-Control-Allow-Origin", "*");
if (request.method === "OPTIONS") {
// Send response to OPTIONS requests
response.set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.set(
"Access-Control-Allow-Headers",
"Authorization,Content-Type"
);
response.set("Access-Control-Max-Age", "3600");
response.status(204).send("");
return;
}
request = request.body.data;
try {
if (request === null) {
console.error(
`error while trying to generate discord pairing code - no input`
);
response.status(200).send({ data: { status: -999 } });
return;
}
// exports.generatePairingCode = functions
// .runWith({
// timeoutSeconds: 100,
// memory: "2GB",
// })
// .https.onRequest((request, response) => {
// response.set("Access-Control-Allow-Origin", "*");
// if (request.method === "OPTIONS") {
// // Send response to OPTIONS requests
// response.set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
// response.set(
// "Access-Control-Allow-Headers",
// "Authorization,Content-Type"
// );
// response.set("Access-Control-Max-Age", "3600");
// response.status(204).send("");
// return;
// }
// request = request.body.data;
// try {
// if (request === null) {
// console.error(
// `error while trying to generate discord pairing code - no input`
// );
// response.status(200).send({ data: { status: -999 } });
// return;
// }
return db
.collection("users")
.doc(request.uid)
.get()
.then(async (userDoc) => {
userDocData = userDoc.data();
if (
userDocData.discordPairingCode !== undefined &&
userDocData.discordPairingCode !== null
) {
console.log(
`user ${request.uid} already has code ${userDocData.discordPairingCode}`
);
response.status(200).send({
data: {
status: -999,
pairingCode: userDocData.discordPairingCode,
},
});
} else {
let stepSize = 1000;
let existingCodes = [];
let query = await db
.collection(`users`)
.where("discordPairingCode", ">", "")
.limit(stepSize)
.get();
let lastDoc;
while (query.docs.length > 0) {
lastDoc = query.docs[query.docs.length - 1];
query.docs.forEach((doc) => {
let docData = doc.data();
if (
docData.discordPairingCode !== undefined &&
docData.discordPairingCode !== null
) {
existingCodes.push(docData.discordPairingCode);
}
});
query = await db
.collection(`users`)
.where("discordPairingCode", ">", "")
.limit(stepSize)
.startAfter(lastDoc)
.get();
}
// return db
// .collection("users")
// .doc(request.uid)
// .get()
// .then(async (userDoc) => {
// userDocData = userDoc.data();
// if (
// userDocData.discordPairingCode !== undefined &&
// userDocData.discordPairingCode !== null
// ) {
// console.log(
// `user ${request.uid} already has code ${userDocData.discordPairingCode}`
// );
// response.status(200).send({
// data: {
// status: -999,
// pairingCode: userDocData.discordPairingCode,
// },
// });
// } else {
// let stepSize = 1000;
// let existingCodes = [];
// let query = await db
// .collection(`users`)
// .where("discordPairingCode", ">", "")
// .limit(stepSize)
// .get();
// let lastDoc;
// while (query.docs.length > 0) {
// lastDoc = query.docs[query.docs.length - 1];
// query.docs.forEach((doc) => {
// let docData = doc.data();
// if (
// docData.discordPairingCode !== undefined &&
// docData.discordPairingCode !== null
// ) {
// existingCodes.push(docData.discordPairingCode);
// }
// });
// query = await db
// .collection(`users`)
// .where("discordPairingCode", ">", "")
// .limit(stepSize)
// .startAfter(lastDoc)
// .get();
// }
let randomCode = generate(9);
// let randomCode = generate(9);
while (existingCodes.includes(randomCode)) {
randomCode = generate(9);
}
// while (existingCodes.includes(randomCode)) {
// randomCode = generate(9);
// }
return db
.collection("users")
.doc(request.uid)
.update(
{
discordPairingCode: randomCode,
},
{ merge: true }
)
.then((res) => {
console.log(`generated ${randomCode} for user ${request.uid}`);
response.status(200).send({
data: {
status: 1,
pairingCode: randomCode,
},
});
return;
})
.catch((e) => {
console.error(
`error while trying to set discord pairing code ${randomCode} for user ${request.uid} - ${e}`
);
response.status(200).send({
data: {
status: -999,
},
});
return;
});
}
});
} catch (e) {
console.error(
`error while trying to generate discord pairing code for user ${request.uid} - ${e}`
);
response.status(200).send({
data: {
status: -999,
},
});
return;
}
});
// return db
// .collection("users")
// .doc(request.uid)
// .update(
// {
// discordPairingCode: randomCode,
// },
// { merge: true }
// )
// .then((res) => {
// console.log(`generated ${randomCode} for user ${request.uid}`);
// response.status(200).send({
// data: {
// status: 1,
// pairingCode: randomCode,
// },
// });
// return;
// })
// .catch((e) => {
// console.error(
// `error while trying to set discord pairing code ${randomCode} for user ${request.uid} - ${e}`
// );
// response.status(200).send({
// data: {
// status: -999,
// },
// });
// return;
// });
// }
// });
// } catch (e) {
// console.error(
// `error while trying to generate discord pairing code for user ${request.uid} - ${e}`
// );
// response.status(200).send({
// data: {
// status: -999,
// },
// });
// return;
// }
// });
exports.unlinkDiscord = functions.https.onRequest((request, response) => {
response.set("Access-Control-Allow-Origin", "*");
if (request.method === "OPTIONS") {
// Send response to OPTIONS requests
response.set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.set(
"Access-Control-Allow-Headers",
"Authorization,Content-Type"
);
response.set("Access-Control-Allow-Headers", "Authorization,Content-Type");
response.set("Access-Control-Max-Age", "3600");
response.status(204).send("");
return;
@ -1578,40 +1637,46 @@ exports.unlinkDiscord = functions.https.onRequest((request, response) => {
request = request.body.data;
try {
if (request === null || request.uid === undefined) {
response.status(200).send({ data: { status: -999, message: "Empty request" } });
response
.status(200)
.send({ data: { status: -999, message: "Empty request" } });
return;
}
return db.collection(`users`).doc(request.uid).update({
discordId: null
}).then(f => {
response.status(200).send({
data: {
status: 1,
message: "Unlinked"
},
return db
.collection(`users`)
.doc(request.uid)
.update({
discordId: null,
})
.then((f) => {
response.status(200).send({
data: {
status: 1,
message: "Unlinked",
},
});
return;
})
.catch((e) => {
response.status(200).send({
data: {
status: -999,
message: e.message,
},
});
return;
});
return;
}).catch(e => {
response.status(200).send({
data: {
status: -999,
message: e.message
},
});
return;
})
} catch (e) {
response.status(200).send({
data: {
status: -999,
message: e
message: e,
},
});
return;
}
});
async function checkLeaderboards(
resultObj,
type,

35
package-lock.json generated
View file

@ -611,7 +611,7 @@
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"requires": {
"lodash": ">=4.17.19"
"lodash": "^4.17.14"
}
},
"async-each": {
@ -1773,7 +1773,7 @@
"jsonschema": "^1.0.2",
"jsonwebtoken": "^8.2.1",
"leven": "^3.1.0",
"lodash": ">=4.17.19",
"lodash": "^4.17.14",
"marked": "^0.7.0",
"marked-terminal": "^3.3.0",
"minimatch": "^3.0.4",
@ -2272,7 +2272,7 @@
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^2.0.0",
"lodash": ">=4.17.19",
"lodash": "^4.17.11",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rxjs": "^6.4.0",
@ -2429,6 +2429,17 @@
"requires": {
"node-fetch": "^1.0.1",
"whatwg-fetch": ">=0.10.0"
},
"dependencies": {
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
"requires": {
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
}
}
}
},
"isstream": {
@ -2979,7 +2990,7 @@
"requires": {
"async": "^1.3.0",
"flat-arguments": "^1.0.0",
"lodash": ">=4.17.19",
"lodash": "^4.17.5",
"minimist": "^1.1.0"
},
"dependencies": {
@ -3014,13 +3025,9 @@
}
},
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
"requires": {
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
}
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-forge": {
"version": "0.9.1",
@ -3712,7 +3719,7 @@
"home-dir": "^1.0.0",
"is-url": "^1.2.2",
"join-path": "^1.1.1",
"lodash": ">=4.17.19",
"lodash": "^4.17.4",
"mime-types": "^2.1.16",
"minimatch": "^3.0.4",
"morgan": "^1.8.2",
@ -3958,7 +3965,7 @@
"resolved": "https://registry.npmjs.org/toxic/-/toxic-1.0.1.tgz",
"integrity": "sha512-WI3rIGdcaKULYg7KVoB0zcjikqvcYYvcuT6D89bFPz2rVR0Rl0PK6x8/X62rtdLtBKIE985NzVf/auTtGegIIg==",
"requires": {
"lodash": ">=4.17.19"
"lodash": "^4.17.10"
}
},
"traverse": {
@ -4233,7 +4240,7 @@
"requires": {
"http-parser-js": ">=0.4.0 <0.4.11",
"safe-buffer": ">=5.1.0",
"websocket-extensions": ">=0.1.4"
"websocket-extensions": ">=0.1.1"
}
},
"websocket-extensions": {

View file

@ -1470,6 +1470,7 @@ key {
width: 100%;
align-content: flex-start;
user-select: none;
padding-bottom: 1em;
/* a little hack for right-to-left languages */
&.rightToLeftTest {

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Monkey Type</title>
<title>Monkeytype</title>
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
<link rel="stylesheet" href="css/balloon.css" />
<link rel="stylesheet" href="css/style.min.css?v=58" />
@ -11,34 +11,28 @@
<link rel="stylesheet" href="" id="funBoxTheme" />
<link id="favicon" rel="shortcut icon" href="fav.png" />
<link rel="shortcut icon" href="fav.png" />
<meta name="name" content="Monkey Type" />
<meta name="image" content="https://monkey-type.com/mtsocial_large.png" />
<meta name="name" content="Monkeytype" />
<meta name="image" content="https://monkeytype.com/mtsocial.png" />
<meta
name="description"
content="A super customisable, minimalistic typing test."
content="A minimalistic, customisable typing website. Test yourself in various modes, track your progress and improve your typing speed."
/>
<meta
name="keywords"
content="typing, test, typing-test, typing test, monkey-type, monkeytype, monkey type, monkey-types, monkey types, types, monkey, type, miodec, wpm, words per minute, typing website, minimalistic, custom typing test, customizable, customisable, themes, random words, smooth caret, smooth, new, new typing site, new typing website, minimalist typing website, minimalistic typing website, minimalist typing test"
content="typing, test, typing-test, typing test, monkey-type, monkeytype, monkey type, monkey-types, monkeytypes, monkey types, types, monkey, type, miodec, wpm, words per minute, typing website, minimalistic, custom typing test, customizable, customisable, themes, random words, smooth caret, smooth, new, new typing site, new typing website, minimalist typing website, minimalistic typing website, minimalist typing test"
/>
<meta name="author" content="Miodec" />
<meta property="og:title" content="Monkey Type" />
<meta property="og:url" content="https://monkey-type.com/" />
<meta property="og:title" content="Monkeytype" />
<meta property="og:url" content="https://monkeytype.com/" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="A super customisable, minimalistic typing test."
/>
<meta
property="og:image"
content="https://monkey-type.com/mtsocial_large.png"
content="A minimalistic, customisable typing website. Test yourself in various modes, track your progress and improve your typing speed."
/>
<meta property="og:image" content="https://monkeytype.com/mtsocial.png" />
<meta name="theme-color" content="#e2b714" id="metaThemeColor" />
<meta name="twitter:title" content="Monkey Type" />
<meta
name="twitter:image"
content="https://monkey-type.com/mtsocial_large.png"
/>
<meta name="twitter:title" content="Monkeytype" />
<meta name="twitter:image" content="https://monkeytype.com/mtsocial.png" />
<meta name="twitter:card" content="summary_large_image" />
</head>
@ -749,7 +743,7 @@
<div class="suggestions"></div>
</div>
<div id="commandInput" class="hidden">
<input type="text" class="input" placeholder="input"></input>
<input type="text" class="input" placeholder="input" />
</div>
</div>
<div id="timerWrapper">
@ -758,17 +752,22 @@
<div id="centerContent" class="hidden">
<div id="top">
<div class="logo">
<div class="top">monkey-see</div>
<div class="bottom">monkey-type</div>
<div class="top">monkey see</div>
<div class="bottom">monkeytype</div>
</div>
<div id="menu">
<div class="icon-button" tabindex="2" href="/" onclick="this.blur();">
<div
class="icon-button view-start"
tabindex="2"
href="/"
onclick="this.blur();"
>
<div class="icon">
<i class="fas fa-fw fa-keyboard"></i>
</div>
</div>
<div
class="icon-button leaderboards"
class="icon-button leaderboards view-leaderboards"
tabindex="2"
onclick="this.blur();"
>
@ -777,7 +776,7 @@
</div>
</div>
<div
class="icon-button"
class="icon-button view-about"
tabindex="2"
href="/about"
onclick="this.blur();"
@ -799,7 +798,7 @@
</div>
</a> -->
<div
class="icon-button"
class="icon-button view-settings"
tabindex="2"
href="/settings"
onclick="this.blur();"
@ -809,7 +808,7 @@
</div>
</div>
<div
class="icon-button hidden account"
class="icon-button hidden account view-account"
tabindex="2"
href="/account"
onclick="this.blur();"
@ -820,7 +819,7 @@
<div class="text"></div>
</div>
<div
class="icon-button login"
class="icon-button login view-login"
tabindex="2"
href="/login"
onclick="this.blur();"
@ -832,12 +831,7 @@
</div>
<div class="config">
<div
style="
display: grid;
grid-auto-flow: column;
"
>
<div style="display: grid; grid-auto-flow: column;">
<div class="group punctuationMode">
<!-- <div class="title">time</div> -->
<div class="buttons">
@ -928,7 +922,6 @@
</div>
<div id="middle">
<div class="page pageTest active">
<div id="typingTest">
<div id="capsWarning" class="hidden">
<i class="fas fa-lock"></i>
@ -936,14 +929,17 @@
</div>
<div id="testModesNotice"></div>
<div id="caret" class="default size15"></div>
<div id="paceCaret"class="default size15 hidden"></div>
<div id="paceCaret" class="default size15 hidden"></div>
<input id="wordsInput" class="" tabindex="0" autocomplete="off" />
<div id="timerNumber"><div>60</div></div>
<div id="miniTimerAndLiveWpm">
<div class="time hidden">1:00</div>
<div class="wpm">60</div>
</div>
<div class="outOfFocusWarning"><i class="fas fa-mouse-pointer"></i> Click the words to focus</div>
<div class="outOfFocusWarning">
<i class="fas fa-mouse-pointer"></i>
Click the words to focus
</div>
<div id="wordsWrapper">
<div id="words" class="size15"></div>
</div>
@ -1157,10 +1153,10 @@
<div class="group wpm">
<div class="top">
wpm
<div class="crown hidden" aria-label="" data-balloon-pos="up">
<i class="fas fa-crown"></i>
</div>
<div class="crown hidden" aria-label="" data-balloon-pos="up">
<i class="fas fa-crown"></i>
</div>
</div>
<div class="bottom" aria-label="" data-balloon-pos="up">-</div>
</div>
<div class="group acc">
@ -1190,8 +1186,15 @@
<div class="bottom" aria-label="" data-balloon-pos="up">-</div>
</div>
<div class="group key">
<div class="top">char</div>
<div class="bottom" aria-label="" data-balloon-pos="up">-</div>
<div class="top">characters</div>
<div
class="bottom"
aria-label="Correct, incorrect, extra and missed"
data-balloon-break=""
data-balloon-pos="up"
>
-
</div>
</div>
<!-- </div> -->
@ -1237,7 +1240,7 @@
<div class="words"></div>
</div>
<div class="loginTip">Sign in to save your results</div>
<div class="ssWatermark hidden">monkey-type.com</div>
<div class="ssWatermark hidden">monkeytype.com</div>
<div class="buttons">
<div
id="nextTestButton"
@ -1291,7 +1294,7 @@
<div class="section">
<h1>about</h1>
<p>
Monkey-type is a minimalistic typing test, featuring many test
Monkeytype is a minimalistic typing test, featuring many test
modes, an account system to save your typing speed history and
user configurable features like themes, a smooth caret and more.
</p>
@ -1362,9 +1365,10 @@
Discord server
</a>
, send me a message on Reddit or create an issue on
<a href="https://github.com/Miodec/monkey-type" target="_blank">
GitHub.
<a href="https://github.com/Miodec/monkeytype" target="_blank">
GitHub
</a>
.
</p>
</div>
<div class="section">
@ -1397,7 +1401,7 @@
</p>
<p>
<a
href="https://github.com/Miodec/monkey-type/graphs/contributors"
href="https://github.com/Miodec/monkeytype/graphs/contributors"
>
Contributors
</a>
@ -1451,36 +1455,33 @@
<div class="section discordIntegration">
<h1>discord integration</h1>
<div class="text">
When you connect your monkey-type account to your Discord
When you connect your monkeytype account to your Discord
account, you will be automatically assigned a new role every
time you achieve a new personal best in a 60 second test.
<div class="howto hidden">
To pair your accounts, direct message 'George' (our bot) on
Discord with the message '!verify
<span class="howtocode">-</span>
'.
</div>
</div>
<div class="buttons">
<div class="button generateCodeButton">
Generate pairing code
</div>
<a
class="button"
href="https://discord.com/api/oauth2/authorize?client_id=757704816532258856&redirect_uri=https%3A%2F%2Fmonkeytype.com%2Fverify&response_type=token&scope=identify"
style="text-decoration: none;"
>
Verify with Discord
</a>
</div>
<div class="info hidden">
<div>
<i class="fas fa-check"></i>
Your accounts are paired!
</div>
<div id="unlinkDiscordButton" class="text-button" aria-label="Unlink" data-balloon-pos="up">
<div
id="unlinkDiscordButton"
class="text-button"
aria-label="Unlink"
data-balloon-pos="up"
>
<i class="fas fa-unlink" aria-hidden="true"></i>
</div>
</div>
<div class="code hidden">
<div style="width: min-content; white-space: nowrap;">
<div class="top">code</div>
<div class="bottom">-</div>
</div>
</div>
</div>
<div class="sectionSpacer"></div>
</div>
@ -1543,8 +1544,6 @@
</div>
</div>
<div class="section blindMode">
<h1>blind mode</h1>
<div class="text">
@ -1607,6 +1606,34 @@
</div>
</div>
</div>
<div class="section singleListCommandLine">
<h1>single list command line</h1>
<div class="text">
When enabled, it will show the command line with all commands in
a single list instead of submenu arrangements. Selecting
'manual' will expose all commands only after typing
<key>></key>
.
</div>
<div class="buttons">
<div
class="button"
singleListCommandLine="manual"
tabindex="0"
onclick="this.blur();"
>
manual
</div>
<div
class="button"
singleListCommandLine="on"
tabindex="0"
onclick="this.blur();"
>
on
</div>
</div>
</div>
<div class="section language">
<h1>language</h1>
<div class="buttons"></div>
@ -1923,7 +1950,14 @@
>
custom
</div>
<input type="number" step="1" class="customPaceCaretSpeed" placeholder="wpm" min="0" value="">
<input
type="number"
step="1"
class="customPaceCaretSpeed"
placeholder="wpm"
min="0"
value=""
/>
</div>
</div>
<div class="section paceCaretStyle" section="">
@ -2137,7 +2171,8 @@
<div class="section startGraphsAtZero">
<h1>start graphs at zero</h1>
<div class="text">
Force graph axis to always start at zero, no matter what the data is. Turning this off may exaggerate the value changes.
Force graph axis to always start at zero, no matter what the
data is. Turning this off may exaggerate the value changes.
</div>
<div class="buttons">
<div class="button off" tabindex="0" onclick="this.blur();">
@ -2560,7 +2595,8 @@
<div class="section showOutOfFocusWarning">
<h1>out of focus warning</h1>
<div class="text">
Shows an out of focus reminder after 1 second of being 'out of focus' (not being able to type).
Shows an out of focus reminder after 1 second of being 'out of
focus' (not being able to type).
</div>
<div class="buttons">
<div class="button off" tabindex="0" onclick="this.blur();">
@ -2605,12 +2641,9 @@
<div class="register side">
<div class="title">register</div>
<form action="" autocomplete="off">
<input type="text" placeholder="username" />
<input type="text" placeholder="username" />
<input type="text" placeholder="email" />
<input
type="password"
placeholder="password"
/>
<input type="password" placeholder="password" />
<input type="password" placeholder="verify password" />
<div class="button">
<i class="fas fa-user-plus"></i>
@ -2622,8 +2655,12 @@
<div class="title">login</div>
<div id="forgotPasswordButton">Forgot password?</div>
<form action="">
<input type="text" placeholder="email" autocomplete="email"/>
<input type="password" placeholder="password" autocomplete="password"/>
<input type="text" placeholder="email" autocomplete="email" />
<input
type="password"
placeholder="password"
autocomplete="password"
/>
<div>
<label id="rememberMe">
<input type="checkbox" checked />
@ -2667,9 +2704,7 @@
</div>
<div class="group">
<div class="title">personal bests</div>
<div style="display: grid;
grid-auto-flow: column;
gap: 1rem;">
<div style="display: grid; grid-auto-flow: column; gap: 1rem;">
<div class="titleAndTable timePbTable">
<table width="100%">
<thead>
@ -2678,6 +2713,7 @@
<td>wpm</td>
<td>raw</td>
<td>accuracy</td>
<td>consistency</td>
</tr>
</thead>
<tbody>
@ -2716,6 +2752,7 @@
<td>wpm</td>
<td>raw</td>
<td>accuracy</td>
<td>consistency</td>
</tr>
</thead>
<tbody>
@ -2768,7 +2805,10 @@
</div>
</div>
</div>
<div class="buttonsAndTitle testDate" style="grid-column: 1/3;margin-top: 1rem">
<div
class="buttonsAndTitle testDate"
style="grid-column: 1/3; margin-top: 1rem;"
>
<!-- <div class="title">date</div> -->
<div class="buttons filterGroup" group="date">
<div class="button" filter="last_day">last day</div>
@ -2778,7 +2818,7 @@
</div>
</div>
</div>
<div class="group filterButtons" style="display:none">
<div class="group filterButtons" style="display: none;">
<div class="buttonsAndTitle" style="grid-column: 1/3;">
<div class="title">advanced filters</div>
<div class="buttons">
@ -2861,7 +2901,10 @@
<canvas id="resultHistoryChart"></canvas>
</div>
<div class="below"></div>
<div class="toggleAccuracyOnChart button"><i class="fas fa-bullseye"></i>Toggle Accuracy</div>
<div class="toggleAccuracyOnChart button">
<i class="fas fa-bullseye"></i>
Toggle Accuracy
</div>
</div>
<div class="group dailyActivityChart">
<div class="chart" style="height: 200px;">
@ -2914,7 +2957,13 @@
<div class="group testsCompleted">
<div class="title">
tests completed
<span data-balloon-length="xlarge" aria-label="Due to the increasing number of results in the database, you can now only see your last 1000 results in detail. Total time spent typing, started and completed tests stats will still be up to date at the top of the page, above the filters." data-balloon-pos="up"><i class="fas fa-question-circle"></i></span>
<span
data-balloon-length="xlarge"
aria-label="Due to the increasing number of results in the database, you can now only see your last 1000 results in detail. Total time spent typing, started and completed tests stats will still be up to date at the top of the page, above the filters."
data-balloon-pos="up"
>
<i class="fas fa-question-circle"></i>
</span>
</div>
<div class="val">-</div>
</div>
@ -3016,9 +3065,9 @@
<div>
<i class="fas fa-fw fa-code"></i>
Contribute on
<a
href="https://github.com/Miodec/monkey-type"
target="_blank">GitHub</a>
<a href="https://github.com/Miodec/monkeytype" target="_blank">
GitHub
</a>
</div>
<div>
<i class="fas fa-fw fa-donate"></i>
@ -3037,7 +3086,9 @@
</div>
<div>
<i class="fab fa-discord"></i>
<a href="https://www.discord.gg/monkeytype" class="discordLink">discord.gg/monkeytype</a>
<a href="https://www.discord.gg/monkeytype" class="discordLink">
discord.gg/monkeytype
</a>
</div>
<div>
<i class="fas fa-code-branch"></i>
@ -3060,7 +3111,10 @@
<!-- Initialize Firebase -->
<script src="/__/firebase/init.js"></script>
<script src="https://kit.fontawesome.com/f3f87d89b4.js" crossorigin="anonymous"></script>
<script
src="https://kit.fontawesome.com/f3f87d89b4.js"
crossorigin="anonymous"
></script>
<script src="js/jquery-3.5.1.min.js"></script>
<script src="js/jquery.color.min.js"></script>
<script src="js/easing.js"></script>

View file

@ -261,6 +261,17 @@ firebase.auth().onAuthStateChanged(function (user) {
// showNotification('Signed in', 1000);
$(".pageLogin .preloader").addClass("hidden");
$("#menu .icon-button.account .text").text(displayName);
if (verifyUserWhenLoggedIn !== null) {
showNotification('Verifying', 1000);
verifyUserWhenLoggedIn.uid = user.uid;
verifyUser(verifyUserWhenLoggedIn).then(data => {
showNotification(data.data.message, 3000);
if (data.data.status === 1) {
dbSnapshot.discordId = data.data.did;
updateDiscordSettingsSection()
}
})
}
}
});
@ -536,6 +547,16 @@ let activityChart = new Chart($(".pageAccount #activityChart"), {
lineStyle: "dotted",
width: 2,
},
order: 3
},
{
yAxisID: "avgWpm",
label: "Average Wpm",
data: [],
type: "line",
order: 2,
lineTension: 0,
fill: false,
},
],
},
@ -612,6 +633,28 @@ let activityChart = new Chart($(".pageAccount #activityChart"), {
fontFamily: "Roboto Mono",
},
},
{
id: "avgWpm",
ticks: {
fontFamily: "Roboto Mono",
beginAtZero: true,
min: 0,
autoSkip: true,
stepSize: 1,
autoSkipPadding: 40,
stepSize: 10,
},
display: true,
position: "right",
scaleLabel: {
display: true,
labelString: "Average Wpm",
fontFamily: "Roboto Mono",
},
gridLines: {
display: false,
},
},
],
},
},
@ -849,6 +892,9 @@ function updateHoverChart(filteredId) {
if (!config.startGraphsAtZero) {
hoverChart.options.scales.yAxes[0].ticks.min = Math.round(minChartVal);
hoverChart.options.scales.yAxes[1].ticks.min = Math.round(minChartVal);
} else {
hoverChart.options.scales.yAxes[0].ticks.min = 0;
hoverChart.options.scales.yAxes[1].ticks.min = 0;
}
hoverChart.update({ duration: 0 });
@ -1452,24 +1498,28 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>30</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>60</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>120</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
`);
$(".pageAccount .wordsPbTable tbody").html(`
@ -1478,24 +1528,28 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>25</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>50</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>100</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
`);
@ -1511,6 +1565,9 @@ function fillPbTables() {
<td>${pbData.wpm}</td>
<td>${pbData.raw === undefined ? "-" : pbData.raw}</td>
<td>${pbData.acc === undefined ? "-" : pbData.acc + "%"}</td>
<td>
${pbData.consistency === undefined ? "-" : pbData.consistency + "%"}
</td>
</tr>`;
} catch (e) {
text += `<tr>
@ -1518,6 +1575,7 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>`;
}
try {
@ -1527,6 +1585,9 @@ function fillPbTables() {
<td>${pbData.wpm}</td>
<td>${pbData.raw === undefined ? "-" : pbData.raw}</td>
<td>${pbData.acc === undefined ? "-" : pbData.acc + "%"}</td>
<td>
${pbData.consistency === undefined ? "-" : pbData.consistency + "%"}
</td>
</tr>`;
} catch (e) {
text += `<tr>
@ -1534,6 +1595,7 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>`;
}
try {
@ -1543,6 +1605,9 @@ function fillPbTables() {
<td>${pbData.wpm}</td>
<td>${pbData.raw === undefined ? "-" : pbData.raw}</td>
<td>${pbData.acc === undefined ? "-" : pbData.acc + "%"}</td>
<td>
${pbData.consistency === undefined ? "-" : pbData.consistency + "%"}
</td>
</tr>`;
} catch (e) {
text += `<tr>
@ -1550,6 +1615,7 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>`;
}
try {
@ -1559,6 +1625,9 @@ function fillPbTables() {
<td>${pbData.wpm}</td>
<td>${pbData.raw === undefined ? "-" : pbData.raw}</td>
<td>${pbData.acc === undefined ? "-" : pbData.acc + "%"}</td>
<td>
${pbData.consistency === undefined ? "-" : pbData.consistency + "%"}
</td>
</tr>`;
} catch (e) {
text += `<tr>
@ -1566,6 +1635,7 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>`;
}
$(".pageAccount .timePbTable tbody").html(text);
@ -1578,6 +1648,9 @@ function fillPbTables() {
<td>${pbData.wpm}</td>
<td>${pbData.raw === undefined ? "-" : pbData.raw}</td>
<td>${pbData.acc === undefined ? "-" : pbData.acc + "%"}</td>
<td>
${pbData.consistency === undefined ? "-" : pbData.consistency + "%"}
</td>
</tr>`;
} catch (e) {
text += `<tr>
@ -1585,6 +1658,7 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>`;
}
try {
@ -1594,6 +1668,9 @@ function fillPbTables() {
<td>${pbData.wpm}</td>
<td>${pbData.raw === undefined ? "-" : pbData.raw}</td>
<td>${pbData.acc === undefined ? "-" : pbData.acc + "%"}</td>
<td>
${pbData.consistency === undefined ? "-" : pbData.consistency + "%"}
</td>
</tr>`;
} catch (e) {
text += `<tr>
@ -1601,6 +1678,7 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>`;
}
try {
@ -1610,6 +1688,9 @@ function fillPbTables() {
<td>${pbData.wpm}</td>
<td>${pbData.raw === undefined ? "-" : pbData.raw}</td>
<td>${pbData.acc === undefined ? "-" : pbData.acc + "%"}</td>
<td>
${pbData.consistency === undefined ? "-" : pbData.consistency + "%"}
</td>
</tr>`;
} catch (e) {
text += `<tr>
@ -1617,6 +1698,7 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>`;
}
try {
@ -1626,6 +1708,9 @@ function fillPbTables() {
<td>${pbData.wpm}</td>
<td>${pbData.raw === undefined ? "-" : pbData.raw}</td>
<td>${pbData.acc === undefined ? "-" : pbData.acc + "%"}</td>
<td>
${pbData.consistency === undefined ? "-" : pbData.consistency + "%"}
</td>
</tr>`;
} catch (e) {
text += `<tr>
@ -1633,6 +1718,7 @@ function fillPbTables() {
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>`;
}
$(".pageAccount .wordsPbTable tbody").html(text);
@ -1854,7 +1940,7 @@ function refreshAccountPage() {
let consCount = 0;
let dailyActivityDays = [];
let activityChartData = [];
let activityChartData = {};
filteredResults = [];
$(".pageAccount .history table tbody").empty();
@ -2004,9 +2090,13 @@ function refreshAccountPage() {
resultDate = resultDate.getTime();
if (Object.keys(activityChartData).includes(String(resultDate))) {
activityChartData[resultDate] = activityChartData[resultDate] + 1;
activityChartData[resultDate].amount++;
activityChartData[resultDate].totalWpm += result.wpm;
} else {
activityChartData[resultDate] = 1;
activityChartData[resultDate] = {
amount: 1,
totalWpm: result.wpm,
}
}
tt = 0;
@ -2101,7 +2191,8 @@ function refreshAccountPage() {
thisDate.setMilliseconds(0);
thisDate = thisDate.getTime();
let tempChartData = [];
let activityChartData_amount = [];
let activityChartData_avgWpm = [];
let lastTimestamp = 0;
Object.keys(activityChartData).forEach((date) => {
let datecheck;
@ -2115,23 +2206,27 @@ function refreshAccountPage() {
if (numDaysBetweenTheDays > 1) {
if (datecheck === thisDate) {
tempChartData.push({
activityChartData_amount.push({
x: parseInt(thisDate),
y: 0,
});
}
for (let i = 0; i < numDaysBetweenTheDays - 1; i++) {
tempChartData.push({
activityChartData_amount.push({
x: parseInt(datecheck) - 86400000 * (i + 1),
y: 0,
});
}
}
tempChartData.push({
activityChartData_amount.push({
x: parseInt(date),
y: activityChartData[date],
y: activityChartData[date].amount,
});
activityChartData_avgWpm.push({
x: parseInt(date),
y: roundTo2(activityChartData[date].totalWpm / activityChartData[date].amount),
});
lastTimestamp = date;
});
@ -2150,7 +2245,18 @@ function refreshAccountPage() {
activityChart.options.legend.labels.fontColor = themeColors.sub;
activityChart.data.datasets[0].trendlineLinear.style = themeColors.sub;
activityChart.data.datasets[0].data = tempChartData;
activityChart.data.datasets[0].data = activityChartData_amount;
activityChart.options.scales.yAxes[1].ticks.minor.fontColor =
themeColors.sub;
activityChart.options.scales.yAxes[1].scaleLabel.fontColor =
themeColors.sub;
activityChart.data.datasets[1].borderColor = themeColors.sub;
// activityChart.data.datasets[1].backgroundColor = themeColors.main;
activityChart.data.datasets[1].data = activityChartData_avgWpm;
activityChart.options.legend.labels.fontColor = themeColors.sub;
resultHistoryChart.options.scales.xAxes[0].ticks.minor.fontColor =
themeColors.sub;
@ -2192,6 +2298,9 @@ function refreshAccountPage() {
resultHistoryChart.options.scales.yAxes[1].ticks.min = Math.floor(
minAccuracyChartVal
);
} else {
resultHistoryChart.options.scales.yAxes[0].ticks.min = 0;
resultHistoryChart.options.scales.yAxes[1].ticks.min = 0;
}
if (chartData == [] || chartData.length == 0) {
@ -2361,6 +2470,7 @@ function refreshAccountPage() {
try {
cont();
} catch (e) {
console.error(e);
showNotification(`Something went wrong: ${e}`, 5000);
}
}
@ -2393,21 +2503,6 @@ function hideResultEditTagsPanel() {
}
}
let chartAccuracyVisible = true;
function toggleChartAccuracy() {
if (chartAccuracyVisible) {
resultHistoryChart.data.datasets[1].hidden = true;
resultHistoryChart.options.scales.yAxes[1].display = false;
chartAccuracyVisible = false;
} else {
resultHistoryChart.data.datasets[1].hidden = false;
resultHistoryChart.options.scales.yAxes[1].display = true;
chartAccuracyVisible = true;
}
resultHistoryChart.update();
}
$(".pageAccount .toggleAccuracyOnChart").click((params) => {
toggleChartAccuracy();
});

View file

@ -2,6 +2,57 @@ function capitalizeFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function addChildCommands(unifiedCommands, commandItem, parentCommandDisplay = '') {
let commandItemDisplay = commandItem.display.replace(/\s?\.\.\.$/g,'');
if (parentCommandDisplay) commandItemDisplay = parentCommandDisplay + " > " + commandItemDisplay;
if (commandItem.subgroup) {
try {
commandItem.exec();
currentCommandsIndex = currentCommands.length-1;
currentCommands[currentCommandsIndex].list.forEach( cmd => addChildCommands(unifiedCommands, cmd, commandItemDisplay));
currentCommands.pop();
} catch(e) {}
} else {
let tempCommandItem = {...commandItem};
if (parentCommandDisplay)
tempCommandItem.display = commandItemDisplay;
unifiedCommands.push(tempCommandItem);
}
}
function generateSingleListOfCommands() {
allCommands = [];
oldShowCommandLine = showCommandLine;
showCommandLine = () => {};
commands.list.forEach(c => addChildCommands(allCommands, c));
showCommandLine = oldShowCommandLine;
return {
title: "All Commands",
list: allCommands};
}
function isSingleListCommandLineActive() {
return $("#commandLine").hasClass("allCommands");
}
function useSingleListCommandLine(show = true) {
let allCommands = generateSingleListOfCommands();
if (config.singleListCommandLine == "manual") currentCommands.push(allCommands);
else if (config.singleListCommandLine == "on") currentCommands = [allCommands];
if (config.singleListCommandLine != "off") $("#commandLine").addClass("allCommands");
if (show) showCommandLine();
}
function restoreOldCommandLine(show = true) {
if (isSingleListCommandLineActive()) {
$("#commandLine").removeClass("allCommands");
currentCommands = currentCommands.filter( l => l.title != "All Commands");
if (currentCommands.length < 1) currentCommands = [commands];
}
if (show) showCommandLine();
}
let commands = {
title: "",
list: [
@ -167,6 +218,15 @@ let commands = {
toggleQuickEnd();
},
},
{
id: "singleListCommandLine",
display: "Single list command line...",
subgroup: true,
exec: () => {
currentCommands.push(commandsSingleListCommandLine);
showCommandLine();
},
},
{
id: "togglePlaySoundOnError",
display: "Toggle play sound on error",
@ -404,6 +464,34 @@ let commands = {
display: "Next random theme",
exec: () => randomiseTheme()
},
{
id: "viewTypingPage",
display: "View Typing Page",
exec: () => $('#top #menu .icon-button.view-start').click()
},
{
id: "viewLeaderboards",
display: "View Leaderboards Page",
exec: () => $('#top #menu .icon-button.view-leaderboards').click()
},
{
id: "viewAbout",
display: "View About Page",
exec: () => $('#top #menu .icon-button.view-about').click()
},
{
id: "viewSettings",
display: "View Settings Page",
exec: () => $('#top #menu .icon-button.view-settings').click()
},
{
id: "viewAccount",
display: "View Account Page",
exec: () =>
$('#top #menu .icon-button.view-account').hasClass('hidden') ?
$('#top #menu .icon-button.view-login').click() :
$('#top #menu .icon-button.view-account').click()
},
{
id: "bailOut",
display: "Bail out...",
@ -841,6 +929,26 @@ let commandsTimerColor = {
],
};
let commandsSingleListCommandLine = {
title: "Single list command line...",
list: [
{
id: "singleListCommandLineManual",
display: "manual",
exec: () => {
setSingleListCommandLine("manual");
},
},
{
id: "singleListCommandLineOn",
display: "on",
exec: () => {
setSingleListCommandLine("on");
},
}
],
};
let commandsTimerOpacity = {
title: "Change timer opacity...",
list: [
@ -1351,12 +1459,20 @@ $(document).ready((e) => {
//escape
if ((event.keyCode == 27 && !config.swapEscAndTab) || (event["keyCode"] == 9 && config.swapEscAndTab)) {
event.preventDefault();
if ($("#commandLineWrapper").hasClass("hidden")) {
currentCommands = [commands];
if (!$("#leaderboardsWrapper").hasClass("hidden")) { //maybe add more condition for closing other dialogs in the future as well
event.preventDefault();
hideLeaderboards();
return;
} else if ($("#commandLineWrapper").hasClass("hidden")) {
if (config.singleListCommandLine == "on")
useSingleListCommandLine(false);
else
currentCommands = [commands];
showCommandLine();
} else {
if (currentCommands.length > 1) {
currentCommands.pop();
$("#commandLine").removeClass("allCommands");
showCommandLine();
} else {
hideCommandLine();
@ -1414,18 +1530,34 @@ $("#commandLineWrapper #commandLine .suggestions").on("mouseover", (e) => {
});
$("#commandLineWrapper #commandLine .suggestions").click((e) => {
$(".suggestions .entry").removeClass('activeKeyboard');
triggerCommand($(e.target).attr("command"));
});
$("#commandLineWrapper").click((e) => {
if ($(e.target).attr("id") === "commandLineWrapper") {
hideCommandLine();
setTheme(config.theme, true);
}
});
$(document).keydown((e) => {
if (isPreviewingTheme) {
previewTheme(config.theme, false);
}
if (!$("#commandLineWrapper").hasClass("hidden")) {
$("#commandLine input").focus();
if (e.key == ">" && config.singleListCommandLine == "manual") {
if (!isSingleListCommandLineActive()) {
useSingleListCommandLine();
return;
} else if ($("#commandLine input").val() == ">") { //so that it will ignore succeeding ">" when input is already ">"
e.preventDefault();
return;
}
}
if (e.keyCode == 8 && $("#commandLine input").val().length == 1 && config.singleListCommandLine == "manual" && isSingleListCommandLineActive())
restoreOldCommandLine();
if (e.keyCode == 13) {
//enter
e.preventDefault();
@ -1533,6 +1665,7 @@ function hideCommandLine() {
100,
() => {
$("#commandLineWrapper").addClass("hidden");
$("#commandLine").removeClass("allCommands");
focusWords();
}
);
@ -1572,9 +1705,11 @@ function showCommandInput(command, placeholder) {
}
function updateSuggestedCommands() {
let inputVal = $("#commandLine input").val().toLowerCase().split(" ");
let inputVal = $("#commandLine input").val().toLowerCase().split(" ").filter((s,i) => s||i==0); //remove empty entries after first
let list = currentCommands[currentCommands.length - 1];
if (inputVal[0] == "") {
//ignore the preceeding ">"s in the command line input
if (inputVal[0] && inputVal[0][0] == ">") inputVal[0] = inputVal[0].replace(/^>+/,'');
if (inputVal[0] == "" && inputVal.length == 1) {
$.each(list.list, (index, obj) => {
if (obj.visible !== false) obj.found = true;
});

View file

@ -68,6 +68,7 @@ async function db_getUserSnapshot() {
.then((res) => {
// console.log('getting data from db!');
let data = res.data();
if (data === undefined) return;
try {
if (data.personalBests !== undefined) {
snap.personalBests = data.personalBests;
@ -207,7 +208,8 @@ async function db_saveLocalPB(
difficulty,
wpm,
acc,
raw
raw,
consistency
) {
function cont() {
try {
@ -225,6 +227,8 @@ async function db_saveLocalPB(
pb.wpm = wpm;
pb.acc = acc;
pb.raw = raw;
pb.timestamp = Date.now();
pb.consistency = consistency;
}
});
if (!found) {
@ -236,6 +240,8 @@ async function db_saveLocalPB(
wpm: wpm,
acc: acc,
raw: raw,
timestamp: Date.now(),
consistency: consistency
});
}
} catch (e) {
@ -249,6 +255,8 @@ async function db_saveLocalPB(
wpm: wpm,
acc: acc,
raw: raw,
timestamp: Date.now(),
consistency: consistency
},
];
}

View file

@ -27168,12 +27168,6 @@
"id": 4573,
"length": 262
},
{
"text": "I've flown seven million miles. And I've been waiting on people almost 20 years. The best job I could get after my bust was Cabo Air, which is the worst job you can get in this industry. And now with this arrest hanging over my head, I'm scared. If I lose my job I gotta start all over again, but I got nothing to start over with.",
"source": "Jackie Brown",
"id": 4574,
"length": 330
},
{
"text": "Raindrops on roses and whiskers on kittens, bright copper kettles and warm woolen mittens, brown paper packages tied up with strings, these are a few of my favorite things. Cream-colored ponies and crisp apple strudels, doorbells and sleigh bells and schnitzel with noodles, wild geese that fly with the moon on their wings, these are a few of my favorite things.",
"source": "My Favorite Things",

View file

@ -148,7 +148,7 @@ function showNotification(text, time) {
function getReleasesFromGitHub() {
$.getJSON(
"https://api.github.com/repos/Miodec/monkey-type/releases",
"https://api.github.com/repos/Miodec/monkeytype/releases",
(data) => {
$("#bottom .version").text(data[0].name).css("opacity", 1);
$("#versionHistory .releases").empty();

View file

@ -41,6 +41,8 @@ let caretAnimating = true;
let lastSecondNotRound = false;
let paceCaret = null;
let missedWords = [];
let verifyUserWhenLoggedIn = null;
let modeBeforePractise = null;
let themeColors = {
bg: "#323437",
@ -73,6 +75,8 @@ let keypressStats = {
let errorSound = new Audio("../sound/error.wav");
let clickSounds = null;
let isPreviewingTheme = false;
function initClickSounds() {
clickSounds = {
"1": [
@ -207,6 +211,8 @@ const generatePairingCode = firebase
.httpsCallable("generatePairingCode");
const saveLbMemory = firebase.functions().httpsCallable("saveLbMemory");
const unlinkDiscord = firebase.functions().httpsCallable("unlinkDiscord");
const verifyUser = firebase.functions().httpsCallable("verifyUser");
function refreshThemeColorObject() {
@ -431,7 +437,7 @@ function initWords() {
config.mode == "custom"
) {
// let wordsBound = config.mode == "time" ? 60 : config.words;
let wordsBound = 60;
let wordsBound = 100;
if (config.showAllLines) {
if (config.mode === "custom") {
if (customTextIsRandom) {
@ -710,10 +716,9 @@ function punctuateWord(previousWord, currentWord, index, maxindex) {
}
function addWord() {
let bound = 60;
let bound = 100;
if (activeFunBox === "plus_one") bound = 1;
if (
!config.showAllLines &&
(wordsList.length - inputHistory.length > bound ||
(config.mode === "words" && wordsList.length >= config.words) ||
(config.mode === "custom" &&
@ -1510,19 +1515,32 @@ function countChars() {
}
} else {
//not enough chars
let toAdd = {
correct: 0,
incorrect: 0,
missed: 0
}
for (let c = 0; c < wordsList[i].length; c++) {
if (c < inputHistory[i].length) {
//on char that still has a word list pair
if (inputHistory[i][c] == wordsList[i][c]) {
correctChars++;
toAdd.correct++;
} else {
incorrectChars++;
toAdd.incorrect++;
}
} else {
//on char that is extra
missedChars++;
toAdd.missed++;
}
}
correctChars += toAdd.correct;
incorrectChars += toAdd.incorrect;
if (i === inputHistory.length - 1 && config.mode == "time") {
//last word - check if it was all correct - add to correct word chars
if(toAdd.incorrect === 0) correctWordChars += toAdd.correct;
} else {
missedChars += toAdd.missed;
}
}
if (i < inputHistory.length - 1) {
spaces++;
@ -1573,7 +1591,9 @@ function calculateStats() {
wpmRaw: isNaN(wpmraw) ? 0 : wpmraw,
acc: acc,
correctChars: chars.correctWordChars,
incorrectChars: chars.incorrectChars + chars.extraChars + chars.missedChars,
incorrectChars: chars.incorrectChars,
missedChars: chars.missedChars,
extraChars: chars.extraChars,
allChars:
chars.allCorrectChars +
chars.spaces +
@ -1621,6 +1641,8 @@ function showResult(difficultyFailed = false) {
acc: 0,
correctChars: 0,
incorrectChars: 0,
missedChars: 0,
extraChars: 0,
time: 0,
spaces: 0,
correctSpaces: 0,
@ -1667,15 +1689,18 @@ function showResult(difficultyFailed = false) {
let correctcharpercent = roundTo2(
((stats.correctChars + stats.correctSpaces) /
(stats.correctChars + stats.correctSpaces + stats.incorrectChars)) *
(stats.correctChars + stats.correctSpaces + stats.incorrectChars + stats.extraChars)) *
100
);
$("#result .stats .key .bottom").text(testtime + "s");
$("#result .stats .key .bottom").attr("aria-label", `${correctcharpercent}%`);
// $("#result .stats .key .bottom").attr("aria-label", `Correct, incorrect, missed and extra \n ${correctcharpercent}%`);
$("#words").removeClass("blurred");
$(".outOfFocusWarning").addClass("hidden");
$("#result .stats .key .bottom").text(
stats.correctChars + stats.correctSpaces + "/" + stats.incorrectChars
stats.correctChars + stats.correctSpaces +
"/" + stats.incorrectChars +
"/" + stats.extraChars +
"/" + stats.missedChars
);
setTimeout(function () {
@ -1808,6 +1833,9 @@ function showResult(difficultyFailed = false) {
if (!config.startGraphsAtZero) {
wpmOverTimeChart.options.scales.yAxes[0].ticks.min = minChartVal;
wpmOverTimeChart.options.scales.yAxes[1].ticks.min = minChartVal;
} else {
wpmOverTimeChart.options.scales.yAxes[0].ticks.min = 0;
wpmOverTimeChart.options.scales.yAxes[1].ticks.min = 0;
}
// wpmOverTimeChart.options.scales.yAxes[0].ticks.min = Math.round(minChartVal);
@ -2011,9 +2039,9 @@ function showResult(difficultyFailed = false) {
if (dbSnapshot !== null && dbSnapshot.results !== undefined) {
dbSnapshot.results.unshift(completedEvent);
if (dbSnapshot.globalStats.time == undefined) {
dbSnapshot.globalStats.time = testtime;
dbSnapshot.globalStats.time = testtime + completedEvent.incompleteTestSeconds;
} else {
dbSnapshot.globalStats.time += testtime;
dbSnapshot.globalStats.time += testtime + completedEvent.incompleteTestSeconds;
}
if (dbSnapshot.globalStats.started == undefined) {
dbSnapshot.globalStats.started = restartCount + 1;
@ -2211,6 +2239,7 @@ function showResult(difficultyFailed = false) {
stats.wpm,
stats.acc,
stats.wpmRaw,
consistency
);
} else if (e.data.resultCode === 1) {
if (localPb) {
@ -2563,6 +2592,12 @@ function restartTest(withSameWordset = false, nosave = false) {
}
}
if (modeBeforePractise !== null) {
showNotification("Reverting to previous settings.", 1500);
changeMode(modeBeforePractise);
modeBeforePractise = null;
}
manualRestart = false;
clearTimeout(timer);
time = 0;
@ -3101,7 +3136,7 @@ async function loadWordsHistory() {
let wordEl = "";
try {
if (input === "") throw Exception;
if (correctedHistory[i] !== "") {
if (correctedHistory[i] !== undefined && correctedHistory[i] !== "") {
wordEl = `<div class='word' input="${correctedHistory[i].replace(
/"/g,
"&quot;"
@ -3109,11 +3144,41 @@ async function loadWordsHistory() {
} else {
wordEl = `<div class='word' input="${input.replace(/"/g, "&quot;")}">`;
}
if (input !== wordsList[i]) {
wordEl = `<div class='word error' input="${input.replace(
/"/g,
"&quot;"
)}">`;
if (i === inputHistory.length - 1) {
//last word
let word = {
correct: 0,
incorrect: 0,
missed: 0
}
for (let c = 0; c < wordsList[i].length; c++) {
if (c < inputHistory[i].length) {
//on char that still has a word list pair
if (inputHistory[i][c] == wordsList[i][c]) {
word.correct++;
} else {
word.incorrect++;
}
} else {
//on char that is extra
word.missed++;
}
}
if (word.incorrect !== 0 || config.mode !== "time") {
if (input !== wordsList[i]) {
wordEl = `<div class='word error' input="${input.replace(
/"/g,
"&quot;"
)}">`;
}
}
} else {
if (input !== wordsList[i]) {
wordEl = `<div class='word error' input="${input.replace(
/"/g,
"&quot;"
)}">`;
}
}
let loop;
@ -3134,7 +3199,7 @@ async function loadWordsHistory() {
correctedChar = undefined;
}
let extraCorrected = "";
if (c + 1 === loop && correctedHistory[i].length > input.length) {
if (c + 1 === loop && correctedHistory[i] !== undefined &&correctedHistory[i].length > input.length) {
extraCorrected = "extraCorrected";
}
if (wordsList[i][c] !== undefined) {
@ -4141,11 +4206,13 @@ $(document.body).on("click", "#restartTestButton", (event) => {
$(document).on("keypress", "#practiseMissedWordsButton", (event) => {
if (event.keyCode == 13) {
if (missedWords.length > 0) {
let currentMode = config.mode;
changeMode("custom");
customText = missedWords;
customTextIsRandom = true;
customTextWordCount = 50;
restartTest();
modeBeforePractise = currentMode;
} else {
showNotification("You haven't missed any words.", 2000);
}
@ -4154,11 +4221,13 @@ $(document).on("keypress", "#practiseMissedWordsButton", (event) => {
$(document.body).on("click", "#practiseMissedWordsButton", (event) => {
if (missedWords.length > 0) {
let currentMode = config.mode;
changeMode("custom");
customText = missedWords;
customTextIsRandom = true;
customTextWordCount = 50;
restartTest();
modeBeforePractise = currentMode;
} else {
showNotification("You haven't missed any words.", 2000);
}
@ -4774,11 +4843,11 @@ $(document).keydown((event) => {
) {
updateTimer();
}
if (config.showAllLines) {
if (config.mode == "time") {
addWord();
}
} else {
// if (config.showAllLines) {
// if (config.mode == "time") {
// addWord();
// }
// } else {
if (
config.mode == "time" ||
config.mode == "words" ||
@ -4786,7 +4855,7 @@ $(document).keydown((event) => {
) {
addWord();
}
}
// }
}
}
});
@ -4824,7 +4893,10 @@ $(document).on("mouseenter", "#resultWordsHistory .words .word", (e) => {
});
$(document).on("click", "#bottom .leftright .right .current-theme", (e) => {
currentCommands.push(commandsThemes);
if (config.customTheme) {
togglePresetCustomTheme();
}
currentCommands = [commandsThemes];
showCommandLine();
});
@ -4866,7 +4938,18 @@ $(document).ready(() => {
setCustomThemeInputs();
applyCustomThemeColors();
}
if (window.location.pathname === "/account") {
if (window.location.pathname === "/verify") {
const fragment = new URLSearchParams(window.location.hash.slice(1));
if (fragment.has("access_token")) {
const accessToken = fragment.get("access_token");
const tokenType = fragment.get("token_type");
verifyUserWhenLoggedIn = {
accessToken: accessToken,
tokenType: tokenType
}
history.replaceState("/", null, "/");
}
}else if (window.location.pathname === "/account") {
history.replaceState("/", null, "/");
} else if (window.location.pathname !== "/") {
let page = window.location.pathname.replace("/", "");

View file

@ -147,6 +147,10 @@ settingsGroups.alwaysShowWordsHistory = new SettingsGroup(
"alwaysShowWordsHistory",
setAlwaysShowWordsHistory
);
settingsGroups.singleListCommandLine = new SettingsGroup(
"singleListCommandLine",
setSingleListCommandLine
);
settingsGroups.flipTestColors = new SettingsGroup(
"flipTestColors",
setFlipTestColors
@ -467,7 +471,7 @@ $("#shareCustomThemeButton").click((e) => {
}
);
let url = "https://monkey-type.com?" + objectToQueryString({ customTheme: share });
let url = "https://monkeytype.com?" + objectToQueryString({ customTheme: share });
navigator.clipboard.writeText(url).then(function () {
showNotification("URL Copied to clipboard", 2000);
}, function (err) {
@ -611,48 +615,20 @@ function updateDiscordSettingsSection() {
$(".pageSettings .section.discordIntegration").removeClass("hidden");
if (
dbSnapshot.pairingCode == undefined &&
dbSnapshot.discordId == undefined
) {
//show button
$(".pageSettings .section.discordIntegration .howto").addClass("hidden");
$(".pageSettings .section.discordIntegration .buttons").removeClass(
"hidden"
);
$(".pageSettings .section.discordIntegration .info").addClass("hidden");
$(".pageSettings .section.discordIntegration .code").addClass("hidden");
} else if (
dbSnapshot.pairingCode != undefined &&
dbSnapshot.discordId == undefined
) {
//show code
$(".pageSettings .section.discordIntegration .code .bottom").text(
dbSnapshot.pairingCode
);
$(".pageSettings .section.discordIntegration .howtocode").text(
dbSnapshot.pairingCode
);
$(".pageSettings .section.discordIntegration .howto").removeClass(
"hidden"
);
$(".pageSettings .section.discordIntegration .buttons").addClass(
"hidden"
);
$(".pageSettings .section.discordIntegration .info").addClass("hidden");
$(".pageSettings .section.discordIntegration .code").removeClass(
"hidden"
);
} else if (
dbSnapshot.discordId != undefined
) {
$(".pageSettings .section.discordIntegration .howto").addClass("hidden");
} else{
$(".pageSettings .section.discordIntegration .buttons").addClass(
"hidden"
);
$(".pageSettings .section.discordIntegration .info").removeClass(
"hidden"
);
$(".pageSettings .section.discordIntegration .code").addClass("hidden");
}
}
}

View file

@ -53,6 +53,7 @@ let defaultConfig = {
smoothLineScroll: false,
alwaysShowDecimalPlaces: false,
alwaysShowWordsHistory: false,
singleListCommandLine: "manual",
playSoundOnError: false,
playSoundOnClick: "off",
startGraphsAtZero: true,
@ -60,7 +61,8 @@ let defaultConfig = {
showOutOfFocusWarning: true,
paceCaret: "off",
paceCaretCustomSpeed: 100,
pageWidth: "100"
pageWidth: "100",
chartAccuracy: true
};
let cookieConfig = null;
@ -190,6 +192,7 @@ function applyConfig(configObj) {
setShowTimerProgress(configObj.showTimerProgress, true);
setAlwaysShowDecimalPlaces(configObj.alwaysShowDecimalPlaces, true);
setAlwaysShowWordsHistory(configObj.alwaysShowWordsHistory, true);
setSingleListCommandLine(configObj.singleListCommandLine, true);
setPlaySoundOnError(configObj.playSoundOnError, true);
setPlaySoundOnClick(configObj.playSoundOnClick, true);
setStopOnError(configObj.stopOnError, true);
@ -201,6 +204,7 @@ function applyConfig(configObj) {
setPaceCaret(configObj.paceCaret, true);
setPaceCaretCustomSpeed(configObj.paceCaretCustomSpeed, true);
setPageWidth(configObj.pageWidth, true);
setChartAccuracy(configObj.chartAccuracy, true);
config.startGraphsAtZero = configObj.startGraphsAtZero;
// if (
@ -306,6 +310,31 @@ function setBlindMode(blind, nosave) {
if (!nosave) saveConfigToCookie();
}
function updateChartAccuracy() {
resultHistoryChart.data.datasets[1].hidden = !config.chartAccuracy;
resultHistoryChart.options.scales.yAxes[1].display = config.chartAccuracy;
resultHistoryChart.update();
}
function toggleChartAccuracy() {
if (config.chartAccuracy) {
config.chartAccuracy = false;
} else {
config.chartAccuracy = true;
}
updateChartAccuracy();
saveConfigToCookie();
}
function setChartAccuracy(chartAccuracy, nosave) {
if (chartAccuracy == undefined) {
chartAccuracy = true;
}
config.chartAccuracy = chartAccuracy;
updateChartAccuracy();
if (!nosave) saveConfigToCookie();
}
//read ahead mode
// function toggleReadAheadMode() {
// config.readAheadMode = !config.readAheadMode;
@ -429,6 +458,13 @@ function setAlwaysShowWordsHistory(val, nosave) {
if (!nosave) saveConfigToCookie();
}
//single list command line
function setSingleListCommandLine(option, nosave) {
if (!option) option = "manual";
config.singleListCommandLine = option;
if (!nosave) saveConfigToCookie();
}
//show all lines
function toggleShowAllLines() {
sal = !config.showAllLines;
@ -873,7 +909,7 @@ function setIndicateTypos(it, nosave) {
}
function previewTheme(name) {
function previewTheme(name, setIsPreviewingVar = true) {
if (
(testActive || resultVisible) &&
(config.theme === "nausea" || config.theme === "round_round_baby")
@ -881,6 +917,7 @@ function previewTheme(name) {
return;
if (resultVisible && (name === "nausea" || name === "round_round_baby"))
return;
isPreviewingTheme = setIsPreviewingVar;
$("#currentTheme").attr("href", `themes/${name}.css`);
setTimeout(() => {
refreshThemeColorObject();
@ -969,6 +1006,7 @@ function applyCustomThemeColors() {
document.documentElement.style.setProperty(e, array[index]);
});
} else {
$(".current-theme").text(config.theme.replace('_',' '));
previewTheme(config.theme);
colorVars.forEach((e) => {
document.documentElement.style.setProperty(e, "");

View file

@ -4158,206 +4158,206 @@ const words = {
italian: {
leftToRight: true,
words: [
"cosa",
"modo",
"giorno",
"morire",
"venire",
"chiamare",
"chiudere",
"prendere",
"perché",
"città",
"signore",
"ora",
"quello",
"grande",
"potere",
"fenomeno",
"amore",
"momento",
"anno",
"macchina",
"fratello",
"amico",
"nemico",
"bisogno",
"salire",
"uno",
"capacità",
"capire",
"italiano",
"francese",
"spagnolo",
"cane",
"oca",
"panda",
"koala",
"nero",
"ciano",
"rosa",
"colore",
"superiore",
"fisica",
"latino",
"volare",
"spendere",
"veloce",
"uscire",
"invitare",
"vento",
"dormire",
"rettangolo",
"chiesa",
"tutti",
"calcio",
"benvenuto",
"video",
"cinema",
"nemmeno",
"come",
"ho",
"ascoltare",
"guidare",
"treno",
"rispondere",
"niente",
"utente",
"disegno",
"sole",
"cuore",
"sicuramente",
"egocentrico",
"basso",
"pasta",
"libro",
"baciare",
"cavallo",
"merenda",
"quaderno",
"vero",
"facile",
"recipiente",
"dove",
"quando",
"comode",
"albero",
"poco",
"bambino",
"notare",
"moda",
"naturale",
"chiaro",
"scuro",
"chiaramente",
"lentamente",
"pavimento",
"parete",
"semplice",
"probabilità",
"domani",
"oggi",
"ieri",
"tantissimo",
"bello",
"brevemente",
"righello",
"conoscere",
"anello",
"no",
"sì",
"si",
"pensare",
"fino",
"montagna",
"mare",
"legno",
"opera",
"oceano",
"salice",
"cipresso",
"musica",
"Sanremo",
"mela",
"pera",
"grano",
"arancia",
"commentare",
"coltivare",
"seminare",
"nuvole",
"temporale",
"tempo",
"mancano",
"onda",
"energia",
"spirito",
"anima",
"corrente",
"respirazione",
"concentrazione",
"universo",
"mondo",
"salto",
"dolore",
"cento",
"camicia",
"motore",
"meccanismo",
"Milano",
"Roma",
"Palermo",
"cucinare",
"camera",
"delfino",
"pesce",
"canzone",
"comprare",
"vendere",
"chiedere",
"camminare",
"io",
"nostro",
"per",
"sono",
"con",
"uno",
"è",
"avevamo",
"questo",
"quello",
"da",
"caldo",
"freddo",
"corsa",
"forse",
"nove",
"nuovo",
"sorridere",
"permettere",
"già",
"più",
"parola",
"però",
"cosa",
"alcuni",
"vostro",
"posto",
"altro",
"era",
"no",
"fare",
"sì",
"preso",
"chiesto",
"fatto",
"tempo",
"ogni",
"dire",
"tre",
"quattro",
"chiedere",
"bene",
"anche",
"diversità",
"sognare",
"occhio",
"giocare",
"piccolo",
"male",
"morto",
"mettere",
"finire",
"leggere",
"mano",
"casa",
"grande",
"più",
"terra",
"giovane",
"vecchio",
"giardino",
"essere",
"piccolo",
"medio",
"memoria",
"mentre",
"tipo",
"bisogno",
"provare",
"mamma",
"papà",
"vicino",
"nuovo",
"indietro",
"anno",
"treno",
"pensiero",
"turno",
"molte",
"troppo",
"italiano",
"quando",
"sarebbe",
"lungo",
"guardare",
"andare",
"potuto",
"numero",
"compagno",
"governo",
"grazie",
"prego",
"che",
"mano",
"piede",
"gamba",
"sembrare",
"qualcuno",
"qualcosa",
"presto",
"moneta",
"giorno",
"mio",
"sapere",
"acqua",
"aranciata",
"limone",
"cestino",
"problema",
"preoccuparsi"
"trovare",
"fondare",
"risposta",
"scuola",
"ancora",
"qualche",
"chiamare",
"chiudere",
"portare",
"idea",
"colore",
"legno",
"bianco",
"nero",
"camminare",
"alleviare",
"secondo",
"abbastanza",
"primo",
"misurare",
"metà",
"capire",
"comprendere",
"prendere",
"studiare",
"mancare",
"lontano",
"sicuramente",
"veloce",
"velocemente",
"giustamente",
"intelligente",
"oceano",
"matematica",
"mente",
"ricordo",
"interessante",
"parole",
"nuvole",
"cielo",
"pace",
"amore",
"fratello",
"sorella",
"famiglia",
"massa",
"palla",
"cuore",
"venire",
"inverno",
"strano",
"viaggio",
"musica",
"melodia",
"pausa",
"salire",
"giardino",
"giusto",
"bambino",
"latte",
"ordinare",
"povero",
"metallo",
"legge",
"per",
"a",
"anello",
"raggio",
"atomo",
"umano",
"pasta",
"pizza",
"pianoforte",
"maggiore",
"minore",
"scale",
"corrente",
"pranzo",
"mangiare",
"uscire",
"cane",
"gatto",
"elefante",
"mucca",
"albero",
"fiore",
"vincere",
"perdere",
"baciare",
"vediamo",
"chiacchierare",
"video",
"canzone",
"vento",
"contro",
"serpente",
"presto",
"mentre",
"durante",
"dentro",
"completamente",
"chimica",
"fisica",
"latino",
"viaggiare",
"loro",
"bicicletta",
"automobile",
"pensare",
"curare",
"lago",
"aperto",
"sentire",
"cavallo",
"notte",
"bello",
"brutto",
"oggi",
"domani",
"ieri",
"dormire",
"sognare",
"divano",
"arrogante",
"ballare"
]
},
norwegian: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View file

@ -1,70 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Monkey Type - Coming Soon!</title>
<link
href="https://fonts.googleapis.com/css?family=Roboto+Mono&display=swap"
rel="stylesheet"
/>
<style>
* {
font-family: "Roboto Mono";
color: var(--main-color);
}
:root {
--main-color: #eee;
--sub-color: #444;
--bg-color: #111;
--caret-color: #fff;
--active-word-color: #444;
--roundness: 0.25rem;
}
body {
background-color: var(--bg-color);
width: 100vw;
height: 100vh;
display: grid;
align-content: center;
justify-content: center;
padding: 0;
margin: 0;
}
.grid {
display: grid;
gap: 1rem;
}
.logo {
margin-bottom: -0.12rem;
white-space: nowrap;
}
.logo .top {
font-size: 0.65rem;
line-height: 0.65rem;
margin-bottom: -0.4rem;
margin-left: -0.1rem;
color: var(--sub-color);
}
.logo .bottom {
margin-left: -0.15rem;
font-size: 2.3rem;
line-height: 2.3rem;
}
.soon {
text-align: center;
}
</style>
</head>
<body>
<div class="grid">
<div class="logo">
<div class="top">monkey-see</div>
<div class="bottom">monkey-type</div>
</div>
<div class="soon">Coming very soon...</div>
</div>
</body>
</html>

View file

@ -0,0 +1,19 @@
:root {
--bg-color: #ebe1ef;
--main-color: #8a5bd6;
--caret-color: #212b43;
--sub-color: #ac76e5;
--text-color: #212b43;
--error-color: #f794ca;
--error-extra-color: #f279c2;
--colorful-error-color: #f794ca;
--colorful-error-extra-color: #f279c2;
}
#menu .icon-button {
color: #ba96db;
}
#menu .icon-button:hover {
color: #212b43;
}

View file

@ -388,5 +388,15 @@
"name": "nebula",
"bgColor": "#212135",
"textColor": "#be3c88"
},
{
"name": "lil_dragon",
"bgColor": "#ebe1ef",
"textColor": "#8a5bd6"
},
{
"name": "pastel",
"bgColor": "#ffd1dc",
"textColor": "#b39eb5"
}
]

11
public/themes/pastel.css Normal file
View file

@ -0,0 +1,11 @@
:root {
--bg-color: #e0b2bd;
--main-color: #fbf4b6;
--caret-color: #fbf4b6;
--sub-color: #b4e9ff;
--text-color: #6d5c6f;
--error-color: #ff6961;
--error-extra-color: #c23b22;
--colorful-error-color: #ff6961;
--colorful-error-extra-color: #c23b22;
}