mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-09 23:25:53 +08:00
5936 lines
165 KiB
JavaScript
5936 lines
165 KiB
JavaScript
let wordsList = [];
|
||
let currentWordIndex = 0;
|
||
let currentWordElementIndex = 0;
|
||
let inputHistory = [];
|
||
let correctedHistory = [];
|
||
let currentCorrected = "";
|
||
let currentInput = "";
|
||
let time = 0;
|
||
let timer = null;
|
||
let testActive = false;
|
||
let testStart, testEnd;
|
||
let testInvalid = false;
|
||
let wpmHistory = [];
|
||
let rawHistory = [];
|
||
let restartCount = 0;
|
||
let incompleteTestSeconds = 0;
|
||
let currentTestLine = 0;
|
||
let pageTransition = false;
|
||
let lineTransition = false;
|
||
let keypressPerSecond = [];
|
||
let currentKeypress = {
|
||
count: 0,
|
||
mod: 0,
|
||
words: [],
|
||
};
|
||
let errorsPerSecond = [];
|
||
let currentError = {
|
||
count: 0,
|
||
words: [],
|
||
};
|
||
let resultVisible = false;
|
||
let activeWordTopBeforeJump = 0;
|
||
let activeWordTop = 0;
|
||
let activeWordJumped = false;
|
||
let sameWordset = false;
|
||
let quotes = null;
|
||
let focusState = false;
|
||
let activeFunBox = "none";
|
||
let manualRestart = false;
|
||
let bailout = false;
|
||
let notSignedInLastResult = null;
|
||
let caretAnimating = true;
|
||
let lastSecondNotRound = false;
|
||
let paceCaret = null;
|
||
let missedWords = [];
|
||
let verifyUserWhenLoggedIn = null;
|
||
let modeBeforePractise = null;
|
||
let punctuationBeforePractise = null;
|
||
let numbersBeforePractise = null;
|
||
let memoryFunboxTimer = null;
|
||
let memoryFunboxInterval = null;
|
||
let textHasTab = false;
|
||
|
||
let themeColors = {
|
||
bg: "#323437",
|
||
main: "#e2b714",
|
||
caret: "#e2b714",
|
||
sub: "#646669",
|
||
text: "#d1d0c5",
|
||
error: "#ca4754",
|
||
errorExtra: "#7e2a33",
|
||
colorfulError: "#ca4754",
|
||
colorfulErrorExtra: "#7e2a33",
|
||
};
|
||
|
||
let accuracyStats = {
|
||
correct: 0,
|
||
incorrect: 0,
|
||
};
|
||
|
||
let keypressStats = {
|
||
spacing: {
|
||
current: -1,
|
||
array: [],
|
||
},
|
||
duration: {
|
||
current: -1,
|
||
array: [],
|
||
},
|
||
};
|
||
|
||
let errorSound = new Audio("../sound/error.wav");
|
||
let clickSounds = null;
|
||
|
||
let isPreviewingTheme = false;
|
||
|
||
function initClickSounds() {
|
||
clickSounds = {
|
||
1: [
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click1/click1_1.wav"),
|
||
new Audio("../sound/click1/click1_1.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click1/click1_2.wav"),
|
||
new Audio("../sound/click1/click1_2.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click1/click1_3.wav"),
|
||
new Audio("../sound/click1/click1_3.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
],
|
||
2: [
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click2/click2_1.wav"),
|
||
new Audio("../sound/click2/click2_1.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click2/click2_2.wav"),
|
||
new Audio("../sound/click2/click2_2.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click2/click2_3.wav"),
|
||
new Audio("../sound/click2/click2_3.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
],
|
||
3: [
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click3/click3_1.wav"),
|
||
new Audio("../sound/click3/click3_1.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click3/click3_2.wav"),
|
||
new Audio("../sound/click3/click3_2.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click3/click3_3.wav"),
|
||
new Audio("../sound/click3/click3_3.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
],
|
||
4: [
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click4/click4_1.wav"),
|
||
new Audio("../sound/click4/click4_1.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click4/click4_2.wav"),
|
||
new Audio("../sound/click4/click4_2.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click4/click4_3.wav"),
|
||
new Audio("../sound/click4/click4_3.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click4/click4_4.wav"),
|
||
new Audio("../sound/click4/click4_4.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click4/click4_5.wav"),
|
||
new Audio("../sound/click4/click4_5.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
{
|
||
sounds: [
|
||
new Audio("../sound/click4/click4_6.wav"),
|
||
new Audio("../sound/click4/click4_6.wav"),
|
||
],
|
||
counter: 0,
|
||
},
|
||
],
|
||
};
|
||
}
|
||
|
||
let customText = {
|
||
text: "The quick brown fox jumps over the lazy dog".split(" "),
|
||
isWordRandom: false,
|
||
isTimeRandom: false,
|
||
word: "",
|
||
time: "",
|
||
};
|
||
|
||
// let customText = "The quick brown fox jumps over the lazy dog".split(" ");
|
||
// let customText.isWordRandom = false;
|
||
// let customText.word = 1;
|
||
let randomQuote = null;
|
||
|
||
function refreshThemeColorObject() {
|
||
let st = getComputedStyle(document.body);
|
||
|
||
themeColors.bg = st.getPropertyValue("--bg-color").replace(" ", "");
|
||
themeColors.main = st.getPropertyValue("--main-color").replace(" ", "");
|
||
themeColors.caret = st.getPropertyValue("--caret-color").replace(" ", "");
|
||
themeColors.sub = st.getPropertyValue("--sub-color").replace(" ", "");
|
||
themeColors.text = st.getPropertyValue("--text-color").replace(" ", "");
|
||
themeColors.error = st.getPropertyValue("--error-color").replace(" ", "");
|
||
themeColors.errorExtra = st
|
||
.getPropertyValue("--error-extra-color")
|
||
.replace(" ", "");
|
||
themeColors.colorfulError = st
|
||
.getPropertyValue("--colorful-error-color")
|
||
.replace(" ", "");
|
||
themeColors.colorfulErrorExtra = st
|
||
.getPropertyValue("--colorful-error-extra-color")
|
||
.replace(" ", "");
|
||
updateChartColors();
|
||
}
|
||
|
||
function copyResultToClipboard() {
|
||
$(".pageTest .ssWatermark").removeClass("hidden");
|
||
$(".pageTest .buttons").addClass("hidden");
|
||
let src = $("#middle");
|
||
var sourceX = src.position().left; /*X position from div#target*/
|
||
var sourceY = src.position().top; /*Y position from div#target*/
|
||
var sourceWidth = src.width(); /*clientWidth/offsetWidth from div#target*/
|
||
var sourceHeight = src.height(); /*clientHeight/offsetHeight from div#target*/
|
||
$(".notification").addClass("hidden");
|
||
$(".pageTest .loginTip").addClass("hidden");
|
||
try {
|
||
html2canvas(document.body, {
|
||
backgroundColor: themeColors.bg,
|
||
height: sourceHeight + 50,
|
||
width: sourceWidth + 50,
|
||
x: sourceX - 25,
|
||
y: sourceY - 25,
|
||
}).then(function (canvas) {
|
||
canvas.toBlob(function (blob) {
|
||
try {
|
||
navigator.clipboard
|
||
.write([
|
||
new ClipboardItem(
|
||
Object.defineProperty({}, blob.type, {
|
||
value: blob,
|
||
enumerable: true,
|
||
})
|
||
),
|
||
])
|
||
.then((f) => {
|
||
$(".notification").removeClass("hidden");
|
||
Notifications.add("Copied to clipboard", 1, 2);
|
||
$(".pageTest .ssWatermark").addClass("hidden");
|
||
$(".pageTest .buttons").removeClass("hidden");
|
||
if (firebase.auth().currentUser == null)
|
||
$(".pageTest .loginTip").removeClass("hidden");
|
||
})
|
||
.catch((f) => {
|
||
open(URL.createObjectURL(blob));
|
||
$(".notification").removeClass("hidden");
|
||
Notifications.add(
|
||
"Error saving image to clipboard: " + f.message,
|
||
-1
|
||
);
|
||
$(".pageTest .ssWatermark").addClass("hidden");
|
||
$(".pageTest .buttons").removeClass("hidden");
|
||
if (firebase.auth().currentUser == null)
|
||
$(".pageTest .loginTip").removeClass("hidden");
|
||
});
|
||
} catch (e) {
|
||
open(URL.createObjectURL(blob));
|
||
$(".notification").removeClass("hidden");
|
||
Notifications.add(
|
||
"Error saving image to clipboard: " + e.message,
|
||
-1
|
||
);
|
||
$(".pageTest .ssWatermark").addClass("hidden");
|
||
$(".pageTest .buttons").removeClass("hidden");
|
||
if (firebase.auth().currentUser == null)
|
||
$(".pageTest .loginTip").removeClass("hidden");
|
||
}
|
||
});
|
||
});
|
||
} catch (e) {
|
||
$(".notification").removeClass("hidden");
|
||
Notifications.add("Error creating image: " + e.message, -1);
|
||
$(".pageTest .ssWatermark").addClass("hidden");
|
||
$(".pageTest .buttons").removeClass("hidden");
|
||
if (firebase.auth().currentUser == null)
|
||
$(".pageTest .loginTip").removeClass("hidden");
|
||
}
|
||
}
|
||
|
||
async function activateFunbox(funbox, mode) {
|
||
if (testActive || resultVisible) {
|
||
Notifications.add(
|
||
"You can only change the funbox before starting a test.",
|
||
0
|
||
);
|
||
return false;
|
||
}
|
||
if (Misc.getCurrentLanguage().ligatures) {
|
||
if (funbox == "choo_choo" || funbox == "earthquake") {
|
||
Notifications.add(
|
||
"Current language does not support this funbox mode",
|
||
0
|
||
);
|
||
activateFunbox("none", null);
|
||
return;
|
||
}
|
||
}
|
||
$("#funBoxTheme").attr("href", ``);
|
||
$("#words").removeClass("nospace");
|
||
// if (funbox === "none") {
|
||
activeFunBox = "none";
|
||
memoryFunboxInterval = clearInterval(memoryFunboxInterval);
|
||
memoryFunboxTimer = null;
|
||
$("#wordsWrapper").removeClass("hidden");
|
||
// }
|
||
|
||
if (mode === null || mode === undefined) {
|
||
let list = await Misc.getFunboxList();
|
||
mode = list.filter((f) => f.name === funbox)[0].type;
|
||
}
|
||
|
||
manualRestart = true;
|
||
if (mode === "style") {
|
||
if (funbox != undefined) {
|
||
$("#funBoxTheme").attr("href", `funbox/${funbox}.css`);
|
||
activeFunBox = funbox;
|
||
}
|
||
|
||
if (funbox === "simon_says") {
|
||
setKeymapMode("next");
|
||
settingsGroups.keymapMode.updateButton();
|
||
restartTest();
|
||
}
|
||
|
||
if (funbox === "read_ahead" || funbox === "read_ahead_easy") {
|
||
setHighlightMode("letter", true);
|
||
restartTest();
|
||
}
|
||
} else if (mode === "script") {
|
||
if (funbox === "tts") {
|
||
$("#funBoxTheme").attr("href", `funbox/simon_says.css`);
|
||
config.keymapMode = "off";
|
||
settingsGroups.keymapMode.updateButton();
|
||
restartTest();
|
||
} else if (funbox === "layoutfluid") {
|
||
config.keymapMode = "on";
|
||
setKeymapMode("next");
|
||
settingsGroups.keymapMode.updateButton();
|
||
config.savedLayout = config.layout;
|
||
setLayout("qwerty");
|
||
settingsGroups.layout.updateButton();
|
||
setKeymapLayout("qwerty");
|
||
settingsGroups.keymapLayout.updateButton();
|
||
restartTest();
|
||
} else if (funbox === "memory") {
|
||
setMode("words");
|
||
setShowAllLines(true, true);
|
||
restartTest(false, true);
|
||
if (config.keymapMode === "next") {
|
||
setKeymapMode("react");
|
||
}
|
||
} else if (funbox === "nospace") {
|
||
$("#words").addClass("nospace");
|
||
setHighlightMode("letter", true);
|
||
restartTest(false, true);
|
||
}
|
||
activeFunBox = funbox;
|
||
}
|
||
|
||
if (funbox !== "layoutfluid" || mode !== "script") {
|
||
if (config.layout !== config.savedLayout) {
|
||
setLayout(config.savedLayout);
|
||
settingsGroups.layout.updateButton();
|
||
}
|
||
}
|
||
updateTestModesNotice();
|
||
return true;
|
||
}
|
||
|
||
function toggleScriptFunbox(...params) {
|
||
if (activeFunBox === "tts") {
|
||
var msg = new SpeechSynthesisUtterance();
|
||
msg.text = params[0];
|
||
msg.lang = "en-US";
|
||
window.speechSynthesis.cancel();
|
||
window.speechSynthesis.speak(msg);
|
||
}
|
||
}
|
||
|
||
function getuid() {
|
||
console.error("Only share this uid with Miodec and nobody else!");
|
||
console.log(firebase.auth().currentUser.uid);
|
||
console.error("Only share this uid with Miodec and nobody else!");
|
||
}
|
||
|
||
function setFocus(foc) {
|
||
if (foc && !focusState) {
|
||
focusState = true;
|
||
stopCaretAnimation();
|
||
$("#top").addClass("focus");
|
||
$("#bottom").addClass("focus");
|
||
$("body").css("cursor", "none");
|
||
$("#middle").addClass("focus");
|
||
} else if (!foc && focusState) {
|
||
focusState = false;
|
||
if (testActive) {
|
||
stopCaretAnimation();
|
||
} else {
|
||
startCaretAnimation();
|
||
}
|
||
$("#top").removeClass("focus");
|
||
$("#bottom").removeClass("focus");
|
||
$("body").css("cursor", "default");
|
||
$("#middle").removeClass("focus");
|
||
}
|
||
}
|
||
|
||
async function initWords() {
|
||
testActive = false;
|
||
wordsList = [];
|
||
currentWordIndex = 0;
|
||
currentWordElementIndex = 0;
|
||
accuracyStats = {
|
||
correct: 0,
|
||
incorrect: 0,
|
||
};
|
||
inputHistory = [];
|
||
correctedHistory = [];
|
||
currentCorrected = "";
|
||
currentInput = "";
|
||
|
||
let language = await Misc.getLanguage(config.language);
|
||
if (language && language.name !== config.language) {
|
||
config.language = "english";
|
||
}
|
||
|
||
if (
|
||
config.mode === "quote" &&
|
||
(quotes === null ||
|
||
quotes.language !== config.language.replace(/_\d*k$/g, ""))
|
||
) {
|
||
// if (config.language.split("_")[0] !== "code") {
|
||
setLanguage(config.language.replace(/_\d*k$/g, ""), true);
|
||
// }
|
||
showBackgroundLoader();
|
||
$.ajax({
|
||
url: `quotes/${config.language}.json`,
|
||
async: false,
|
||
success: function (data) {
|
||
hideBackgroundLoader();
|
||
try {
|
||
if (data.quotes.length === 0) {
|
||
throw new Error("No quotes");
|
||
}
|
||
quotes = data;
|
||
quotes.groups.forEach((qg, i) => {
|
||
let lower = qg[0];
|
||
let upper = qg[1];
|
||
quotes.groups[i] = quotes.quotes.filter((q) => {
|
||
if (q.length >= lower && q.length <= upper) {
|
||
q.group = i;
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
});
|
||
});
|
||
quotes.quotes = [];
|
||
} catch (e) {
|
||
console.error(e);
|
||
Notifications.add(
|
||
`No ${config.language.replace(/_\d*k$/g, "")} quotes found`,
|
||
0
|
||
);
|
||
return;
|
||
}
|
||
},
|
||
error: (e) => {
|
||
Notifications.add(
|
||
`Error while loading ${config.language.replace(
|
||
/_\d*k$/g,
|
||
""
|
||
)} quotes: ${e}`,
|
||
-1
|
||
);
|
||
return;
|
||
},
|
||
});
|
||
}
|
||
|
||
if (!language) {
|
||
config.language = "english";
|
||
language = words[config.language];
|
||
}
|
||
|
||
if (
|
||
config.mode == "time" ||
|
||
config.mode == "words" ||
|
||
config.mode == "custom"
|
||
) {
|
||
let wordsBound = 100;
|
||
if (config.showAllLines) {
|
||
if (config.mode === "custom") {
|
||
if (customText.isWordRandom) {
|
||
wordsBound = customText.word;
|
||
} else if (customText.isTimeRandom) {
|
||
wordsBound = 100;
|
||
} else {
|
||
wordsBound = customText.text.length;
|
||
}
|
||
} else if (config.mode != "time") {
|
||
wordsBound = config.words;
|
||
}
|
||
} else {
|
||
if (config.mode === "words" && config.words < wordsBound) {
|
||
wordsBound = config.words;
|
||
}
|
||
if (
|
||
config.mode == "custom" &&
|
||
customText.isWordRandom &&
|
||
customText.word < wordsBound
|
||
) {
|
||
wordsBound = customText.word;
|
||
}
|
||
if (
|
||
config.mode == "custom" &&
|
||
customText.isTimeRandom &&
|
||
customText.time < wordsBound
|
||
) {
|
||
wordsBound = 100;
|
||
}
|
||
if (
|
||
config.mode == "custom" &&
|
||
!customText.isWordRandom &&
|
||
customText.text.length < wordsBound
|
||
) {
|
||
wordsBound = customText.text.length;
|
||
}
|
||
}
|
||
|
||
if (
|
||
(config.mode === "custom" &&
|
||
customText.isWordRandom &&
|
||
customText.word == 0) ||
|
||
(config.mode === "custom" &&
|
||
customText.isTimeRandom &&
|
||
customText.time == 0)
|
||
) {
|
||
wordsBound = 100;
|
||
}
|
||
|
||
if (config.mode === "words" && config.words === 0) {
|
||
wordsBound = 100;
|
||
}
|
||
if (activeFunBox === "plus_one") {
|
||
wordsBound = 2;
|
||
}
|
||
let wordset = language.words;
|
||
if (config.mode == "custom") {
|
||
wordset = customText.text;
|
||
}
|
||
for (let i = 0; i < wordsBound; i++) {
|
||
let randomWord = wordset[Math.floor(Math.random() * wordset.length)];
|
||
const previousWord = wordsList[i - 1];
|
||
const previousWord2 = wordsList[i - 2];
|
||
if (
|
||
config.mode == "custom" &&
|
||
(customText.isWordRandom || customText.isTimeRandom)
|
||
) {
|
||
randomWord = wordset[Math.floor(Math.random() * wordset.length)];
|
||
} else if (config.mode == "custom" && !customText.isWordRandom) {
|
||
randomWord = customText.text[i];
|
||
} else {
|
||
while (
|
||
randomWord == previousWord ||
|
||
randomWord == previousWord2 ||
|
||
(!config.punctuation && randomWord == "I") ||
|
||
randomWord.indexOf(" ") > -1
|
||
) {
|
||
randomWord = wordset[Math.floor(Math.random() * wordset.length)];
|
||
}
|
||
}
|
||
|
||
if (activeFunBox === "rAnDoMcAsE") {
|
||
let randomcaseword = "";
|
||
for (let i = 0; i < randomWord.length; i++) {
|
||
if (i % 2 != 0) {
|
||
randomcaseword += randomWord[i].toUpperCase();
|
||
} else {
|
||
randomcaseword += randomWord[i];
|
||
}
|
||
}
|
||
randomWord = randomcaseword;
|
||
} else if (activeFunBox === "gibberish") {
|
||
randomWord = Misc.getGibberish();
|
||
} else if (activeFunBox === "58008") {
|
||
setToggleSettings(false, true);
|
||
randomWord = Misc.getNumbers(7);
|
||
} else if (activeFunBox === "specials") {
|
||
setToggleSettings(false, true);
|
||
randomWord = Misc.getSpecials();
|
||
} else if (activeFunBox === "ascii") {
|
||
setToggleSettings(false, true);
|
||
randomWord = Misc.getASCII();
|
||
}
|
||
|
||
if (config.punctuation) {
|
||
randomWord = punctuateWord(previousWord, randomWord, i, wordsBound);
|
||
}
|
||
if (config.numbers) {
|
||
if (Math.random() < 0.1) {
|
||
randomWord = Misc.getNumbers(4);
|
||
}
|
||
}
|
||
|
||
if (/\t/g.test(randomWord)) {
|
||
textHasTab = true;
|
||
}
|
||
|
||
wordsList.push(randomWord);
|
||
}
|
||
} else if (config.mode == "quote") {
|
||
let group = config.quoteLength;
|
||
|
||
if (config.quoteLength === -1) {
|
||
group = Math.floor(Math.random() * quotes.groups.length);
|
||
while (quotes.groups[group].length === 0) {
|
||
group = Math.floor(Math.random() * quotes.groups.length);
|
||
}
|
||
} else {
|
||
if (quotes.groups[group].length === 0) {
|
||
Notifications.add("No quotes found for selected quote length", 0);
|
||
return;
|
||
}
|
||
}
|
||
|
||
let rq =
|
||
quotes.groups[group][
|
||
Math.floor(Math.random() * quotes.groups[group].length)
|
||
];
|
||
if (randomQuote != null && rq.id === randomQuote.id) {
|
||
rq =
|
||
quotes.groups[group][
|
||
Math.floor(Math.random() * quotes.groups[group].length)
|
||
];
|
||
}
|
||
randomQuote = rq;
|
||
randomQuote.text = randomQuote.text.replace(/ +/gm, " ");
|
||
randomQuote.text = randomQuote.text.replace(/( *(\r\n|\r|\n) *)/g, "\n ");
|
||
let w = randomQuote.text.trim().split(" ");
|
||
for (let i = 0; i < w.length; i++) {
|
||
if (/\t/g.test(w[i])) {
|
||
textHasTab = true;
|
||
}
|
||
wordsList.push(w[i]);
|
||
}
|
||
}
|
||
//handle right-to-left languages
|
||
if (language.leftToRight) {
|
||
arrangeCharactersLeftToRight();
|
||
} else {
|
||
arrangeCharactersRightToLeft();
|
||
}
|
||
if (language.ligatures) {
|
||
$("#words").addClass("withLigatures");
|
||
} else {
|
||
$("#words").removeClass("withLigatures");
|
||
}
|
||
showWords();
|
||
}
|
||
|
||
function arrangeCharactersRightToLeft() {
|
||
$("#words").addClass("rightToLeftTest");
|
||
}
|
||
|
||
function arrangeCharactersLeftToRight() {
|
||
$("#words").removeClass("rightToLeftTest");
|
||
}
|
||
|
||
function setToggleSettings(state, nosave) {
|
||
setPunctuation(state, nosave);
|
||
setNumbers(state, nosave);
|
||
}
|
||
|
||
function emulateLayout(event) {
|
||
function emulatedLayoutShouldShiftKey(event, newKeyPreview) {
|
||
if (config.capsLockBackspace) return event.shiftKey;
|
||
const isCapsLockHeld = event.originalEvent.getModifierState("CapsLock");
|
||
if (isCapsLockHeld)
|
||
return Misc.isASCIILetter(newKeyPreview) !== event.shiftKey;
|
||
return event.shiftKey;
|
||
}
|
||
|
||
function replaceEventKey(event, keyCode) {
|
||
const newKey = String.fromCharCode(keyCode);
|
||
event.keyCode = keyCode;
|
||
event.charCode = keyCode;
|
||
event.which = keyCode;
|
||
event.key = newKey;
|
||
event.code = "Key" + newKey.toUpperCase();
|
||
}
|
||
|
||
let newEvent = event;
|
||
|
||
try {
|
||
if (config.layout === "default") {
|
||
//override the caps lock modifier for the default layout if needed
|
||
if (config.capsLockBackspace && Misc.isASCIILetter(newEvent.key)) {
|
||
replaceEventKey(
|
||
newEvent,
|
||
newEvent.shiftKey
|
||
? newEvent.key.toUpperCase().charCodeAt(0)
|
||
: newEvent.key.toLowerCase().charCodeAt(0)
|
||
);
|
||
}
|
||
return newEvent;
|
||
}
|
||
const keyEventCodes = [
|
||
"Backquote",
|
||
"Digit1",
|
||
"Digit2",
|
||
"Digit3",
|
||
"Digit4",
|
||
"Digit5",
|
||
"Digit6",
|
||
"Digit7",
|
||
"Digit8",
|
||
"Digit9",
|
||
"Digit0",
|
||
"Minus",
|
||
"Equal",
|
||
"KeyQ",
|
||
"KeyW",
|
||
"KeyE",
|
||
"KeyR",
|
||
"KeyT",
|
||
"KeyY",
|
||
"KeyU",
|
||
"KeyI",
|
||
"KeyO",
|
||
"KeyP",
|
||
"BracketLeft",
|
||
"BracketRight",
|
||
"Backslash",
|
||
"KeyA",
|
||
"KeyS",
|
||
"KeyD",
|
||
"KeyF",
|
||
"KeyG",
|
||
"KeyH",
|
||
"KeyJ",
|
||
"KeyK",
|
||
"KeyL",
|
||
"Semicolon",
|
||
"Quote",
|
||
"IntlBackslash",
|
||
"KeyZ",
|
||
"KeyX",
|
||
"KeyC",
|
||
"KeyV",
|
||
"KeyB",
|
||
"KeyN",
|
||
"KeyM",
|
||
"Comma",
|
||
"Period",
|
||
"Slash",
|
||
"Space",
|
||
];
|
||
const layoutMap = layouts[config.layout].keys;
|
||
|
||
let mapIndex;
|
||
for (let i = 0; i < keyEventCodes.length; i++) {
|
||
if (newEvent.code == keyEventCodes[i]) {
|
||
mapIndex = i;
|
||
}
|
||
}
|
||
const newKeyPreview = layoutMap[mapIndex][0];
|
||
const shift = emulatedLayoutShouldShiftKey(newEvent, newKeyPreview) ? 1 : 0;
|
||
const newKey = layoutMap[mapIndex][shift];
|
||
replaceEventKey(newEvent, newKey.charCodeAt(0));
|
||
} catch (e) {
|
||
return event;
|
||
}
|
||
return newEvent;
|
||
}
|
||
|
||
function punctuateWord(previousWord, currentWord, index, maxindex) {
|
||
let word = currentWord;
|
||
|
||
if (
|
||
(index == 0 ||
|
||
Misc.getLastChar(previousWord) == "." ||
|
||
Misc.getLastChar(previousWord) == "?" ||
|
||
Misc.getLastChar(previousWord) == "!") &&
|
||
config.language.split("_")[0] != "code"
|
||
) {
|
||
//always capitalise the first word or if there was a dot unless using a code alphabet
|
||
word = Misc.capitalizeFirstLetter(word);
|
||
} else if (
|
||
(Math.random() < 0.1 &&
|
||
Misc.getLastChar(previousWord) != "." &&
|
||
Misc.getLastChar(previousWord) != "," &&
|
||
index != maxindex - 2) ||
|
||
index == maxindex - 1
|
||
) {
|
||
let rand = Math.random();
|
||
if (rand <= 0.8) {
|
||
word += ".";
|
||
} else if (rand > 0.8 && rand < 0.9) {
|
||
if (config.language.split("_")[0] == "french") {
|
||
word = "?";
|
||
} else {
|
||
word += "?";
|
||
}
|
||
} else {
|
||
if (config.language.split("_")[0] == "french") {
|
||
word = "!";
|
||
} else {
|
||
word += "!";
|
||
}
|
||
}
|
||
} else if (
|
||
Math.random() < 0.01 &&
|
||
Misc.getLastChar(previousWord) != "," &&
|
||
Misc.getLastChar(previousWord) != "." &&
|
||
config.language.split("_")[0] !== "russian"
|
||
) {
|
||
word = `"${word}"`;
|
||
} else if (
|
||
Math.random() < 0.01 &&
|
||
Misc.getLastChar(previousWord) != "," &&
|
||
Misc.getLastChar(previousWord) != "." &&
|
||
config.language.split("_")[0] !== "russian"
|
||
) {
|
||
word = `'${word}'`;
|
||
} else if (
|
||
Math.random() < 0.01 &&
|
||
Misc.getLastChar(previousWord) != "," &&
|
||
Misc.getLastChar(previousWord) != "."
|
||
) {
|
||
if (config.language.split("_")[0] == "code") {
|
||
let r = Math.random();
|
||
if (r < 0.25) {
|
||
word = `(${word})`;
|
||
} else if (r < 0.5) {
|
||
word = `{${word}}`;
|
||
} else if (r < 0.75) {
|
||
word = `[${word}]`;
|
||
} else {
|
||
word = `<${word}>`;
|
||
}
|
||
} else {
|
||
word = `(${word})`;
|
||
}
|
||
} else if (Math.random() < 0.01) {
|
||
if (config.language.split("_")[0] == "french") {
|
||
word = ":";
|
||
} else {
|
||
word += ":";
|
||
}
|
||
} else if (
|
||
Math.random() < 0.01 &&
|
||
Misc.getLastChar(previousWord) != "," &&
|
||
Misc.getLastChar(previousWord) != "." &&
|
||
previousWord != "-"
|
||
) {
|
||
word = "-";
|
||
} else if (
|
||
Math.random() < 0.01 &&
|
||
Misc.getLastChar(previousWord) != "," &&
|
||
Misc.getLastChar(previousWord) != "." &&
|
||
Misc.getLastChar(previousWord) != ";"
|
||
) {
|
||
if (config.language.split("_")[0] == "french") {
|
||
word = ";";
|
||
} else {
|
||
word += ";";
|
||
}
|
||
} else if (Math.random() < 0.2 && Misc.getLastChar(previousWord) != ",") {
|
||
word += ",";
|
||
} else if (Math.random() < 0.25 && config.language.split("_")[0] == "code") {
|
||
let specials = ["{", "}", "[", "]", "(", ")", ";", "=", "%", "/"];
|
||
|
||
word = specials[Math.floor(Math.random() * 10)];
|
||
}
|
||
return word;
|
||
}
|
||
|
||
function addWord() {
|
||
let bound = 100;
|
||
if (activeFunBox === "plus_one") bound = 1;
|
||
if (
|
||
wordsList.length - inputHistory.length > bound ||
|
||
(config.mode === "words" &&
|
||
wordsList.length >= config.words &&
|
||
config.words > 0) ||
|
||
(config.mode === "custom" &&
|
||
customText.isWordRandom &&
|
||
wordsList.length >= customText.word &&
|
||
customText.word != 0) ||
|
||
(config.mode === "custom" &&
|
||
!customText.isWordRandom &&
|
||
wordsList.length >= customText.text.length)
|
||
)
|
||
return;
|
||
const language =
|
||
config.mode !== "custom"
|
||
? Misc.getCurrentLanguage()
|
||
: {
|
||
//borrow the direction of the current language
|
||
leftToRight: Misc.getCurrentLanguage().leftToRight,
|
||
words: customText.text,
|
||
};
|
||
const wordset = language.words;
|
||
let randomWord = wordset[Math.floor(Math.random() * wordset.length)];
|
||
const previousWord = wordsList[wordsList.length - 1];
|
||
const previousWordStripped = previousWord
|
||
.replace(/[.?!":\-,]/g, "")
|
||
.toLowerCase();
|
||
const previousWord2Stripped = wordsList[wordsList.length - 2]
|
||
.replace(/[.?!":\-,]/g, "")
|
||
.toLowerCase();
|
||
|
||
if (
|
||
config.mode === "custom" &&
|
||
customText.isWordRandom &&
|
||
wordset.length < 3
|
||
) {
|
||
randomWord = wordset[Math.floor(Math.random() * wordset.length)];
|
||
} else if (config.mode == "custom" && !customText.isWordRandom) {
|
||
randomWord = customText.text[wordsList.length];
|
||
} else {
|
||
while (
|
||
previousWordStripped == randomWord ||
|
||
previousWord2Stripped == randomWord ||
|
||
randomWord.indexOf(" ") > -1 ||
|
||
(!config.punctuation && randomWord == "I")
|
||
) {
|
||
randomWord = wordset[Math.floor(Math.random() * wordset.length)];
|
||
}
|
||
}
|
||
|
||
if (activeFunBox === "rAnDoMcAsE") {
|
||
let randomcaseword = "";
|
||
for (let i = 0; i < randomWord.length; i++) {
|
||
if (i % 2 != 0) {
|
||
randomcaseword += randomWord[i].toUpperCase();
|
||
} else {
|
||
randomcaseword += randomWord[i];
|
||
}
|
||
}
|
||
randomWord = randomcaseword;
|
||
} else if (activeFunBox === "gibberish") {
|
||
randomWord = Misc.getGibberish();
|
||
} else if (activeFunBox === "58008") {
|
||
randomWord = Misc.getNumbers(7);
|
||
} else if (activeFunBox === "specials") {
|
||
randomWord = Misc.getSpecials();
|
||
} else if (activeFunBox === "ascii") {
|
||
randomWord = Misc.getASCII();
|
||
}
|
||
|
||
if (config.punctuation && config.mode != "custom") {
|
||
randomWord = punctuateWord(previousWord, randomWord, wordsList.length, 0);
|
||
}
|
||
if (config.numbers && config.mode != "custom") {
|
||
if (Math.random() < 0.1) {
|
||
randomWord = Misc.getNumbers(4);
|
||
}
|
||
}
|
||
|
||
wordsList.push(randomWord);
|
||
|
||
let w = "<div class='word'>";
|
||
for (let c = 0; c < randomWord.length; c++) {
|
||
w += "<letter>" + randomWord.charAt(c) + "</letter>";
|
||
}
|
||
w += "</div>";
|
||
$("#words").append(w);
|
||
}
|
||
|
||
function showWords() {
|
||
$("#words").empty();
|
||
|
||
let wordsHTML = "";
|
||
let newlineafter = false;
|
||
for (let i = 0; i < wordsList.length; i++) {
|
||
newlineafter = false;
|
||
wordsHTML += `<div class='word'>`;
|
||
for (let c = 0; c < wordsList[i].length; c++) {
|
||
if (wordsList[i].charAt(c) === "\t") {
|
||
wordsHTML += `<letter class='tabChar'><i class="fas fa-long-arrow-alt-right"></i></letter>`;
|
||
} else if (wordsList[i].charAt(c) === "\n") {
|
||
newlineafter = true;
|
||
wordsHTML += `<letter class='nlChar'><i class="fas fa-angle-down"></i></letter>`;
|
||
} else {
|
||
wordsHTML += "<letter>" + wordsList[i].charAt(c) + "</letter>";
|
||
}
|
||
}
|
||
wordsHTML += "</div>";
|
||
if (newlineafter) wordsHTML += "<div class='newline'></div>";
|
||
}
|
||
$("#words").html(wordsHTML);
|
||
|
||
$("#wordsWrapper").removeClass("hidden");
|
||
const wordHeight = $(document.querySelector(".word")).outerHeight(true);
|
||
const wordsHeight = $(document.querySelector("#words")).outerHeight(true);
|
||
if (
|
||
config.showAllLines &&
|
||
config.mode != "time" &&
|
||
!(customText.isWordRandom && customText.word == 0) &&
|
||
!customText.isTimeRandom
|
||
) {
|
||
$("#words").css("height", "auto");
|
||
$("#wordsWrapper").css("height", "auto");
|
||
let nh = wordHeight * 3;
|
||
|
||
if (nh > wordsHeight) {
|
||
nh = wordsHeight;
|
||
}
|
||
$(".outOfFocusWarning").css("line-height", nh + "px");
|
||
} else {
|
||
$("#words")
|
||
.css("height", wordHeight * 4 + "px")
|
||
.css("overflow", "hidden");
|
||
$("#wordsWrapper")
|
||
.css("height", wordHeight * 3 + "px")
|
||
.css("overflow", "hidden");
|
||
$(".outOfFocusWarning").css("line-height", wordHeight * 3 + "px");
|
||
}
|
||
|
||
if (config.keymapMode === "next") {
|
||
updateHighlightedKeymapKey();
|
||
}
|
||
|
||
updateActiveElement();
|
||
updateCaretPosition();
|
||
}
|
||
|
||
(function (history) {
|
||
var pushState = history.pushState;
|
||
history.pushState = function (state) {
|
||
if (activeFunBox === "memory" && state !== "/") {
|
||
memoryFunboxInterval = clearInterval(memoryFunboxInterval);
|
||
memoryFunboxTimer = null;
|
||
}
|
||
return pushState.apply(history, arguments);
|
||
};
|
||
})(window.history);
|
||
|
||
function updateActiveElement() {
|
||
let active = document.querySelector("#words .active");
|
||
if (active !== null) {
|
||
if (config.highlightMode == "word") {
|
||
active.querySelectorAll("letter").forEach((e) => {
|
||
e.classList.remove("correct");
|
||
});
|
||
}
|
||
active.classList.remove("active");
|
||
}
|
||
try {
|
||
let activeWord = document.querySelectorAll("#words .word")[
|
||
currentWordElementIndex
|
||
];
|
||
activeWord.classList.add("active");
|
||
activeWord.classList.remove("error");
|
||
activeWordTop = document.querySelector("#words .active").offsetTop;
|
||
if (config.highlightMode == "word") {
|
||
activeWord.querySelectorAll("letter").forEach((e) => {
|
||
e.classList.add("correct");
|
||
});
|
||
}
|
||
} catch (e) {}
|
||
toggleScriptFunbox(wordsList[currentWordIndex]);
|
||
}
|
||
|
||
function updateWordElement(showError) {
|
||
let input = currentInput;
|
||
let wordAtIndex;
|
||
let currentWord;
|
||
wordAtIndex = document.querySelector("#words .word.active");
|
||
currentWord = wordsList[currentWordIndex];
|
||
let ret = "";
|
||
|
||
if (config.highlightMode == "word") {
|
||
//only for word highlight
|
||
|
||
let correctSoFar = false;
|
||
if (currentWord.slice(0, input.length) == input) {
|
||
// this is when input so far is correct
|
||
correctSoFar = true;
|
||
}
|
||
let classString = correctSoFar ? "correct" : "incorrect";
|
||
if (config.blindMode) {
|
||
classString = "correct";
|
||
}
|
||
|
||
//show letters in the current word
|
||
for (let i = 0; i < currentWord.length; i++) {
|
||
ret += `<letter class="${classString}">` + currentWord[i] + `</letter>`;
|
||
}
|
||
|
||
//show any extra letters if hide extra letters is disabled
|
||
if (currentInput.length > currentWord.length && !config.hideExtraLetters) {
|
||
for (let i = currentWord.length; i < currentInput.length; i++) {
|
||
let letter = currentInput[i];
|
||
if (letter == " ") {
|
||
letter = "_";
|
||
}
|
||
ret += `<letter class="${classString}">${letter}</letter>`;
|
||
}
|
||
}
|
||
} else {
|
||
for (let i = 0; i < input.length; i++) {
|
||
let charCorrect;
|
||
if (currentWord[i] == input[i]) {
|
||
charCorrect = true;
|
||
} else {
|
||
charCorrect = false;
|
||
}
|
||
|
||
try {
|
||
if (config.language === "russian" && charCorrect === false) {
|
||
if (
|
||
(currentWord[i].toLowerCase() === "е" &&
|
||
input[i].toLowerCase() === "ё") ||
|
||
(currentWord[i].toLowerCase() === "ё" &&
|
||
input[i].toLowerCase() === "е")
|
||
) {
|
||
charCorrect = true;
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
|
||
let currentLetter = currentWord[i];
|
||
let tabChar = "";
|
||
let nlChar = "";
|
||
if (currentLetter === "\t") {
|
||
tabChar = "tabChar";
|
||
currentLetter = `<i class="fas fa-long-arrow-alt-right"></i>`;
|
||
} else if (currentLetter === "\n") {
|
||
nlChar = "nlChar";
|
||
currentLetter = `<i class="fas fa-angle-down"></i>`;
|
||
}
|
||
|
||
if (charCorrect) {
|
||
ret += `<letter class="correct ${tabChar}${nlChar}">${currentLetter}</letter>`;
|
||
} else {
|
||
// if (config.difficulty == "master") {
|
||
// if (!resultVisible) {
|
||
// failTest();
|
||
// }
|
||
// }
|
||
if (!showError) {
|
||
if (currentLetter == undefined) {
|
||
} else {
|
||
ret += `<letter class="correct ${tabChar}${nlChar}">${currentLetter}</letter>`;
|
||
}
|
||
} else {
|
||
if (currentLetter == undefined) {
|
||
if (!config.hideExtraLetters) {
|
||
let letter = input[i];
|
||
if (letter == " " || letter == "\t" || letter == "\n") {
|
||
letter = "_";
|
||
}
|
||
ret += `<letter class="incorrect extra ${tabChar}${nlChar}">${letter}</letter>`;
|
||
}
|
||
} else {
|
||
ret +=
|
||
`<letter class="incorrect ${tabChar}${nlChar}">` +
|
||
currentLetter +
|
||
(config.indicateTypos ? `<hint>${input[i]}</hint>` : "") +
|
||
"</letter>";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (input.length < currentWord.length) {
|
||
for (let i = input.length; i < currentWord.length; i++) {
|
||
if (currentWord[i] === "\t") {
|
||
ret += `<letter class='tabChar'><i class="fas fa-long-arrow-alt-right"></i></letter>`;
|
||
} else if (currentWord[i] === "\n") {
|
||
ret += `<letter class='nlChar'><i class="fas fa-angle-down"></i></letter>`;
|
||
} else {
|
||
ret += "<letter>" + currentWord[i] + "</letter>";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
wordAtIndex.innerHTML = ret;
|
||
}
|
||
|
||
function highlightBadWord(index, showError) {
|
||
if (!showError) return;
|
||
$($("#words .word")[index]).addClass("error");
|
||
}
|
||
|
||
function showTimer() {
|
||
let op = config.showTimerProgress ? config.timerOpacity : 0;
|
||
if (config.timerStyle === "bar") {
|
||
$("#timerWrapper").stop(true, true).removeClass("hidden").animate(
|
||
{
|
||
opacity: op,
|
||
},
|
||
125
|
||
);
|
||
} else if (config.timerStyle === "text") {
|
||
$("#timerNumber")
|
||
.stop(true, true)
|
||
.removeClass("hidden")
|
||
.css("opacity", 0)
|
||
.animate(
|
||
{
|
||
opacity: op,
|
||
},
|
||
125
|
||
);
|
||
} else if (config.timerStyle === "mini") {
|
||
if (op > 0) {
|
||
$("#miniTimerAndLiveWpm .time")
|
||
.stop(true, true)
|
||
.removeClass("hidden")
|
||
.animate(
|
||
{
|
||
opacity: op,
|
||
},
|
||
125
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
function hideTimer() {
|
||
$("#timerWrapper").stop(true, true).animate(
|
||
{
|
||
opacity: 0,
|
||
},
|
||
125
|
||
);
|
||
$("#miniTimerAndLiveWpm .time")
|
||
.stop(true, true)
|
||
.animate(
|
||
{
|
||
opacity: 0,
|
||
},
|
||
125,
|
||
() => {
|
||
$("#miniTimerAndLiveWpm .time").addClass("hidden");
|
||
}
|
||
);
|
||
$("#timerNumber").stop(true, true).animate(
|
||
{
|
||
opacity: 0,
|
||
},
|
||
125
|
||
);
|
||
}
|
||
|
||
function restartTimer() {
|
||
if (config.timerStyle === "bar") {
|
||
if (config.mode === "time") {
|
||
$("#timer").stop(true, true).animate(
|
||
{
|
||
width: "100vw",
|
||
},
|
||
0
|
||
);
|
||
} else if (config.mode === "words" || config.mode === "custom") {
|
||
$("#timer").stop(true, true).animate(
|
||
{
|
||
width: "0vw",
|
||
},
|
||
0
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateTimer() {
|
||
if (!config.showTimerProgress) return;
|
||
if (
|
||
config.mode === "time" ||
|
||
(config.mode === "custom" && customText.isTimeRandom)
|
||
) {
|
||
let maxtime = config.time;
|
||
if (config.mode === "custom" && customText.isTimeRandom) {
|
||
maxtime = customText.time;
|
||
}
|
||
if (config.timerStyle === "bar") {
|
||
let percent = 100 - ((time + 1) / maxtime) * 100;
|
||
$("#timer")
|
||
.stop(true, true)
|
||
.animate(
|
||
{
|
||
width: percent + "vw",
|
||
},
|
||
1000,
|
||
"linear"
|
||
);
|
||
} else if (config.timerStyle === "text") {
|
||
let displayTime = Misc.secondsToString(maxtime - time);
|
||
if (maxtime === 0) {
|
||
displayTime = Misc.secondsToString(time);
|
||
}
|
||
$("#timerNumber").html("<div>" + displayTime + "</div>");
|
||
} else if (config.timerStyle === "mini") {
|
||
let displayTime = Misc.secondsToString(maxtime - time);
|
||
if (maxtime === 0) {
|
||
displayTime = Misc.secondsToString(time);
|
||
}
|
||
$("#miniTimerAndLiveWpm .time").html(displayTime);
|
||
}
|
||
} else if (
|
||
config.mode === "words" ||
|
||
config.mode === "custom" ||
|
||
config.mode === "quote"
|
||
) {
|
||
if (config.timerStyle === "bar") {
|
||
let outof = wordsList.length;
|
||
if (config.mode === "words") {
|
||
outof = config.words;
|
||
}
|
||
if (config.mode === "custom") {
|
||
if (customText.isWordRandom) {
|
||
outof = customText.word;
|
||
} else {
|
||
outof = customText.text.length;
|
||
}
|
||
}
|
||
let percent = Math.floor(((currentWordIndex + 1) / outof) * 100);
|
||
$("#timer")
|
||
.stop(true, true)
|
||
.animate(
|
||
{
|
||
width: percent + "vw",
|
||
},
|
||
250
|
||
);
|
||
} else if (config.timerStyle === "text") {
|
||
let outof = wordsList.length;
|
||
if (config.mode === "words") {
|
||
outof = config.words;
|
||
}
|
||
if (config.mode === "custom") {
|
||
if (customText.isWordRandom) {
|
||
outof = customText.word;
|
||
} else {
|
||
outof = customText.text.length;
|
||
}
|
||
}
|
||
if (outof === 0) {
|
||
$("#timerNumber").html("<div>" + `${inputHistory.length}` + "</div>");
|
||
} else {
|
||
$("#timerNumber").html(
|
||
"<div>" + `${inputHistory.length}/${outof}` + "</div>"
|
||
);
|
||
}
|
||
} else if (config.timerStyle === "mini") {
|
||
let outof = wordsList.length;
|
||
if (config.mode === "words") {
|
||
outof = config.words;
|
||
}
|
||
if (config.mode === "custom") {
|
||
if (customText.isWordRandom) {
|
||
outof = customText.word;
|
||
} else {
|
||
outof = customText.text.length;
|
||
}
|
||
}
|
||
if (config.words === 0) {
|
||
$("#miniTimerAndLiveWpm .time").html(`${inputHistory.length}`);
|
||
} else {
|
||
$("#miniTimerAndLiveWpm .time").html(`${inputHistory.length}/${outof}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function hideCaret() {
|
||
$("#caret").addClass("hidden");
|
||
}
|
||
|
||
function showCaret() {
|
||
if ($("#result").hasClass("hidden")) {
|
||
updateCaretPosition();
|
||
$("#caret").removeClass("hidden");
|
||
startCaretAnimation();
|
||
}
|
||
}
|
||
|
||
function stopCaretAnimation() {
|
||
if (caretAnimating === true) {
|
||
$("#caret").css("animation-name", "none");
|
||
$("#caret").css("opacity", "1");
|
||
caretAnimating = false;
|
||
}
|
||
}
|
||
|
||
function startCaretAnimation() {
|
||
if (caretAnimating === false) {
|
||
if (config.smoothCaret) {
|
||
$("#caret").css("animation-name", "caretFlashSmooth");
|
||
} else {
|
||
$("#caret").css("animation-name", "caretFlashHard");
|
||
}
|
||
caretAnimating = true;
|
||
}
|
||
}
|
||
|
||
function hideKeymap() {
|
||
$(".keymap").addClass("hidden");
|
||
// $("#liveWpm").removeClass("lower");
|
||
}
|
||
|
||
function showKeymap() {
|
||
$(".keymap").removeClass("hidden");
|
||
// $("#liveWpm").addClass("lower");
|
||
}
|
||
|
||
function flashPressedKeymapKey(key, correct) {
|
||
if (key == undefined) return;
|
||
switch (key) {
|
||
case "\\":
|
||
case "|":
|
||
key = "#KeyBackslash";
|
||
break;
|
||
case "}":
|
||
case "]":
|
||
key = "#KeyRightBracket";
|
||
break;
|
||
case "{":
|
||
case "[":
|
||
key = "#KeyLeftBracket";
|
||
break;
|
||
case '"':
|
||
case "'":
|
||
key = "#KeyQuote";
|
||
break;
|
||
case ":":
|
||
case ";":
|
||
key = "#KeySemicolon";
|
||
break;
|
||
case "<":
|
||
case ",":
|
||
key = "#KeyComma";
|
||
break;
|
||
case ">":
|
||
case ".":
|
||
key = "#KeyPeriod";
|
||
break;
|
||
case "?":
|
||
case "/":
|
||
key = "#KeySlash";
|
||
break;
|
||
case "" || "Space":
|
||
key = "#KeySpace";
|
||
break;
|
||
default:
|
||
key = `#Key${key.toUpperCase()}`;
|
||
}
|
||
|
||
if (key == "#KeySpace") {
|
||
key = ".key-split-space";
|
||
}
|
||
|
||
try {
|
||
if (correct || config.blindMode) {
|
||
$(key)
|
||
.stop(true, true)
|
||
.css({
|
||
color: themeColors.bg,
|
||
backgroundColor: themeColors.main,
|
||
borderColor: themeColors.main,
|
||
})
|
||
.animate(
|
||
{
|
||
color: themeColors.sub,
|
||
backgroundColor: "transparent",
|
||
borderColor: themeColors.sub,
|
||
},
|
||
500,
|
||
"easeOutExpo"
|
||
);
|
||
} else {
|
||
$(key)
|
||
.stop(true, true)
|
||
.css({
|
||
color: themeColors.bg,
|
||
backgroundColor: themeColors.error,
|
||
borderColor: themeColors.error,
|
||
})
|
||
.animate(
|
||
{
|
||
color: themeColors.sub,
|
||
backgroundColor: "transparent",
|
||
borderColor: themeColors.sub,
|
||
},
|
||
500,
|
||
"easeOutExpo"
|
||
);
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
|
||
function updateHighlightedKeymapKey() {
|
||
try {
|
||
if ($(".active-key") != undefined) {
|
||
$(".active-key").removeClass("active-key");
|
||
}
|
||
|
||
var currentKey = wordsList[currentWordIndex]
|
||
.substring(currentInput.length, currentInput.length + 1)
|
||
.toString()
|
||
.toUpperCase();
|
||
|
||
let highlightKey;
|
||
switch (currentKey) {
|
||
case "\\":
|
||
case "|":
|
||
highlightKey = "#KeyBackslash";
|
||
break;
|
||
case "}":
|
||
case "]":
|
||
highlightKey = "#KeyRightBracket";
|
||
break;
|
||
case "{":
|
||
case "[":
|
||
highlightKey = "#KeyLeftBracket";
|
||
break;
|
||
case '"':
|
||
case "'":
|
||
highlightKey = "#KeyQuote";
|
||
break;
|
||
case ":":
|
||
case ";":
|
||
highlightKey = "#KeySemicolon";
|
||
break;
|
||
case "<":
|
||
case ",":
|
||
highlightKey = "#KeyComma";
|
||
break;
|
||
case ">":
|
||
case ".":
|
||
highlightKey = "#KeyPeriod";
|
||
break;
|
||
case "?":
|
||
case "/":
|
||
highlightKey = "#KeySlash";
|
||
break;
|
||
case "":
|
||
highlightKey = "#KeySpace";
|
||
break;
|
||
default:
|
||
highlightKey = `#Key${currentKey}`;
|
||
}
|
||
|
||
$(highlightKey).addClass("active-key");
|
||
if (highlightKey === "#KeySpace") {
|
||
$("#KeySpace2").addClass("active-key");
|
||
}
|
||
} catch (e) {
|
||
console.log("could not update highlighted keymap key: " + e.message);
|
||
}
|
||
}
|
||
|
||
function updateCaretPosition() {
|
||
if ($("#wordsWrapper").hasClass("hidden")) return;
|
||
if ($("#caret").hasClass("off")) {
|
||
return;
|
||
}
|
||
|
||
let caret = $("#caret");
|
||
|
||
let inputLen = currentInput.length;
|
||
let currentLetterIndex = inputLen - 1;
|
||
if (currentLetterIndex == -1) {
|
||
currentLetterIndex = 0;
|
||
}
|
||
try {
|
||
let currentWordNodeList = document
|
||
.querySelector("#words .active")
|
||
.querySelectorAll("letter");
|
||
let currentLetter = currentWordNodeList[currentLetterIndex];
|
||
if (inputLen > currentWordNodeList.length) {
|
||
currentLetter = currentWordNodeList[currentWordNodeList.length - 1];
|
||
}
|
||
|
||
if ($(currentLetter).length == 0) return;
|
||
const isLanguageLeftToRight = Misc.getCurrentLanguage().leftToRight;
|
||
let currentLetterPosLeft = isLanguageLeftToRight
|
||
? currentLetter.offsetLeft
|
||
: currentLetter.offsetLeft + $(currentLetter).width();
|
||
let currentLetterPosTop = currentLetter.offsetTop;
|
||
let letterHeight = $(currentLetter).height();
|
||
let newTop = 0;
|
||
let newLeft = 0;
|
||
|
||
newTop = currentLetterPosTop - Math.round(letterHeight / 5);
|
||
if (inputLen == 0) {
|
||
newLeft = isLanguageLeftToRight
|
||
? currentLetterPosLeft - caret.width() / 2
|
||
: currentLetterPosLeft + caret.width() / 2;
|
||
} else {
|
||
newLeft = isLanguageLeftToRight
|
||
? currentLetterPosLeft + $(currentLetter).width() - caret.width() / 2
|
||
: currentLetterPosLeft - $(currentLetter).width() + caret.width() / 2;
|
||
}
|
||
|
||
let smoothlinescroll = $("#words .smoothScroller").height();
|
||
if (smoothlinescroll === undefined) smoothlinescroll = 0;
|
||
|
||
if (config.smoothCaret) {
|
||
caret.stop(true, false).animate(
|
||
{
|
||
top: newTop - smoothlinescroll,
|
||
left: newLeft,
|
||
},
|
||
100
|
||
);
|
||
} else {
|
||
caret.stop(true, true).animate(
|
||
{
|
||
top: newTop - smoothlinescroll,
|
||
left: newLeft,
|
||
},
|
||
0
|
||
);
|
||
}
|
||
|
||
if (config.showAllLines) {
|
||
let browserHeight = window.innerHeight;
|
||
let middlePos = browserHeight / 2 - $("#caret").outerHeight() / 2;
|
||
let contentHeight = document.body.scrollHeight;
|
||
|
||
if (newTop >= middlePos && contentHeight > browserHeight) {
|
||
window.scrollTo({
|
||
left: 0,
|
||
top: newTop - middlePos,
|
||
behavior: "smooth",
|
||
});
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log("could not move caret: " + e.message);
|
||
}
|
||
}
|
||
|
||
function countChars() {
|
||
let correctWordChars = 0;
|
||
let correctChars = 0;
|
||
let incorrectChars = 0;
|
||
let extraChars = 0;
|
||
let missedChars = 0;
|
||
let spaces = 0;
|
||
let correctspaces = 0;
|
||
for (let i = 0; i < inputHistory.length; i++) {
|
||
let word = wordsList[i];
|
||
if (inputHistory[i] === "") {
|
||
//last word that was not started
|
||
continue;
|
||
}
|
||
if (inputHistory[i] == word) {
|
||
//the word is correct
|
||
correctWordChars += word.length;
|
||
correctChars += word.length;
|
||
if (
|
||
i < inputHistory.length - 1 &&
|
||
Misc.getLastChar(inputHistory[i]) !== "\n"
|
||
) {
|
||
correctspaces++;
|
||
}
|
||
} else if (inputHistory[i].length >= word.length) {
|
||
//too many chars
|
||
for (let c = 0; c < inputHistory[i].length; c++) {
|
||
if (c < word.length) {
|
||
//on char that still has a word list pair
|
||
if (inputHistory[i][c] == word[c]) {
|
||
correctChars++;
|
||
} else {
|
||
incorrectChars++;
|
||
}
|
||
} else {
|
||
//on char that is extra
|
||
extraChars++;
|
||
}
|
||
}
|
||
} else {
|
||
//not enough chars
|
||
let toAdd = {
|
||
correct: 0,
|
||
incorrect: 0,
|
||
missed: 0,
|
||
};
|
||
for (let c = 0; c < word.length; c++) {
|
||
if (c < inputHistory[i].length) {
|
||
//on char that still has a word list pair
|
||
if (inputHistory[i][c] == word[c]) {
|
||
toAdd.correct++;
|
||
} else {
|
||
toAdd.incorrect++;
|
||
}
|
||
} else {
|
||
//on char that is extra
|
||
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++;
|
||
}
|
||
}
|
||
if (activeFunBox === "nospace") {
|
||
spaces = 0;
|
||
correctspaces = 0;
|
||
}
|
||
return {
|
||
spaces: spaces,
|
||
correctWordChars: correctWordChars,
|
||
allCorrectChars: correctChars,
|
||
incorrectChars: incorrectChars,
|
||
extraChars: extraChars,
|
||
missedChars: missedChars,
|
||
correctSpaces: correctspaces,
|
||
};
|
||
}
|
||
|
||
function calculateStats() {
|
||
let testSeconds = (testEnd - testStart) / 1000;
|
||
let chars = countChars();
|
||
let wpm = Misc.roundTo2(
|
||
((chars.correctWordChars + chars.correctSpaces) * (60 / testSeconds)) / 5
|
||
);
|
||
let wpmraw = Misc.roundTo2(
|
||
((chars.allCorrectChars +
|
||
chars.spaces +
|
||
chars.incorrectChars +
|
||
chars.extraChars) *
|
||
(60 / testSeconds)) /
|
||
5
|
||
);
|
||
let acc = Misc.roundTo2(
|
||
(accuracyStats.correct /
|
||
(accuracyStats.correct + accuracyStats.incorrect)) *
|
||
100
|
||
);
|
||
return {
|
||
wpm: isNaN(wpm) ? 0 : wpm,
|
||
wpmRaw: isNaN(wpmraw) ? 0 : wpmraw,
|
||
acc: acc,
|
||
correctChars: chars.correctWordChars,
|
||
incorrectChars: chars.incorrectChars,
|
||
missedChars: chars.missedChars,
|
||
extraChars: chars.extraChars,
|
||
allChars:
|
||
chars.allCorrectChars +
|
||
chars.spaces +
|
||
chars.incorrectChars +
|
||
chars.extraChars,
|
||
time: testSeconds,
|
||
spaces: chars.spaces,
|
||
correctSpaces: chars.correctSpaces,
|
||
};
|
||
}
|
||
|
||
function hideCrown() {
|
||
$("#result .stats .wpm .crown").css("opacity", 0).addClass("hidden");
|
||
}
|
||
|
||
function showCrown() {
|
||
$("#result .stats .wpm .crown")
|
||
.removeClass("hidden")
|
||
.css("opacity", "0")
|
||
.animate(
|
||
{
|
||
opacity: 1,
|
||
},
|
||
250,
|
||
"easeOutCubic"
|
||
);
|
||
}
|
||
|
||
function failTest() {
|
||
inputHistory.push(currentInput);
|
||
correctedHistory.push(currentCorrected);
|
||
lastSecondNotRound = true;
|
||
showResult(true);
|
||
let testNow = performance.now();
|
||
let testSeconds = Misc.roundTo2((testNow - testStart) / 1000);
|
||
let afkseconds = keypressPerSecond.filter((x) => x.count == 0 && x.mod == 0)
|
||
.length;
|
||
incompleteTestSeconds += testSeconds - afkseconds;
|
||
restartCount++;
|
||
}
|
||
|
||
let resultCalculating = false;
|
||
function showResult(difficultyFailed = false) {
|
||
resultCalculating = true;
|
||
resultVisible = true;
|
||
testEnd = performance.now();
|
||
testActive = false;
|
||
setFocus(false);
|
||
hideCaret();
|
||
hideLiveWpm();
|
||
hideLiveAcc();
|
||
hideTimer();
|
||
hideKeymap();
|
||
testInvalid = false;
|
||
let stats = calculateStats();
|
||
if (stats === undefined) {
|
||
stats = {
|
||
wpm: 0,
|
||
wpmRaw: 0,
|
||
acc: 0,
|
||
correctChars: 0,
|
||
incorrectChars: 0,
|
||
missedChars: 0,
|
||
extraChars: 0,
|
||
time: 0,
|
||
spaces: 0,
|
||
correctSpaces: 0,
|
||
};
|
||
}
|
||
let inf = false;
|
||
if (stats.wpm >= 1000) {
|
||
inf = true;
|
||
}
|
||
clearTimeout(timer);
|
||
let testtime = stats.time;
|
||
let afkseconds = keypressPerSecond.filter((x) => x.count == 0 && x.mod == 0)
|
||
.length;
|
||
let afkSecondsPercent = Misc.roundTo2((afkseconds / testtime) * 100);
|
||
|
||
wpmOverTimeChart.options.annotation.annotations = [];
|
||
|
||
$("#result #resultWordsHistory").addClass("hidden");
|
||
|
||
if (config.alwaysShowDecimalPlaces) {
|
||
if (config.alwaysShowCPM == false) {
|
||
$("#result .stats .wpm .top .text").text("wpm");
|
||
if (inf) {
|
||
$("#result .stats .wpm .bottom").text("Infinite");
|
||
} else {
|
||
$("#result .stats .wpm .bottom").text(Misc.roundTo2(stats.wpm));
|
||
}
|
||
$("#result .stats .raw .bottom").text(Misc.roundTo2(stats.wpmRaw));
|
||
$("#result .stats .wpm .bottom").attr(
|
||
"aria-label",
|
||
Misc.roundTo2(stats.wpm * 5) + " cpm"
|
||
);
|
||
} else {
|
||
$("#result .stats .wpm .top .text").text("cpm");
|
||
if (inf) {
|
||
$("#result .stats .wpm .bottom").text("Infinite");
|
||
} else {
|
||
$("#result .stats .wpm .bottom").text(Misc.roundTo2(stats.wpm * 5));
|
||
}
|
||
$("#result .stats .raw .bottom").text(Misc.roundTo2(stats.wpmRaw * 5));
|
||
$("#result .stats .wpm .bottom").attr(
|
||
"aria-label",
|
||
Misc.roundTo2(stats.wpm) + " wpm"
|
||
);
|
||
}
|
||
|
||
$("#result .stats .acc .bottom").text(Misc.roundTo2(stats.acc) + "%");
|
||
let time = Misc.roundTo2(testtime) + "s";
|
||
if (testtime > 61) {
|
||
time = Misc.secondsToString(Misc.roundTo2(testtime));
|
||
}
|
||
$("#result .stats .time .bottom .text").text(time);
|
||
$("#result .stats .raw .bottom").removeAttr("aria-label");
|
||
$("#result .stats .acc .bottom").removeAttr("aria-label");
|
||
$("#result .stats .time .bottom").attr(
|
||
"aria-label",
|
||
`${afkseconds}s afk ${afkSecondsPercent}%`
|
||
);
|
||
} else {
|
||
//not showing decimal places
|
||
if (config.alwaysShowCPM == false) {
|
||
$("#result .stats .wpm .top .text").text("wpm");
|
||
$("#result .stats .wpm .bottom").attr(
|
||
"aria-label",
|
||
stats.wpm + ` (${Misc.roundTo2(stats.wpm * 5)} cpm)`
|
||
);
|
||
if (inf) {
|
||
$("#result .stats .wpm .bottom").text("Infinite");
|
||
} else {
|
||
$("#result .stats .wpm .bottom").text(Math.round(stats.wpm));
|
||
}
|
||
$("#result .stats .raw .bottom").text(Math.round(stats.wpmRaw));
|
||
$("#result .stats .raw .bottom").attr("aria-label", stats.wpmRaw);
|
||
} else {
|
||
$("#result .stats .wpm .top .text").text("cpm");
|
||
$("#result .stats .wpm .bottom").attr(
|
||
"aria-label",
|
||
Misc.roundTo2(stats.wpm * 5) + ` (${Misc.roundTo2(stats.wpm)} wpm)`
|
||
);
|
||
if (inf) {
|
||
$("#result .stats .wpm .bottom").text("Infinite");
|
||
} else {
|
||
$("#result .stats .wpm .bottom").text(Math.round(stats.wpm * 5));
|
||
}
|
||
$("#result .stats .raw .bottom").text(Math.round(stats.wpmRaw * 5));
|
||
$("#result .stats .raw .bottom").attr("aria-label", stats.wpmRaw * 5);
|
||
}
|
||
|
||
$("#result .stats .acc .bottom").text(Math.floor(stats.acc) + "%");
|
||
$("#result .stats .acc .bottom").attr("aria-label", stats.acc + "%");
|
||
let time = Math.round(testtime) + "s";
|
||
if (testtime > 61) {
|
||
time = Misc.secondsToString(Math.round(testtime));
|
||
}
|
||
$("#result .stats .time .bottom .text").text(time);
|
||
$("#result .stats .time .bottom").attr(
|
||
"aria-label",
|
||
`${Misc.roundTo2(testtime)}s (${afkseconds}s afk ${afkSecondsPercent}%)`
|
||
);
|
||
}
|
||
$("#result .stats .time .bottom .afk").text("");
|
||
if (afkSecondsPercent > 0) {
|
||
$("#result .stats .time .bottom .afk").text(afkSecondsPercent + "% afk");
|
||
}
|
||
$("#result .stats .key .bottom").text(testtime + "s");
|
||
$("#words").removeClass("blurred");
|
||
$(".outOfFocusWarning").addClass("hidden");
|
||
$("#result .stats .key .bottom").text(
|
||
stats.correctChars +
|
||
stats.correctSpaces +
|
||
"/" +
|
||
stats.incorrectChars +
|
||
"/" +
|
||
stats.extraChars +
|
||
"/" +
|
||
stats.missedChars
|
||
);
|
||
|
||
setTimeout(function () {
|
||
$("#resultExtraButtons").removeClass("hidden").css("opacity", 0).animate(
|
||
{
|
||
opacity: 1,
|
||
},
|
||
125
|
||
);
|
||
}, 125);
|
||
|
||
$("#testModesNotice").addClass("hidden");
|
||
|
||
$("#result .stats .leaderboards .bottom").text("");
|
||
$("#result .stats .leaderboards").addClass("hidden");
|
||
|
||
let mode2 = "";
|
||
if (config.mode === "time") {
|
||
mode2 = config.time;
|
||
} else if (config.mode === "words") {
|
||
mode2 = config.words;
|
||
} else if (config.mode === "custom") {
|
||
mode2 = "custom";
|
||
} else if (config.mode === "quote") {
|
||
mode2 = randomQuote.id;
|
||
}
|
||
|
||
if (lastSecondNotRound) {
|
||
let wpmAndRaw = liveWpmAndRaw();
|
||
wpmHistory.push(wpmAndRaw.wpm);
|
||
rawHistory.push(wpmAndRaw.raw);
|
||
keypressPerSecond.push(currentKeypress);
|
||
currentKeypress = {
|
||
mod: 0,
|
||
count: 0,
|
||
words: [],
|
||
};
|
||
errorsPerSecond.push(currentError);
|
||
currentError = {
|
||
count: 0,
|
||
words: [],
|
||
};
|
||
}
|
||
|
||
let labels = [];
|
||
for (let i = 1; i <= wpmHistory.length; i++) {
|
||
if (lastSecondNotRound && i === wpmHistory.length) {
|
||
labels.push(Misc.roundTo2(testtime).toString());
|
||
} else {
|
||
labels.push(i.toString());
|
||
}
|
||
}
|
||
|
||
if (themeColors.main == "") {
|
||
refreshThemeColorObject();
|
||
}
|
||
|
||
wpmOverTimeChart.options.scales.xAxes[0].ticks.minor.fontColor =
|
||
themeColors.sub;
|
||
wpmOverTimeChart.options.scales.xAxes[0].scaleLabel.fontColor =
|
||
themeColors.sub;
|
||
wpmOverTimeChart.options.scales.yAxes[0].ticks.minor.fontColor =
|
||
themeColors.sub;
|
||
wpmOverTimeChart.options.scales.yAxes[2].ticks.minor.fontColor =
|
||
themeColors.sub;
|
||
wpmOverTimeChart.options.scales.yAxes[0].scaleLabel.fontColor =
|
||
themeColors.sub;
|
||
wpmOverTimeChart.options.scales.yAxes[2].scaleLabel.fontColor =
|
||
themeColors.sub;
|
||
|
||
wpmOverTimeChart.data.labels = labels;
|
||
|
||
let rawWpmPerSecondRaw = keypressPerSecond.map((f) =>
|
||
Math.round((f.count / 5) * 60)
|
||
);
|
||
|
||
let rawWpmPerSecond = Misc.smooth(rawWpmPerSecondRaw, 1);
|
||
|
||
let stddev = Misc.stdDev(rawWpmPerSecondRaw);
|
||
let avg = Misc.mean(rawWpmPerSecondRaw);
|
||
|
||
let consistency = Misc.roundTo2(Misc.kogasa(stddev / avg));
|
||
let keyConsistency = Misc.roundTo2(
|
||
Misc.kogasa(
|
||
Misc.stdDev(keypressStats.spacing.array) /
|
||
Misc.mean(keypressStats.spacing.array)
|
||
)
|
||
);
|
||
|
||
if (isNaN(consistency)) {
|
||
consistency = 0;
|
||
}
|
||
|
||
if (config.alwaysShowDecimalPlaces) {
|
||
$("#result .stats .consistency .bottom").text(
|
||
Misc.roundTo2(consistency) + "%"
|
||
);
|
||
$("#result .stats .consistency .bottom").attr(
|
||
"aria-label",
|
||
`${keyConsistency}% key`
|
||
);
|
||
} else {
|
||
$("#result .stats .consistency .bottom").text(
|
||
Math.round(consistency) + "%"
|
||
);
|
||
$("#result .stats .consistency .bottom").attr(
|
||
"aria-label",
|
||
`${consistency}% (${keyConsistency}% key)`
|
||
);
|
||
}
|
||
|
||
wpmOverTimeChart.data.datasets[0].borderColor = themeColors.main;
|
||
wpmOverTimeChart.data.datasets[0].pointBackgroundColor = themeColors.main;
|
||
wpmOverTimeChart.data.datasets[0].data = wpmHistory;
|
||
wpmOverTimeChart.data.datasets[1].borderColor = themeColors.sub;
|
||
wpmOverTimeChart.data.datasets[1].pointBackgroundColor = themeColors.sub;
|
||
wpmOverTimeChart.data.datasets[1].data = rawWpmPerSecond;
|
||
|
||
let maxChartVal = Math.max(
|
||
...[Math.max(...rawWpmPerSecond), Math.max(...wpmHistory)]
|
||
);
|
||
|
||
let minChartVal = Math.min(
|
||
...[Math.min(...rawWpmPerSecond), Math.min(...wpmHistory)]
|
||
);
|
||
|
||
if (!config.startGraphsAtZero) {
|
||
wpmOverTimeChart.options.scales.yAxes[0].ticks.min = Math.min(
|
||
...wpmHistory
|
||
);
|
||
wpmOverTimeChart.options.scales.yAxes[1].ticks.min = Math.min(
|
||
...wpmHistory
|
||
);
|
||
} else {
|
||
wpmOverTimeChart.options.scales.yAxes[0].ticks.min = 0;
|
||
wpmOverTimeChart.options.scales.yAxes[1].ticks.min = 0;
|
||
}
|
||
|
||
// let errorsNoZero = [];
|
||
|
||
// for (let i = 0; i < errorsPerSecond.length; i++) {
|
||
// errorsNoZero.push({
|
||
// x: i + 1,
|
||
// y: errorsPerSecond[i].count,
|
||
// });
|
||
// }
|
||
|
||
let errorsArray = [];
|
||
for (let i = 0; i < errorsPerSecond.length; i++) {
|
||
errorsArray.push(errorsPerSecond[i].count);
|
||
}
|
||
|
||
wpmOverTimeChart.data.datasets[2].data = errorsArray;
|
||
|
||
let kps = keypressPerSecond.slice(Math.max(keypressPerSecond.length - 5, 0));
|
||
|
||
kps = kps.map((a) => a.count);
|
||
|
||
kps = kps.reduce((a, b) => a + b, 0);
|
||
|
||
let afkDetected = kps === 0 ? true : false;
|
||
|
||
if (bailout) afkDetected = false;
|
||
|
||
if (difficultyFailed) {
|
||
Notifications.add("Test failed", 0);
|
||
} else if (afkDetected) {
|
||
Notifications.add("Test invalid - AFK detected", 0);
|
||
} else if (sameWordset) {
|
||
Notifications.add("Test invalid - repeated", 0);
|
||
} else {
|
||
let activeTags = [];
|
||
let activeTagsIds = [];
|
||
try {
|
||
db_getSnapshot().tags.forEach((tag) => {
|
||
if (tag.active === true) {
|
||
activeTags.push(tag);
|
||
activeTagsIds.push(tag.id);
|
||
}
|
||
});
|
||
} catch (e) {}
|
||
|
||
let chartData = {
|
||
wpm: wpmHistory,
|
||
raw: rawWpmPerSecond,
|
||
err: errorsArray,
|
||
};
|
||
|
||
if (testtime > 122) {
|
||
chartData = "toolong";
|
||
keypressStats.spacing.array = "toolong";
|
||
keypressStats.duration.array = "toolong";
|
||
}
|
||
|
||
let lang = config.language;
|
||
|
||
let quoteLength = -1;
|
||
if (config.mode === "quote") {
|
||
quoteLength = randomQuote.group;
|
||
}
|
||
|
||
let completedEvent = {
|
||
wpm: stats.wpm,
|
||
rawWpm: stats.wpmRaw,
|
||
correctChars: stats.correctChars + stats.correctSpaces,
|
||
incorrectChars: stats.incorrectChars,
|
||
allChars: stats.allChars,
|
||
acc: stats.acc,
|
||
mode: config.mode,
|
||
mode2: mode2,
|
||
quoteLength: quoteLength,
|
||
punctuation: config.punctuation,
|
||
numbers: config.numbers,
|
||
timestamp: Date.now(),
|
||
language: lang,
|
||
restartCount: restartCount,
|
||
incompleteTestSeconds: incompleteTestSeconds,
|
||
difficulty: config.difficulty,
|
||
testDuration: testtime,
|
||
afkDuration: afkseconds,
|
||
blindMode: config.blindMode,
|
||
theme: config.theme,
|
||
tags: activeTagsIds,
|
||
keySpacing: keypressStats.spacing.array,
|
||
keyDuration: keypressStats.duration.array,
|
||
consistency: consistency,
|
||
keyConsistency: keyConsistency,
|
||
funbox: activeFunBox,
|
||
bailedOut: bailout,
|
||
chartData: chartData,
|
||
};
|
||
if (
|
||
config.difficulty == "normal" ||
|
||
((config.difficulty == "master" || config.difficulty == "expert") &&
|
||
!difficultyFailed)
|
||
) {
|
||
restartCount = 0;
|
||
incompleteTestSeconds = 0;
|
||
}
|
||
if (
|
||
stats.wpm > 0 &&
|
||
stats.wpm < 350 &&
|
||
stats.acc > 50 &&
|
||
stats.acc <= 100
|
||
) {
|
||
if (firebase.auth().currentUser != null) {
|
||
completedEvent.uid = firebase.auth().currentUser.uid;
|
||
|
||
//check local pb
|
||
accountIconLoading(true);
|
||
let localPb = false;
|
||
let dontShowCrown = false;
|
||
let pbDiff = 0;
|
||
db_getLocalPB(
|
||
config.mode,
|
||
mode2,
|
||
config.punctuation,
|
||
config.language,
|
||
config.difficulty
|
||
).then((lpb) => {
|
||
db_getUserHighestWpm(
|
||
config.mode,
|
||
mode2,
|
||
config.punctuation,
|
||
config.language,
|
||
config.difficulty
|
||
).then((highestwpm) => {
|
||
hideCrown();
|
||
$("#result .stats .wpm .crown").attr("aria-label", "");
|
||
if (lpb < stats.wpm && stats.wpm < highestwpm) {
|
||
dontShowCrown = true;
|
||
}
|
||
if (lpb < stats.wpm) {
|
||
//new pb based on local
|
||
pbDiff = Math.abs(stats.wpm - lpb);
|
||
if (!dontShowCrown) {
|
||
hideCrown();
|
||
showCrown();
|
||
$("#result .stats .wpm .crown").attr(
|
||
"aria-label",
|
||
"+" + Misc.roundTo2(pbDiff)
|
||
);
|
||
}
|
||
localPb = true;
|
||
}
|
||
if (lpb > 0) {
|
||
wpmOverTimeChart.options.annotation.annotations.push({
|
||
enabled: false,
|
||
type: "line",
|
||
mode: "horizontal",
|
||
scaleID: "wpm",
|
||
value: lpb,
|
||
borderColor: themeColors.sub,
|
||
borderWidth: 1,
|
||
borderDash: [2, 2],
|
||
label: {
|
||
backgroundColor: themeColors.sub,
|
||
fontFamily: "Roboto Mono",
|
||
fontSize: 11,
|
||
fontStyle: "normal",
|
||
fontColor: themeColors.bg,
|
||
xPadding: 6,
|
||
yPadding: 6,
|
||
cornerRadius: 3,
|
||
position: "center",
|
||
enabled: true,
|
||
content: `PB: ${lpb}`,
|
||
},
|
||
});
|
||
if (maxChartVal >= lpb - 15 && maxChartVal <= lpb + 15) {
|
||
maxChartVal = lpb + 15;
|
||
}
|
||
wpmOverTimeChart.options.scales.yAxes[0].ticks.max = Math.round(
|
||
maxChartVal
|
||
);
|
||
wpmOverTimeChart.options.scales.yAxes[1].ticks.max = Math.round(
|
||
maxChartVal
|
||
);
|
||
wpmOverTimeChart.update({ duration: 0 });
|
||
}
|
||
$("#result .stats .leaderboards").removeClass("hidden");
|
||
$("#result .stats .leaderboards .bottom").html("checking...");
|
||
|
||
if (activeTags.length == 0) {
|
||
$("#result .stats .tags").addClass("hidden");
|
||
} else {
|
||
$("#result .stats .tags").removeClass("hidden");
|
||
}
|
||
$("#result .stats .tags .bottom").text("");
|
||
let annotationSide = "left";
|
||
activeTags.forEach(async (tag) => {
|
||
let tpb = await db_getLocalTagPB(
|
||
tag.id,
|
||
config.mode,
|
||
mode2,
|
||
config.punctuation,
|
||
config.language,
|
||
config.difficulty
|
||
);
|
||
$("#result .stats .tags .bottom").append(`
|
||
<div tagid="${tag.id}" aria-label="PB: ${tpb}" data-balloon-pos="up">${tag.name}<i class="fas fa-crown hidden"></i></div>
|
||
`);
|
||
if (tpb < stats.wpm) {
|
||
//new pb for that tag
|
||
db_saveLocalTagPB(
|
||
tag.id,
|
||
config.mode,
|
||
mode2,
|
||
config.punctuation,
|
||
config.language,
|
||
config.difficulty,
|
||
stats.wpm,
|
||
stats.acc,
|
||
stats.wpmRaw,
|
||
consistency
|
||
);
|
||
$(
|
||
`#result .stats .tags .bottom div[tagid="${tag.id}"] .fas`
|
||
).removeClass("hidden");
|
||
$(`#result .stats .tags .bottom div[tagid="${tag.id}"]`).attr(
|
||
"aria-label",
|
||
"+" + Misc.roundTo2(stats.wpm - tpb)
|
||
);
|
||
console.log("new pb for tag " + tag.name);
|
||
} else {
|
||
wpmOverTimeChart.options.annotation.annotations.push({
|
||
enabled: false,
|
||
type: "line",
|
||
mode: "horizontal",
|
||
scaleID: "wpm",
|
||
value: tpb,
|
||
borderColor: themeColors.sub,
|
||
borderWidth: 1,
|
||
borderDash: [2, 2],
|
||
label: {
|
||
backgroundColor: themeColors.sub,
|
||
fontFamily: "Roboto Mono",
|
||
fontSize: 11,
|
||
fontStyle: "normal",
|
||
fontColor: themeColors.bg,
|
||
xPadding: 6,
|
||
yPadding: 6,
|
||
cornerRadius: 3,
|
||
position: annotationSide,
|
||
enabled: true,
|
||
content: `${tag.name} PB: ${tpb}`,
|
||
},
|
||
});
|
||
if (annotationSide === "left") {
|
||
annotationSide = "right";
|
||
} else {
|
||
annotationSide = "left";
|
||
}
|
||
}
|
||
});
|
||
|
||
CloudFunctions.testCompleted({
|
||
uid: firebase.auth().currentUser.uid,
|
||
obj: completedEvent,
|
||
})
|
||
.then((e) => {
|
||
accountIconLoading(false);
|
||
if (e.data == null) {
|
||
Notifications.add(
|
||
"Unexpected response from the server: " + e.data,
|
||
-1
|
||
);
|
||
return;
|
||
}
|
||
if (e.data.resultCode === -1) {
|
||
Notifications.add("Could not save result", -1);
|
||
} else if (e.data.resultCode === -2) {
|
||
Notifications.add(
|
||
"Possible bot detected. Result not saved.",
|
||
-1
|
||
);
|
||
} else if (e.data.resultCode === -3) {
|
||
Notifications.add(
|
||
"Could not verify keypress stats. Result not saved.",
|
||
-1
|
||
);
|
||
} else if (e.data.resultCode === -4) {
|
||
Notifications.add(
|
||
"Result data does not make sense. Result not saved.",
|
||
-1
|
||
);
|
||
} else if (e.data.resultCode === -999) {
|
||
console.error("internal error: " + e.data.message);
|
||
Notifications.add(
|
||
"Internal error. Result might not be saved. " +
|
||
e.data.message,
|
||
-1
|
||
);
|
||
} else if (e.data.resultCode === 1 || e.data.resultCode === 2) {
|
||
completedEvent.id = e.data.createdId;
|
||
if (e.data.resultCode === 2) {
|
||
completedEvent.isPb = true;
|
||
}
|
||
if (
|
||
db_getSnapshot() !== null &&
|
||
db_getSnapshot().results !== undefined
|
||
) {
|
||
db_getSnapshot().results.unshift(completedEvent);
|
||
if (db_getSnapshot().globalStats.time == undefined) {
|
||
db_getSnapshot().globalStats.time =
|
||
testtime +
|
||
completedEvent.incompleteTestSeconds -
|
||
afkseconds;
|
||
} else {
|
||
db_getSnapshot().globalStats.time +=
|
||
testtime +
|
||
completedEvent.incompleteTestSeconds -
|
||
afkseconds;
|
||
}
|
||
if (db_getSnapshot().globalStats.started == undefined) {
|
||
db_getSnapshot().globalStats.started = restartCount + 1;
|
||
} else {
|
||
db_getSnapshot().globalStats.started += restartCount + 1;
|
||
}
|
||
if (db_getSnapshot().globalStats.completed == undefined) {
|
||
db_getSnapshot().globalStats.completed = 1;
|
||
} else {
|
||
db_getSnapshot().globalStats.completed += 1;
|
||
}
|
||
}
|
||
try {
|
||
firebase
|
||
.analytics()
|
||
.logEvent("testCompleted", completedEvent);
|
||
} catch (e) {
|
||
console.log("Analytics unavailable");
|
||
}
|
||
|
||
if (
|
||
config.mode === "time" &&
|
||
(mode2 == "15" || mode2 == "60") &&
|
||
db_getSnapshot() !== null
|
||
) {
|
||
const lbUpIcon = `<i class="fas fa-angle-up"></i>`;
|
||
const lbDownIcon = `<i class="fas fa-angle-down"></i>`;
|
||
const lbRightIcon = `<i class="fas fa-angle-right"></i>`;
|
||
|
||
//global
|
||
let globalLbString = "";
|
||
const glb = e.data.globalLeaderboard;
|
||
const glbMemory = db_getSnapshot().lbMemory[
|
||
config.mode + mode2
|
||
].global;
|
||
let dontShowGlobalDiff =
|
||
glbMemory == null || glbMemory === -1 ? true : false;
|
||
let globalLbDiff = null;
|
||
if (glb === null) {
|
||
globalLbString = "global: not found";
|
||
} else if (glb.insertedAt === -1) {
|
||
dontShowGlobalDiff = true;
|
||
globalLbDiff = glbMemory - glb.insertedAt;
|
||
updateLbMemory(
|
||
config.mode,
|
||
mode2,
|
||
"global",
|
||
glb.insertedAt
|
||
);
|
||
|
||
globalLbString = "global: not qualified";
|
||
} else if (glb.insertedAt >= 0) {
|
||
if (glb.newBest) {
|
||
globalLbDiff = glbMemory - glb.insertedAt;
|
||
updateLbMemory(
|
||
config.mode,
|
||
mode2,
|
||
"global",
|
||
glb.insertedAt
|
||
);
|
||
let str = Misc.getPositionString(glb.insertedAt + 1);
|
||
globalLbString = `global: ${str}`;
|
||
} else {
|
||
globalLbDiff = glbMemory - glb.foundAt;
|
||
updateLbMemory(
|
||
config.mode,
|
||
mode2,
|
||
"global",
|
||
glb.foundAt
|
||
);
|
||
let str = Misc.getPositionString(glb.foundAt + 1);
|
||
globalLbString = `global: ${str}`;
|
||
}
|
||
}
|
||
if (!dontShowGlobalDiff) {
|
||
let sString =
|
||
globalLbDiff === 1 || globalLbDiff === -1 ? "" : "s";
|
||
if (globalLbDiff > 0) {
|
||
globalLbString += ` <span class="lbChange" aria-label="You've gained ${globalLbDiff} position${sString}" data-balloon-pos="up">(${lbUpIcon}${globalLbDiff})</span>`;
|
||
} else if (globalLbDiff === 0) {
|
||
globalLbString += ` <span class="lbChange" aria-label="Your position remained the same" data-balloon-pos="up">(${lbRightIcon}${globalLbDiff})</span>`;
|
||
} else if (globalLbDiff < 0) {
|
||
globalLbString += ` <span class="lbChange" aria-label="You've lost ${globalLbDiff} position${sString}" data-balloon-pos="up">(${lbDownIcon}${globalLbDiff})</span>`;
|
||
}
|
||
}
|
||
|
||
//daily
|
||
let dailyLbString = "";
|
||
const dlb = e.data.dailyLeaderboard;
|
||
const dlbMemory = db_getSnapshot().lbMemory[
|
||
config.mode + mode2
|
||
].daily;
|
||
let dontShowDailyDiff =
|
||
dlbMemory == null || dlbMemory === -1 ? true : false;
|
||
let dailyLbDiff = null;
|
||
if (dlb === null) {
|
||
dailyLbString = "daily: not found";
|
||
} else if (dlb.insertedAt === -1) {
|
||
dontShowDailyDiff = true;
|
||
dailyLbDiff = dlbMemory - dlb.insertedAt;
|
||
updateLbMemory(
|
||
config.mode,
|
||
mode2,
|
||
"daily",
|
||
dlb.insertedAt
|
||
);
|
||
dailyLbString = "daily: not qualified";
|
||
} else if (dlb.insertedAt >= 0) {
|
||
if (dlb.newBest) {
|
||
dailyLbDiff = dlbMemory - dlb.insertedAt;
|
||
updateLbMemory(
|
||
config.mode,
|
||
mode2,
|
||
"daily",
|
||
dlb.insertedAt
|
||
);
|
||
let str = Misc.getPositionString(dlb.insertedAt + 1);
|
||
dailyLbString = `daily: ${str}`;
|
||
} else {
|
||
dailyLbDiff = dlbMemory - dlb.foundAt;
|
||
updateLbMemory(
|
||
config.mode,
|
||
mode2,
|
||
"daily",
|
||
dlb.foundAt
|
||
);
|
||
let str = Misc.getPositionString(dlb.foundAt + 1);
|
||
dailyLbString = `daily: ${str}`;
|
||
}
|
||
}
|
||
if (!dontShowDailyDiff) {
|
||
let sString =
|
||
dailyLbDiff === 1 || dailyLbDiff === -1 ? "" : "s";
|
||
if (dailyLbDiff > 0) {
|
||
dailyLbString += ` <span class="lbChange" aria-label="You've gained ${dailyLbDiff} position${sString}" data-balloon-pos="up">(${lbUpIcon}${dailyLbDiff})</span>`;
|
||
} else if (dailyLbDiff === 0) {
|
||
dailyLbString += ` <span class="lbChange" aria-label="Your position remained the same" data-balloon-pos="up">(${lbRightIcon}${dailyLbDiff})</span>`;
|
||
} else if (dailyLbDiff < 0) {
|
||
dailyLbString += ` <span class="lbChange" aria-label="You've lost ${dailyLbDiff} position${sString}" data-balloon-pos="up">(${lbDownIcon}${dailyLbDiff})</span>`;
|
||
}
|
||
}
|
||
$("#result .stats .leaderboards .bottom").html(
|
||
globalLbString + "<br>" + dailyLbString
|
||
);
|
||
|
||
// CloudFunctions.saveLbMemory({
|
||
// uid: firebase.auth().currentUser.uid,
|
||
// obj: db_getSnapshot().lbMemory,
|
||
// }).then((d) => {
|
||
// if (d.data.returnCode === 1) {
|
||
// } else {
|
||
// Notifications.add(
|
||
// `Error saving lb memory ${d.data.message}`,
|
||
// 4000
|
||
// );
|
||
// }
|
||
// });
|
||
}
|
||
if (
|
||
e.data.dailyLeaderboard === null &&
|
||
e.data.globalLeaderboard === null
|
||
) {
|
||
$("#result .stats .leaderboards").addClass("hidden");
|
||
}
|
||
if (e.data.needsToVerifyEmail === true) {
|
||
$("#result .stats .leaderboards").removeClass("hidden");
|
||
$("#result .stats .leaderboards .bottom").html(
|
||
`please verify your email<br>to access leaderboards - <a onClick="sendVerificationEmail()">resend email</a>`
|
||
);
|
||
} else if (e.data.lbBanned) {
|
||
$("#result .stats .leaderboards").removeClass("hidden");
|
||
$("#result .stats .leaderboards .bottom").html("banned");
|
||
} else if (e.data.name === false) {
|
||
$("#result .stats .leaderboards").removeClass("hidden");
|
||
$("#result .stats .leaderboards .bottom").html(
|
||
"update your name to access leaderboards"
|
||
);
|
||
} else if (e.data.needsToVerify === true) {
|
||
$("#result .stats .leaderboards").removeClass("hidden");
|
||
$("#result .stats .leaderboards .bottom").html(
|
||
"verification needed to access leaderboards"
|
||
);
|
||
}
|
||
|
||
if (e.data.resultCode === 2) {
|
||
//new pb
|
||
showCrown();
|
||
if (!localPb) {
|
||
}
|
||
db_saveLocalPB(
|
||
config.mode,
|
||
mode2,
|
||
config.punctuation,
|
||
config.language,
|
||
config.difficulty,
|
||
stats.wpm,
|
||
stats.acc,
|
||
stats.wpmRaw,
|
||
consistency
|
||
);
|
||
} else if (e.data.resultCode === 1) {
|
||
hideCrown();
|
||
// if (localPb) {
|
||
// Notifications.add(
|
||
// "Local PB data is out of sync! Refresh the page to resync it or contact Miodec on Discord.",
|
||
// 15000
|
||
// );
|
||
// }
|
||
}
|
||
}
|
||
})
|
||
.catch((e) => {
|
||
console.error(e);
|
||
Notifications.add("Could not save result. " + e, -1);
|
||
});
|
||
});
|
||
});
|
||
} else {
|
||
try {
|
||
firebase.analytics().logEvent("testCompletedNoLogin", completedEvent);
|
||
} catch (e) {
|
||
console.log("Analytics unavailable");
|
||
}
|
||
notSignedInLastResult = completedEvent;
|
||
}
|
||
} else {
|
||
Notifications.add("Test invalid", 0);
|
||
testInvalid = true;
|
||
try {
|
||
firebase.analytics().logEvent("testCompletedInvalid", completedEvent);
|
||
} catch (e) {
|
||
console.log("Analytics unavailable");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (firebase.auth().currentUser != null) {
|
||
$("#result .loginTip").addClass("hidden");
|
||
} else {
|
||
$("#result .stats .leaderboards").addClass("hidden");
|
||
$("#result .loginTip").removeClass("hidden");
|
||
}
|
||
|
||
let testType = "";
|
||
|
||
if (config.mode === "quote") {
|
||
let qlen = "";
|
||
if (config.quoteLength === 0) {
|
||
qlen = "short ";
|
||
} else if (config.quoteLength === 1) {
|
||
qlen = "medium ";
|
||
} else if (config.quoteLength === 2) {
|
||
qlen = "long ";
|
||
} else if (config.quoteLength === 3) {
|
||
qlen = "thicc ";
|
||
}
|
||
testType += qlen + config.mode;
|
||
} else {
|
||
testType += config.mode;
|
||
}
|
||
if (config.mode == "time") {
|
||
testType += " " + config.time;
|
||
} else if (config.mode == "words") {
|
||
testType += " " + config.words;
|
||
}
|
||
if (
|
||
config.mode != "custom" &&
|
||
activeFunBox !== "gibberish" &&
|
||
activeFunBox !== "58008"
|
||
) {
|
||
testType += "<br>" + config.language.replace(/_/g, " ");
|
||
}
|
||
if (config.punctuation) {
|
||
testType += "<br>punctuation";
|
||
}
|
||
if (config.numbers) {
|
||
testType += "<br>numbers";
|
||
}
|
||
if (config.blindMode) {
|
||
testType += "<br>blind";
|
||
}
|
||
if (activeFunBox !== "none") {
|
||
testType += "<br>" + activeFunBox.replace(/_/g, " ");
|
||
}
|
||
if (config.difficulty == "expert") {
|
||
testType += "<br>expert";
|
||
} else if (config.difficulty == "master") {
|
||
testType += "<br>master";
|
||
}
|
||
|
||
$("#result .stats .testType .bottom").html(testType);
|
||
|
||
let otherText = "";
|
||
if (config.layout !== "default") {
|
||
otherText += "<br>" + config.layout;
|
||
}
|
||
if (difficultyFailed) {
|
||
otherText += "<br>failed";
|
||
}
|
||
if (afkDetected) {
|
||
otherText += "<br>afk detected";
|
||
}
|
||
if (testInvalid) {
|
||
otherText += "<br>invalid";
|
||
}
|
||
if (sameWordset) {
|
||
otherText += "<br>repeated";
|
||
}
|
||
if (bailout) {
|
||
otherText += "<br>bailed out";
|
||
}
|
||
|
||
if (otherText == "") {
|
||
$("#result .stats .info").addClass("hidden");
|
||
} else {
|
||
$("#result .stats .info").removeClass("hidden");
|
||
otherText = otherText.substring(4);
|
||
$("#result .stats .info .bottom").html(otherText);
|
||
}
|
||
|
||
if (
|
||
$("#result .stats .tags").hasClass("hidden") &&
|
||
$("#result .stats .info").hasClass("hidden")
|
||
) {
|
||
$("#result .stats .infoAndTags").addClass("hidden");
|
||
} else {
|
||
$("#result .stats .infoAndTags").removeClass("hidden");
|
||
}
|
||
|
||
if (config.mode === "quote") {
|
||
$("#result .stats .source").removeClass("hidden");
|
||
$("#result .stats .source .bottom").html(randomQuote.source);
|
||
} else {
|
||
$("#result .stats .source").addClass("hidden");
|
||
}
|
||
|
||
wpmOverTimeChart.options.scales.yAxes[0].ticks.max = maxChartVal;
|
||
wpmOverTimeChart.options.scales.yAxes[1].ticks.max = maxChartVal;
|
||
|
||
wpmOverTimeChart.update({ duration: 0 });
|
||
wpmOverTimeChart.resize();
|
||
swapElements($("#typingTest"), $("#result"), 250, () => {
|
||
resultCalculating = false;
|
||
$("#words").empty();
|
||
wpmOverTimeChart.resize();
|
||
if (config.alwaysShowWordsHistory) {
|
||
toggleResultWordsDisplay();
|
||
}
|
||
});
|
||
}
|
||
|
||
function startTest() {
|
||
if (pageTransition) {
|
||
return;
|
||
}
|
||
if (!dbConfigLoaded) {
|
||
configChangedBeforeDb = true;
|
||
}
|
||
try {
|
||
if (firebase.auth().currentUser != null) {
|
||
firebase.analytics().logEvent("testStarted");
|
||
} else {
|
||
firebase.analytics().logEvent("testStartedNoLogin");
|
||
}
|
||
} catch (e) {
|
||
console.log("Analytics unavailable");
|
||
}
|
||
testActive = true;
|
||
testStart = performance.now();
|
||
restartTimer();
|
||
showTimer();
|
||
$("#liveWpm").text("0");
|
||
showLiveWpm();
|
||
showLiveAcc();
|
||
updateTimer();
|
||
clearTimeout(timer);
|
||
keypressStats = {
|
||
spacing: {
|
||
current: -1,
|
||
array: [],
|
||
},
|
||
duration: {
|
||
current: -1,
|
||
array: [],
|
||
},
|
||
};
|
||
|
||
if (activeFunBox === "memory") {
|
||
memoryFunboxInterval = clearInterval(memoryFunboxInterval);
|
||
memoryFunboxTimer = null;
|
||
$("#wordsWrapper").addClass("hidden");
|
||
}
|
||
|
||
try {
|
||
if (config.paceCaret !== "off")
|
||
movePaceCaret(performance.now() + paceCaret.spc * 1000);
|
||
} catch (e) {}
|
||
//use a recursive self-adjusting timer to avoid time drift
|
||
const stepIntervalMS = 1000;
|
||
(function loop(expectedStepEnd) {
|
||
const delay = expectedStepEnd - performance.now();
|
||
timer = setTimeout(function () {
|
||
time++;
|
||
$(".pageTest #premidSecondsLeft").text(config.time - time);
|
||
if (
|
||
config.mode === "time" ||
|
||
(config.mode === "custom" && customText.isTimeRandom)
|
||
) {
|
||
updateTimer();
|
||
}
|
||
let wpmAndRaw = liveWpmAndRaw();
|
||
updateLiveWpm(wpmAndRaw.wpm, wpmAndRaw.raw);
|
||
wpmHistory.push(wpmAndRaw.wpm);
|
||
rawHistory.push(wpmAndRaw.raw);
|
||
|
||
let acc = Misc.roundTo2(
|
||
(accuracyStats.correct /
|
||
(accuracyStats.correct + accuracyStats.incorrect)) *
|
||
100
|
||
);
|
||
|
||
if (activeFunBox === "layoutfluid" && config.mode === "time") {
|
||
const layouts = ["qwerty", "dvorak", "colemak"];
|
||
let index = 0;
|
||
index = Math.floor(time / (config.time / 3));
|
||
|
||
if (
|
||
time == Math.floor(config.time / 3) - 3 ||
|
||
time == (config.time / 3) * 2 - 3
|
||
) {
|
||
Notifications.add("3", 0, 1);
|
||
}
|
||
if (
|
||
time == Math.floor(config.time / 3) - 2 ||
|
||
time == Math.floor(config.time / 3) * 2 - 2
|
||
) {
|
||
Notifications.add("2", 0, 1);
|
||
}
|
||
if (
|
||
time == Math.floor(config.time / 3) - 1 ||
|
||
time == Math.floor(config.time / 3) * 2 - 1
|
||
) {
|
||
Notifications.add("1", 0, 1);
|
||
}
|
||
|
||
if (config.layout !== layouts[index] && layouts[index] !== undefined) {
|
||
Notifications.add(`--- !!! ${layouts[index]} !!! ---`, 0);
|
||
}
|
||
setLayout(layouts[index]);
|
||
setKeymapLayout(layouts[index]);
|
||
updateHighlightedKeymapKey();
|
||
settingsGroups.layout.updateButton();
|
||
}
|
||
|
||
keypressPerSecond.push(currentKeypress);
|
||
currentKeypress = {
|
||
mod: 0,
|
||
count: 0,
|
||
words: [],
|
||
};
|
||
errorsPerSecond.push(currentError);
|
||
currentError = {
|
||
count: 0,
|
||
words: [],
|
||
};
|
||
if (
|
||
(config.minWpm === "custom" &&
|
||
wpmAndRaw.wpm < parseInt(config.minWpmCustomSpeed) &&
|
||
currentWordIndex > 3) ||
|
||
(config.minAcc === "custom" && acc < parseInt(config.minAccCustom))
|
||
) {
|
||
clearTimeout(timer);
|
||
failTest();
|
||
return;
|
||
}
|
||
if (
|
||
config.mode == "time" ||
|
||
(config.mode === "custom" && customText.isTimeRandom)
|
||
) {
|
||
if (
|
||
(time >= config.time &&
|
||
config.time !== 0 &&
|
||
config.mode === "time") ||
|
||
(time >= customText.time &&
|
||
customText.time !== 0 &&
|
||
config.mode === "custom")
|
||
) {
|
||
//times up
|
||
clearTimeout(timer);
|
||
hideCaret();
|
||
testActive = false;
|
||
inputHistory.push(currentInput);
|
||
correctedHistory.push(currentCorrected);
|
||
showResult();
|
||
return;
|
||
}
|
||
}
|
||
loop(expectedStepEnd + stepIntervalMS);
|
||
}, delay);
|
||
})(testStart + stepIntervalMS);
|
||
}
|
||
|
||
function restartTest(withSameWordset = false, nosave = false) {
|
||
if (!manualRestart) {
|
||
if (
|
||
(config.mode === "words" && config.words < 1000 && config.words > 0) ||
|
||
(config.mode === "time" && config.time < 3600 && config.time > 0) ||
|
||
config.mode === "quote" ||
|
||
(config.mode === "custom" &&
|
||
customText.isWordRandom &&
|
||
customText.word < 1000 &&
|
||
customText.word != 0) ||
|
||
(config.mode === "custom" &&
|
||
customText.isTimeRandom &&
|
||
customText.time < 3600 &&
|
||
customText.time != 0) ||
|
||
(config.mode === "custom" &&
|
||
!customText.isWordRandom &&
|
||
customText.text.length < 1000)
|
||
) {
|
||
} else {
|
||
if (testActive) {
|
||
Notifications.add(
|
||
"Restart disabled for long tests. Use your mouse to confirm.",
|
||
0
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (modeBeforePractise !== null && !withSameWordset) {
|
||
Notifications.add("Reverting to previous settings.", 0);
|
||
setMode(modeBeforePractise);
|
||
setPunctuation(punctuationBeforePractise);
|
||
setNumbers(numbersBeforePractise);
|
||
modeBeforePractise = null;
|
||
punctuationBeforePractise = null;
|
||
numbersBeforePractise = null;
|
||
}
|
||
|
||
manualRestart = false;
|
||
clearTimeout(timer);
|
||
time = 0;
|
||
wpmHistory = [];
|
||
rawHistory = [];
|
||
missedWords = {};
|
||
correctedHistory = [];
|
||
currentCorrected = "";
|
||
setFocus(false);
|
||
hideCaret();
|
||
testActive = false;
|
||
hideLiveWpm();
|
||
hideLiveAcc();
|
||
hideTimer();
|
||
bailout = false;
|
||
paceCaret = null;
|
||
if (paceCaret !== null) clearTimeout(paceCaret.timeout);
|
||
$("#showWordHistoryButton").removeClass("loaded");
|
||
focusWords();
|
||
keypressPerSecond = [];
|
||
lastSecondNotRound = false;
|
||
currentKeypress = {
|
||
mod: 0,
|
||
count: 0,
|
||
words: [],
|
||
};
|
||
errorsPerSecond = [];
|
||
currentError = {
|
||
count: 0,
|
||
words: [],
|
||
};
|
||
currentTestLine = 0;
|
||
activeWordJumped = false;
|
||
keypressStats = {
|
||
spacing: {
|
||
current: -1,
|
||
array: [],
|
||
},
|
||
duration: {
|
||
current: -1,
|
||
array: [],
|
||
},
|
||
};
|
||
$("#timerNumber").css("opacity", 0);
|
||
let el = null;
|
||
if (resultVisible) {
|
||
//results are being displayed
|
||
el = $("#result");
|
||
} else {
|
||
//words are being displayed
|
||
el = $("#typingTest");
|
||
}
|
||
if (resultVisible) {
|
||
if (
|
||
config.randomTheme !== "off" &&
|
||
!pageTransition &&
|
||
!config.customTheme
|
||
) {
|
||
randomiseTheme();
|
||
}
|
||
}
|
||
resultVisible = false;
|
||
|
||
el.stop(true, true).animate(
|
||
{
|
||
opacity: 0,
|
||
},
|
||
125,
|
||
async () => {
|
||
$("#typingTest").css("opacity", 0).removeClass("hidden");
|
||
if (!withSameWordset) {
|
||
sameWordset = false;
|
||
textHasTab = false;
|
||
await initWords();
|
||
initPaceCaret(nosave);
|
||
} else {
|
||
sameWordset = true;
|
||
testActive = false;
|
||
currentWordIndex = 0;
|
||
currentWordElementIndex = 0;
|
||
accuracyStats = {
|
||
correct: 0,
|
||
incorrect: 0,
|
||
};
|
||
inputHistory = [];
|
||
currentInput = "";
|
||
initPaceCaret();
|
||
showWords();
|
||
}
|
||
if (config.keymapMode !== "off") {
|
||
showKeymap();
|
||
} else {
|
||
hideKeymap();
|
||
}
|
||
document.querySelector("#miniTimerAndLiveWpm .wpm").innerHTML = "0";
|
||
document.querySelector("#miniTimerAndLiveWpm .acc").innerHTML = "100%";
|
||
document.querySelector("#liveWpm").innerHTML = "0";
|
||
document.querySelector("#liveAcc").innerHTML = "100%";
|
||
|
||
if (activeFunBox === "memory") {
|
||
memoryFunboxInterval = clearInterval(memoryFunboxInterval);
|
||
memoryFunboxTimer = Math.round(Math.pow(wordsList.length, 1.2));
|
||
memoryFunboxInterval = setInterval(() => {
|
||
memoryFunboxTimer -= 1;
|
||
Notifications.add(
|
||
memoryFunboxTimer == 0 ? "Times up" : memoryFunboxTimer,
|
||
0,
|
||
1
|
||
);
|
||
if (memoryFunboxTimer <= 0) {
|
||
memoryFunboxInterval = clearInterval(memoryFunboxInterval);
|
||
memoryFunboxTimer = null;
|
||
$("#wordsWrapper").addClass("hidden");
|
||
}
|
||
}, 1000);
|
||
|
||
if (config.keymapMode === "next") {
|
||
setKeymapMode("react");
|
||
}
|
||
}
|
||
|
||
let mode2 = "";
|
||
if (config.mode === "time") {
|
||
mode2 = config.time;
|
||
} else if (config.mode === "words") {
|
||
mode2 = config.words;
|
||
} else if (config.mode === "custom") {
|
||
mode2 = "custom";
|
||
} else if (config.mode === "quote") {
|
||
mode2 = randomQuote.id;
|
||
}
|
||
let fbtext = "";
|
||
if (activeFunBox !== "none") {
|
||
fbtext = " " + activeFunBox;
|
||
}
|
||
$(".pageTest #premidTestMode").text(
|
||
`${config.mode} ${mode2} ${config.language}${fbtext}`
|
||
);
|
||
$(".pageTest #premidSecondsLeft").text(config.time);
|
||
|
||
if (activeFunBox === "layoutfluid") {
|
||
setLayout("qwerty");
|
||
settingsGroups.layout.updateButton();
|
||
setKeymapLayout("qwerty");
|
||
settingsGroups.keymapLayout.updateButton();
|
||
updateHighlightedKeymapKey();
|
||
}
|
||
|
||
$("#result").addClass("hidden");
|
||
$("#testModesNotice").removeClass("hidden").css({
|
||
opacity: 1,
|
||
});
|
||
resetPaceCaret();
|
||
$("#typingTest")
|
||
.css("opacity", 0)
|
||
.removeClass("hidden")
|
||
.stop(true, true)
|
||
.animate(
|
||
{
|
||
opacity: 1,
|
||
},
|
||
125,
|
||
() => {
|
||
resetPaceCaret();
|
||
hideCrown();
|
||
clearTimeout(timer);
|
||
if ($("#commandLineWrapper").hasClass("hidden")) focusWords();
|
||
wpmOverTimeChart.update();
|
||
updateTestModesNotice();
|
||
}
|
||
);
|
||
}
|
||
);
|
||
}
|
||
|
||
function focusWords() {
|
||
if (!$("#wordsWrapper").hasClass("hidden")) {
|
||
$("#wordsInput").focus();
|
||
}
|
||
}
|
||
|
||
function setCustomText() {
|
||
customText.text = prompt("Custom text").trim();
|
||
customText.text = customText.text.replace(/[\n\r\t ]/gm, " ");
|
||
customText.text = customText.text.replace(/ +/gm, " ");
|
||
customText.text = customText.text.split(" ");
|
||
if (customText.text.text.length >= 10000) {
|
||
Notifications.add("Custom text cannot be longer than 10000 words.", 0);
|
||
setMode("time");
|
||
customText.text = "The quick brown fox jumped over the lazy dog".split(" ");
|
||
}
|
||
}
|
||
|
||
function changePage(page) {
|
||
if (pageTransition) {
|
||
return;
|
||
}
|
||
let activePage = $(".page.active");
|
||
$(".page").removeClass("active");
|
||
$("#wordsInput").focusout();
|
||
if (page == "test" || page == "") {
|
||
pageTransition = true;
|
||
swapElements(activePage, $(".page.pageTest"), 250, () => {
|
||
pageTransition = false;
|
||
focusWords();
|
||
$(".page.pageTest").addClass("active");
|
||
history.pushState("/", null, "/");
|
||
});
|
||
showTestConfig();
|
||
hideSignOutButton();
|
||
restartCount = 0;
|
||
incompleteTestSeconds = 0;
|
||
manualRestart = true;
|
||
restartTest();
|
||
} else if (page == "about") {
|
||
pageTransition = true;
|
||
restartTest();
|
||
swapElements(activePage, $(".page.pageAbout"), 250, () => {
|
||
pageTransition = false;
|
||
history.pushState("about", null, "about");
|
||
$(".page.pageAbout").addClass("active");
|
||
});
|
||
hideTestConfig();
|
||
hideSignOutButton();
|
||
} else if (page == "settings") {
|
||
pageTransition = true;
|
||
restartTest();
|
||
swapElements(activePage, $(".page.pageSettings"), 250, () => {
|
||
pageTransition = false;
|
||
history.pushState("settings", null, "settings");
|
||
$(".page.pageSettings").addClass("active");
|
||
});
|
||
updateSettingsPage();
|
||
hideTestConfig();
|
||
hideSignOutButton();
|
||
} else if (page == "account") {
|
||
if (!firebase.auth().currentUser) {
|
||
changePage("login");
|
||
} else {
|
||
pageTransition = true;
|
||
restartTest();
|
||
swapElements(activePage, $(".page.pageAccount"), 250, () => {
|
||
pageTransition = false;
|
||
history.pushState("account", null, "account");
|
||
$(".page.pageAccount").addClass("active");
|
||
});
|
||
refreshAccountPage();
|
||
hideTestConfig();
|
||
showSignOutButton();
|
||
}
|
||
} else if (page == "login") {
|
||
if (firebase.auth().currentUser != null) {
|
||
changePage("account");
|
||
} else {
|
||
pageTransition = true;
|
||
restartTest();
|
||
swapElements(activePage, $(".page.pageLogin"), 250, () => {
|
||
pageTransition = false;
|
||
history.pushState("login", null, "login");
|
||
$(".page.pageLogin").addClass("active");
|
||
});
|
||
hideTestConfig();
|
||
hideSignOutButton();
|
||
}
|
||
}
|
||
}
|
||
|
||
function setMode(mode, nosave) {
|
||
if (mode !== "words" && activeFunBox === "memory") {
|
||
Notifications.add("Memory funbox can only be used with words mode.", 0);
|
||
return;
|
||
}
|
||
|
||
config.mode = mode;
|
||
$("#top .config .mode .text-button").removeClass("active");
|
||
$("#top .config .mode .text-button[mode='" + mode + "']").addClass("active");
|
||
if (config.mode == "time") {
|
||
$("#top .config .wordCount").addClass("hidden");
|
||
$("#top .config .time").removeClass("hidden");
|
||
$("#top .config .customText").addClass("hidden");
|
||
$("#top .config .punctuationMode").removeClass("hidden");
|
||
$("#top .config .numbersMode").removeClass("hidden");
|
||
$("#top .config .quoteLength").addClass("hidden");
|
||
} else if (config.mode == "words") {
|
||
$("#top .config .wordCount").removeClass("hidden");
|
||
$("#top .config .time").addClass("hidden");
|
||
$("#top .config .customText").addClass("hidden");
|
||
$("#top .config .punctuationMode").removeClass("hidden");
|
||
$("#top .config .numbersMode").removeClass("hidden");
|
||
$("#top .config .quoteLength").addClass("hidden");
|
||
} else if (config.mode == "custom") {
|
||
if (
|
||
activeFunBox === "58008" ||
|
||
activeFunBox === "gibberish" ||
|
||
activeFunBox === "ascii"
|
||
) {
|
||
activeFunBox = "none";
|
||
updateTestModesNotice();
|
||
}
|
||
$("#top .config .wordCount").addClass("hidden");
|
||
$("#top .config .time").addClass("hidden");
|
||
$("#top .config .customText").removeClass("hidden");
|
||
$("#top .config .punctuationMode").removeClass("hidden");
|
||
$("#top .config .numbersMode").removeClass("hidden");
|
||
$("#top .config .quoteLength").addClass("hidden");
|
||
setPunctuation(false, true);
|
||
setNumbers(false, true);
|
||
} else if (config.mode == "quote") {
|
||
setToggleSettings(false, nosave);
|
||
$("#top .config .wordCount").addClass("hidden");
|
||
$("#top .config .time").addClass("hidden");
|
||
$("#top .config .customText").addClass("hidden");
|
||
$("#top .config .punctuationMode").addClass("hidden");
|
||
$("#top .config .numbersMode").addClass("hidden");
|
||
$("#result .stats .source").removeClass("hidden");
|
||
$("#top .config .quoteLength").removeClass("hidden");
|
||
}
|
||
if (!nosave) saveConfigToCookie();
|
||
}
|
||
|
||
function liveWpmAndRaw() {
|
||
let chars = 0;
|
||
let correctWordChars = 0;
|
||
let spaces = 0;
|
||
for (let i = 0; i < inputHistory.length; i++) {
|
||
let word = wordsList[i];
|
||
if (inputHistory[i] == word) {
|
||
//the word is correct
|
||
//+1 for space
|
||
correctWordChars += word.length;
|
||
if (
|
||
i < inputHistory.length - 1 &&
|
||
Misc.getLastChar(inputHistory[i]) !== "\n"
|
||
) {
|
||
spaces++;
|
||
}
|
||
}
|
||
chars += inputHistory[i].length;
|
||
}
|
||
if (wordsList[currentWordIndex] == currentInput) {
|
||
correctWordChars += currentInput.length;
|
||
}
|
||
if (activeFunBox === "nospace") {
|
||
spaces = 0;
|
||
}
|
||
chars += currentInput.length;
|
||
let testNow = performance.now();
|
||
let testSeconds = (testNow - testStart) / 1000;
|
||
let wpm = Math.round(((correctWordChars + spaces) * (60 / testSeconds)) / 5);
|
||
let raw = Math.round(((chars + spaces) * (60 / testSeconds)) / 5);
|
||
return {
|
||
wpm: wpm,
|
||
raw: raw,
|
||
};
|
||
}
|
||
|
||
function updateLiveWpm(wpm, raw) {
|
||
if (!testActive || !config.showLiveWpm) {
|
||
hideLiveWpm();
|
||
} else {
|
||
showLiveWpm();
|
||
}
|
||
let number = wpm;
|
||
if (config.blindMode) {
|
||
number = raw;
|
||
}
|
||
if (config.alwaysShowCPM) {
|
||
number = Math.round(number * 5);
|
||
}
|
||
document.querySelector("#miniTimerAndLiveWpm .wpm").innerHTML = number;
|
||
document.querySelector("#liveWpm").innerHTML = number;
|
||
}
|
||
|
||
function updateLiveAcc(acc) {
|
||
if (!testActive || !config.showLiveAcc) {
|
||
hideLiveAcc();
|
||
} else {
|
||
showLiveAcc();
|
||
}
|
||
let number = Math.floor(acc);
|
||
if (config.blindMode) {
|
||
number = 100;
|
||
}
|
||
document.querySelector("#miniTimerAndLiveWpm .acc").innerHTML = number + "%";
|
||
document.querySelector("#liveAcc").innerHTML = number + "%";
|
||
}
|
||
|
||
function showLiveWpm() {
|
||
if (!config.showLiveWpm) return;
|
||
if (!testActive) return;
|
||
if (config.timerStyle === "mini") {
|
||
// $("#miniTimerAndLiveWpm .wpm").css("opacity", config.timerOpacity);
|
||
if (!$("#miniTimerAndLiveWpm .wpm").hasClass("hidden")) return;
|
||
$("#miniTimerAndLiveWpm .wpm")
|
||
.removeClass("hidden")
|
||
.css("opacity", 0)
|
||
.animate(
|
||
{
|
||
opacity: config.timerOpacity,
|
||
},
|
||
125
|
||
);
|
||
} else {
|
||
// $("#liveWpm").css("opacity", config.timerOpacity);
|
||
if (!$("#liveWpm").hasClass("hidden")) return;
|
||
$("#liveWpm").removeClass("hidden").css("opacity", 0).animate(
|
||
{
|
||
opacity: config.timerOpacity,
|
||
},
|
||
125
|
||
);
|
||
}
|
||
}
|
||
|
||
function hideLiveWpm() {
|
||
// $("#liveWpm").css("opacity", 0);
|
||
// $("#miniTimerAndLiveWpm .wpm").css("opacity", 0);
|
||
$("#liveWpm").animate(
|
||
{
|
||
opacity: config.timerOpacity,
|
||
},
|
||
125,
|
||
() => {
|
||
$("#liveWpm").addClass("hidden");
|
||
}
|
||
);
|
||
$("#miniTimerAndLiveWpm .wpm").animate(
|
||
{
|
||
opacity: config.timerOpacity,
|
||
},
|
||
125,
|
||
() => {
|
||
$("#miniTimerAndLiveWpm .wpm").addClass("hidden");
|
||
}
|
||
);
|
||
}
|
||
|
||
function showLiveAcc() {
|
||
if (!config.showLiveAcc) return;
|
||
if (!testActive) return;
|
||
if (config.timerStyle === "mini") {
|
||
// $("#miniTimerAndLiveWpm .wpm").css("opacity", config.timerOpacity);
|
||
if (!$("#miniTimerAndLiveWpm .acc").hasClass("hidden")) return;
|
||
$("#miniTimerAndLiveWpm .acc")
|
||
.removeClass("hidden")
|
||
.css("opacity", 0)
|
||
.animate(
|
||
{
|
||
opacity: config.timerOpacity,
|
||
},
|
||
125
|
||
);
|
||
} else {
|
||
// $("#liveWpm").css("opacity", config.timerOpacity);
|
||
if (!$("#liveAcc").hasClass("hidden")) return;
|
||
$("#liveAcc").removeClass("hidden").css("opacity", 0).animate(
|
||
{
|
||
opacity: config.timerOpacity,
|
||
},
|
||
125
|
||
);
|
||
}
|
||
}
|
||
|
||
function hideLiveAcc() {
|
||
// $("#liveWpm").css("opacity", 0);
|
||
// $("#miniTimerAndLiveWpm .wpm").css("opacity", 0);
|
||
$("#liveAcc").animate(
|
||
{
|
||
opacity: config.timerOpacity,
|
||
},
|
||
125,
|
||
() => {
|
||
$("#liveAcc").addClass("hidden");
|
||
}
|
||
);
|
||
$("#miniTimerAndLiveWpm .acc").animate(
|
||
{
|
||
opacity: config.timerOpacity,
|
||
},
|
||
125,
|
||
() => {
|
||
$("#miniTimerAndLiveWpm .acc").addClass("hidden");
|
||
}
|
||
);
|
||
}
|
||
|
||
function swapElements(
|
||
el1,
|
||
el2,
|
||
totalDuration,
|
||
callback = function () {
|
||
return;
|
||
}
|
||
) {
|
||
if (
|
||
(el1.hasClass("hidden") && !el2.hasClass("hidden")) ||
|
||
(!el1.hasClass("hidden") && el2.hasClass("hidden"))
|
||
) {
|
||
//one of them is hidden and the other is visible
|
||
if (el1.hasClass("hidden")) {
|
||
callback();
|
||
return false;
|
||
}
|
||
$(el1)
|
||
.removeClass("hidden")
|
||
.css("opacity", 1)
|
||
.animate(
|
||
{
|
||
opacity: 0,
|
||
},
|
||
totalDuration / 2,
|
||
() => {
|
||
$(el1).addClass("hidden");
|
||
$(el2)
|
||
.removeClass("hidden")
|
||
.css("opacity", 0)
|
||
.animate(
|
||
{
|
||
opacity: 1,
|
||
},
|
||
totalDuration / 2,
|
||
() => {
|
||
callback();
|
||
}
|
||
);
|
||
}
|
||
);
|
||
} else if (el1.hasClass("hidden") && el2.hasClass("hidden")) {
|
||
//both are hidden, only fade in the second
|
||
$(el2)
|
||
.removeClass("hidden")
|
||
.css("opacity", 0)
|
||
.animate(
|
||
{
|
||
opacity: 1,
|
||
},
|
||
totalDuration,
|
||
() => {
|
||
callback();
|
||
}
|
||
);
|
||
} else {
|
||
callback();
|
||
}
|
||
}
|
||
|
||
function updateAccountLoginButton() {
|
||
if (firebase.auth().currentUser != null) {
|
||
swapElements(
|
||
$("#menu .icon-button.login"),
|
||
$("#menu .icon-button.account"),
|
||
250
|
||
);
|
||
} else {
|
||
swapElements(
|
||
$("#menu .icon-button.account"),
|
||
$("#menu .icon-button.login"),
|
||
250
|
||
);
|
||
}
|
||
}
|
||
|
||
function accountIconLoading(truefalse) {
|
||
if (truefalse) {
|
||
$("#top #menu .account .icon").html(
|
||
'<i class="fas fa-fw fa-spin fa-circle-notch"></i>'
|
||
);
|
||
$("#top #menu .account").css("opacity", 1);
|
||
} else {
|
||
$("#top #menu .account .icon").html('<i class="fas fa-fw fa-user"></i>');
|
||
$("#top #menu .account").css("opacity", 1);
|
||
}
|
||
}
|
||
|
||
function toggleResultWordsDisplay() {
|
||
if (resultVisible) {
|
||
if ($("#resultWordsHistory").stop(true, true).hasClass("hidden")) {
|
||
//show
|
||
|
||
if (!$("#showWordHistoryButton").hasClass("loaded")) {
|
||
$("#words").html(
|
||
`<div class="preloader"><i class="fas fa-fw fa-spin fa-circle-notch"></i></div>`
|
||
);
|
||
loadWordsHistory().then(() => {
|
||
$("#resultWordsHistory")
|
||
.removeClass("hidden")
|
||
.css("display", "none")
|
||
.slideDown(250);
|
||
});
|
||
} else {
|
||
$("#resultWordsHistory")
|
||
.removeClass("hidden")
|
||
.css("display", "none")
|
||
.slideDown(250);
|
||
}
|
||
} else {
|
||
//hide
|
||
|
||
$("#resultWordsHistory").slideUp(250, () => {
|
||
$("#resultWordsHistory").addClass("hidden");
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
async function loadWordsHistory() {
|
||
$("#resultWordsHistory .words").empty();
|
||
let wordsHTML = "";
|
||
for (let i = 0; i < inputHistory.length + 2; i++) {
|
||
let input = inputHistory[i];
|
||
let word = wordsList[i];
|
||
let wordEl = "";
|
||
try {
|
||
if (input === "") throw new Error("empty input word");
|
||
if (correctedHistory[i] !== undefined && correctedHistory[i] !== "") {
|
||
wordEl = `<div class='word' input="${correctedHistory[i]
|
||
.replace(/"/g, """)
|
||
.replace(/ /g, "_")}">`;
|
||
} else {
|
||
wordEl = `<div class='word' input="${input
|
||
.replace(/"/g, """)
|
||
.replace(/ /g, "_")}">`;
|
||
}
|
||
if (i === inputHistory.length - 1) {
|
||
//last word
|
||
let wordstats = {
|
||
correct: 0,
|
||
incorrect: 0,
|
||
missed: 0,
|
||
};
|
||
for (let c = 0; c < word.length; c++) {
|
||
if (c < inputHistory[i].length) {
|
||
//on char that still has a word list pair
|
||
if (inputHistory[i][c] == word[c]) {
|
||
wordstats.correct++;
|
||
} else {
|
||
wordstats.incorrect++;
|
||
}
|
||
} else {
|
||
//on char that is extra
|
||
wordstats.missed++;
|
||
}
|
||
}
|
||
if (wordstats.incorrect !== 0 || config.mode !== "time") {
|
||
if (input !== word) {
|
||
wordEl = `<div class='word error' input="${input
|
||
.replace(/"/g, """)
|
||
.replace(/ /g, "_")}">`;
|
||
}
|
||
}
|
||
} else {
|
||
if (input !== word) {
|
||
wordEl = `<div class='word error' input="${input
|
||
.replace(/"/g, """)
|
||
.replace(/ /g, "_")}">`;
|
||
}
|
||
}
|
||
|
||
let loop;
|
||
if (input.length > word.length) {
|
||
//input is longer - extra characters possible (loop over input)
|
||
loop = input.length;
|
||
} else {
|
||
//input is shorter or equal (loop over word list)
|
||
loop = word.length;
|
||
}
|
||
|
||
for (let c = 0; c < loop; c++) {
|
||
let correctedChar;
|
||
try {
|
||
correctedChar = correctedHistory[i][c];
|
||
} catch (e) {
|
||
correctedChar = undefined;
|
||
}
|
||
let extraCorrected = "";
|
||
if (
|
||
c + 1 === loop &&
|
||
correctedHistory[i] !== undefined &&
|
||
correctedHistory[i].length > input.length
|
||
) {
|
||
extraCorrected = "extraCorrected";
|
||
}
|
||
if (word[c] !== undefined) {
|
||
if (input[c] === word[c]) {
|
||
if (correctedChar === input[c] || correctedChar === undefined) {
|
||
wordEl += `<letter class="correct ${extraCorrected}">${word[c]}</letter>`;
|
||
} else {
|
||
wordEl +=
|
||
`<letter class="corrected ${extraCorrected}">` +
|
||
word[c] +
|
||
"</letter>";
|
||
}
|
||
} else {
|
||
if (input[c] === currentInput) {
|
||
wordEl +=
|
||
`<letter class='correct ${extraCorrected}'>` +
|
||
word[c] +
|
||
"</letter>";
|
||
} else if (input[c] === undefined) {
|
||
wordEl += "<letter>" + word[c] + "</letter>";
|
||
} else {
|
||
wordEl +=
|
||
`<letter class="incorrect ${extraCorrected}">` +
|
||
word[c] +
|
||
"</letter>";
|
||
}
|
||
}
|
||
} else {
|
||
wordEl += '<letter class="incorrect extra">' + input[c] + "</letter>";
|
||
}
|
||
}
|
||
wordEl += "</div>";
|
||
} catch (e) {
|
||
try {
|
||
wordEl = "<div class='word'>";
|
||
for (let c = 0; c < word.length; c++) {
|
||
wordEl += "<letter>" + word[c] + "</letter>";
|
||
}
|
||
wordEl += "</div>";
|
||
} catch {}
|
||
}
|
||
wordsHTML += wordEl;
|
||
}
|
||
$("#resultWordsHistory .words").html(wordsHTML);
|
||
$("#showWordHistoryButton").addClass("loaded");
|
||
return true;
|
||
}
|
||
|
||
function flipTestColors(tf) {
|
||
if (tf) {
|
||
$("#words").addClass("flipped");
|
||
} else {
|
||
$("#words").removeClass("flipped");
|
||
}
|
||
}
|
||
|
||
function applyColorfulMode(tc) {
|
||
if (tc) {
|
||
$("#words").addClass("colorfulMode");
|
||
} else {
|
||
$("#words").removeClass("colorfulMode");
|
||
}
|
||
}
|
||
|
||
function showEditTags(action, id, name) {
|
||
if (action === "add") {
|
||
$("#tagsWrapper #tagsEdit").attr("action", "add");
|
||
$("#tagsWrapper #tagsEdit .title").html("Add new tag");
|
||
$("#tagsWrapper #tagsEdit .button").html(`<i class="fas fa-plus"></i>`);
|
||
$("#tagsWrapper #tagsEdit input").val("");
|
||
$("#tagsWrapper #tagsEdit input").removeClass("hidden");
|
||
} else if (action === "edit") {
|
||
$("#tagsWrapper #tagsEdit").attr("action", "edit");
|
||
$("#tagsWrapper #tagsEdit").attr("tagid", id);
|
||
$("#tagsWrapper #tagsEdit .title").html("Edit tag name");
|
||
$("#tagsWrapper #tagsEdit .button").html(`<i class="fas fa-pen"></i>`);
|
||
$("#tagsWrapper #tagsEdit input").val(name);
|
||
$("#tagsWrapper #tagsEdit input").removeClass("hidden");
|
||
} else if (action === "remove") {
|
||
$("#tagsWrapper #tagsEdit").attr("action", "remove");
|
||
$("#tagsWrapper #tagsEdit").attr("tagid", id);
|
||
$("#tagsWrapper #tagsEdit .title").html("Remove tag " + name);
|
||
$("#tagsWrapper #tagsEdit .button").html(`<i class="fas fa-check"></i>`);
|
||
$("#tagsWrapper #tagsEdit input").addClass("hidden");
|
||
}
|
||
|
||
if ($("#tagsWrapper").hasClass("hidden")) {
|
||
$("#tagsWrapper")
|
||
.stop(true, true)
|
||
.css("opacity", 0)
|
||
.removeClass("hidden")
|
||
.animate({ opacity: 1 }, 100, () => {
|
||
$("#tagsWrapper #tagsEdit input").focus();
|
||
});
|
||
}
|
||
}
|
||
|
||
function hideEditTags() {
|
||
if (!$("#tagsWrapper").hasClass("hidden")) {
|
||
$("#tagsWrapper #tagsEdit").attr("action", "");
|
||
$("#tagsWrapper #tagsEdit").attr("tagid", "");
|
||
$("#tagsWrapper")
|
||
.stop(true, true)
|
||
.css("opacity", 1)
|
||
.animate(
|
||
{
|
||
opacity: 0,
|
||
},
|
||
100,
|
||
() => {
|
||
$("#tagsWrapper").addClass("hidden");
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
function updateTestModesNotice() {
|
||
let anim = false;
|
||
if ($(".pageTest #testModesNotice").text() === "") anim = true;
|
||
|
||
$(".pageTest #testModesNotice").empty();
|
||
|
||
if (sameWordset) {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" onClick="restartTest()" style="color:var(--error-color);"><i class="fas fa-sync-alt"></i>repeated</div>`
|
||
);
|
||
}
|
||
|
||
if (textHasTab) {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button"><i class="fas fa-long-arrow-alt-right"></i>shift + tab to restart</div>`
|
||
);
|
||
}
|
||
|
||
if (config.language === "english_1k" || config.language === "english_10k") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsLanguages"><i class="fas fa-globe-americas"></i>${config.language.replace(
|
||
"_",
|
||
" "
|
||
)}</div>`
|
||
);
|
||
}
|
||
|
||
if (config.difficulty === "expert") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsDifficulty"><i class="fas fa-star-half-alt"></i>expert</div>`
|
||
);
|
||
} else if (config.difficulty === "master") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsDifficulty"><i class="fas fa-star"></i>master</div>`
|
||
);
|
||
}
|
||
|
||
if (config.blindMode) {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" onClick="toggleBlindMode()"><i class="fas fa-eye-slash"></i>blind</div>`
|
||
);
|
||
}
|
||
|
||
if (config.paceCaret !== "off") {
|
||
let speed = "";
|
||
try {
|
||
speed = ` (${Math.round(paceCaret.wpm)} wpm)`;
|
||
} catch {}
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsPaceCaret"><i class="fas fa-tachometer-alt"></i>${
|
||
config.paceCaret === "average"
|
||
? "average"
|
||
: config.paceCaret === "pb"
|
||
? "pb"
|
||
: "custom"
|
||
} pace${speed}</div>`
|
||
);
|
||
}
|
||
|
||
if (config.minWpm !== "off") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsMinWpm"><i class="fas fa-bomb"></i>min ${config.minWpmCustomSpeed} wpm</div>`
|
||
);
|
||
}
|
||
|
||
if (config.minAcc !== "off") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsMinAcc"><i class="fas fa-bomb"></i>min ${config.minAccCustom}% acc</div>`
|
||
);
|
||
}
|
||
|
||
if (activeFunBox !== "none") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsFunbox"><i class="fas fa-gamepad"></i>${activeFunBox.replace(
|
||
/_/g,
|
||
" "
|
||
)}</div>`
|
||
);
|
||
}
|
||
|
||
if (config.confidenceMode === "on") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsConfidenceMode"><i class="fas fa-backspace"></i>confidence</div>`
|
||
);
|
||
}
|
||
if (config.confidenceMode === "max") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsConfidenceMode"><i class="fas fa-backspace"></i>max confidence</div>`
|
||
);
|
||
}
|
||
|
||
if (config.stopOnError != "off") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsStopOnError"><i class="fas fa-hand-paper"></i>stop on ${config.stopOnError}</div>`
|
||
);
|
||
}
|
||
|
||
if (config.layout !== "default") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsLayouts"><i class="fas fa-keyboard"></i>${config.layout}</div>`
|
||
);
|
||
}
|
||
|
||
let tagsString = "";
|
||
try {
|
||
db_getSnapshot().tags.forEach((tag) => {
|
||
if (tag.active === true) {
|
||
tagsString += tag.name + ", ";
|
||
}
|
||
});
|
||
|
||
if (tagsString !== "") {
|
||
$(".pageTest #testModesNotice").append(
|
||
`<div class="text-button" commands="commandsTags"><i class="fas fa-tag"></i>${tagsString.substring(
|
||
0,
|
||
tagsString.length - 2
|
||
)}</div>`
|
||
);
|
||
}
|
||
} catch {}
|
||
|
||
if (anim) {
|
||
$(".pageTest #testModesNotice")
|
||
.css("transition", "none")
|
||
.css("opacity", 0)
|
||
.animate(
|
||
{
|
||
opacity: 1,
|
||
},
|
||
125,
|
||
() => {
|
||
$(".pageTest #testModesNotice").css("transition", ".125s");
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
$("#tagsWrapper").click((e) => {
|
||
if ($(e.target).attr("id") === "tagsWrapper") {
|
||
hideEditTags();
|
||
}
|
||
});
|
||
|
||
$("#tagsWrapper #tagsEdit .button").click(() => {
|
||
tagsEdit();
|
||
});
|
||
|
||
$("#tagsWrapper #tagsEdit input").keypress((e) => {
|
||
if (e.keyCode == 13) {
|
||
tagsEdit();
|
||
}
|
||
});
|
||
|
||
function tagsEdit() {
|
||
let action = $("#tagsWrapper #tagsEdit").attr("action");
|
||
let inputVal = $("#tagsWrapper #tagsEdit input").val();
|
||
let tagid = $("#tagsWrapper #tagsEdit").attr("tagid");
|
||
hideEditTags();
|
||
if (action === "add") {
|
||
showBackgroundLoader();
|
||
CloudFunctions.addTag({
|
||
uid: firebase.auth().currentUser.uid,
|
||
name: inputVal,
|
||
}).then((e) => {
|
||
hideBackgroundLoader();
|
||
let status = e.data.resultCode;
|
||
if (status === 1) {
|
||
Notifications.add("Tag added", 1, 2);
|
||
db_getSnapshot().tags.push({
|
||
name: inputVal,
|
||
id: e.data.id,
|
||
});
|
||
updateResultEditTagsPanelButtons();
|
||
updateSettingsPage();
|
||
updateFilterTags();
|
||
} else if (status === -1) {
|
||
Notifications.add("Invalid tag name", 0);
|
||
} else if (status < -1) {
|
||
Notifications.add("Unknown error: " + e.data.message, -1);
|
||
}
|
||
});
|
||
} else if (action === "edit") {
|
||
showBackgroundLoader();
|
||
CloudFunctions.editTag({
|
||
uid: firebase.auth().currentUser.uid,
|
||
name: inputVal,
|
||
tagid: tagid,
|
||
}).then((e) => {
|
||
hideBackgroundLoader();
|
||
let status = e.data.resultCode;
|
||
if (status === 1) {
|
||
Notifications.add("Tag updated", 1);
|
||
db_getSnapshot().tags.forEach((tag) => {
|
||
if (tag.id === tagid) {
|
||
tag.name = inputVal;
|
||
}
|
||
});
|
||
updateResultEditTagsPanelButtons();
|
||
updateSettingsPage();
|
||
updateFilterTags();
|
||
} else if (status === -1) {
|
||
Notifications.add("Invalid tag name", 0);
|
||
} else if (status < -1) {
|
||
Notifications.add("Unknown error: " + e.data.message, -1);
|
||
}
|
||
});
|
||
} else if (action === "remove") {
|
||
showBackgroundLoader();
|
||
CloudFunctions.removeTag({
|
||
uid: firebase.auth().currentUser.uid,
|
||
tagid: tagid,
|
||
}).then((e) => {
|
||
hideBackgroundLoader();
|
||
let status = e.data.resultCode;
|
||
if (status === 1) {
|
||
Notifications.add("Tag removed", 1);
|
||
db_getSnapshot().tags.forEach((tag, index) => {
|
||
if (tag.id === tagid) {
|
||
db_getSnapshot().tags.splice(index, 1);
|
||
}
|
||
});
|
||
updateResultEditTagsPanelButtons();
|
||
updateSettingsPage();
|
||
updateFilterTags();
|
||
} else if (status < -1) {
|
||
Notifications.add("Unknown error: " + e.data.message, -1);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
function showCapsWarning() {
|
||
if ($("#capsWarning").hasClass("hidden")) {
|
||
$("#capsWarning").removeClass("hidden");
|
||
}
|
||
}
|
||
|
||
function hideCapsWarning() {
|
||
if (!$("#capsWarning").hasClass("hidden")) {
|
||
$("#capsWarning").addClass("hidden");
|
||
}
|
||
}
|
||
|
||
function showCustomTextPopup() {
|
||
if ($("#customTextPopupWrapper").hasClass("hidden")) {
|
||
if ($("#customTextPopup .check input").prop("checked")) {
|
||
$("#customTextPopup .inputs .randomInputFields").removeClass("hidden");
|
||
} else {
|
||
$("#customTextPopup .inputs .randomInputFields").addClass("hidden");
|
||
}
|
||
$("#customTextPopupWrapper")
|
||
.stop(true, true)
|
||
.css("opacity", 0)
|
||
.removeClass("hidden")
|
||
.animate({ opacity: 1 }, 100, () => {
|
||
let newtext = customText.text.join(" ");
|
||
newtext = newtext.replace(/\n /g, "\n");
|
||
$("#customTextPopup textarea").val(newtext);
|
||
$("#customTextPopup .wordcount input").val(customText.word);
|
||
$("#customTextPopup .time input").val(customText.time);
|
||
$("#customTextPopup textarea").focus();
|
||
});
|
||
}
|
||
}
|
||
|
||
function hideCustomTextPopup() {
|
||
if (!$("#customTextPopupWrapper").hasClass("hidden")) {
|
||
$("#customTextPopupWrapper")
|
||
.stop(true, true)
|
||
.css("opacity", 1)
|
||
.animate(
|
||
{
|
||
opacity: 0,
|
||
},
|
||
100,
|
||
(e) => {
|
||
$("#customTextPopupWrapper").addClass("hidden");
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
$("#customTextPopupWrapper").mousedown((e) => {
|
||
if ($(e.target).attr("id") === "customTextPopupWrapper") {
|
||
hideCustomTextPopup();
|
||
}
|
||
});
|
||
|
||
$("#customTextPopup .inputs .check input").change(() => {
|
||
if ($("#customTextPopup .check input").prop("checked")) {
|
||
$("#customTextPopup .inputs .randomInputFields").removeClass("hidden");
|
||
} else {
|
||
$("#customTextPopup .inputs .randomInputFields").addClass("hidden");
|
||
}
|
||
});
|
||
|
||
$("#customTextPopup textarea").keypress((e) => {
|
||
if (e.code === "Enter" && e.ctrlKey) {
|
||
$("#customTextPopup .button").click();
|
||
}
|
||
});
|
||
|
||
$("#customTextPopup .randomInputFields .wordcount input").keypress((e) => {
|
||
$("#customTextPopup .randomInputFields .time input").val("");
|
||
});
|
||
|
||
$("#customTextPopup .randomInputFields .time input").keypress((e) => {
|
||
$("#customTextPopup .randomInputFields .wordcount input").val("");
|
||
});
|
||
|
||
$("#customTextPopup .button").click(() => {
|
||
let text = $("#customTextPopup textarea").val();
|
||
text = text.trim();
|
||
// text = text.replace(/[\r]/gm, " ");
|
||
text = text.replace(/\\\\t/gm, "\t");
|
||
text = text.replace(/\\\\n/gm, "\n");
|
||
text = text.replace(/\\t/gm, "\t");
|
||
text = text.replace(/\\n/gm, "\n");
|
||
text = text.replace(/ +/gm, " ");
|
||
// text = text.replace(/(\r\n)+/g, "\r\n");
|
||
// text = text.replace(/(\n)+/g, "\n");
|
||
// text = text.replace(/(\r)+/g, "\r");
|
||
text = text.replace(/( *(\r\n|\r|\n) *)/g, "\n ");
|
||
if ($("#customTextPopup .typographyCheck input").prop("checked")) {
|
||
text = Misc.cleanTypographySymbols(text);
|
||
}
|
||
// text = Misc.remove_non_ascii(text);
|
||
text = text.replace(/[\u2060]/g, "");
|
||
text = text.split(" ");
|
||
customText.text = text;
|
||
customText.word = parseInt($("#customTextPopup .wordcount input").val());
|
||
customText.time = parseInt($("#customTextPopup .time input").val());
|
||
|
||
customText.isWordRandom =
|
||
$("#customTextPopup .check input").prop("checked") &&
|
||
!isNaN(customText.word);
|
||
customText.isTimeRandom =
|
||
$("#customTextPopup .check input").prop("checked") &&
|
||
!isNaN(customText.time);
|
||
|
||
if (
|
||
isNaN(customText.word) &&
|
||
isNaN(customText.time) &&
|
||
(customText.isTimeRandom || customText.isWordRandom)
|
||
) {
|
||
Notifications.add(
|
||
"You need to specify word count or time in seconds to start a random custom test.",
|
||
0,
|
||
5
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (
|
||
!isNaN(customText.word) &&
|
||
!isNaN(customText.time) &&
|
||
(customText.isTimeRandom || customText.isWordRandom)
|
||
) {
|
||
Notifications.add(
|
||
"You need to pick between word count or time in seconds to start a random custom test.",
|
||
0,
|
||
5
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (
|
||
(customText.isWordRandom && parseInt(customText.word) === 0) ||
|
||
(customText.isTimeRandom && parseInt(customText.time) === 0)
|
||
) {
|
||
Notifications.add(
|
||
"Infinite words! Make sure to use Bail Out from the command line to save your result.",
|
||
0,
|
||
7
|
||
);
|
||
}
|
||
|
||
manualRestart = true;
|
||
restartTest();
|
||
hideCustomTextPopup();
|
||
});
|
||
|
||
function showCustomMode2Popup(mode) {
|
||
if ($("#customMode2PopupWrapper").hasClass("hidden")) {
|
||
$("#customMode2PopupWrapper")
|
||
.stop(true, true)
|
||
.css("opacity", 0)
|
||
.removeClass("hidden")
|
||
.animate({ opacity: 1 }, 100, (e) => {
|
||
if (mode == "time") {
|
||
$("#customMode2Popup .text").text("Test length");
|
||
$("#customMode2Popup").attr("mode", "time");
|
||
} else if (mode == "words") {
|
||
$("#customMode2Popup .text").text("Word amount");
|
||
$("#customMode2Popup").attr("mode", "words");
|
||
}
|
||
$("#customMode2Popup input").focus().select();
|
||
});
|
||
}
|
||
}
|
||
|
||
function hideCustomMode2Popup() {
|
||
if (!$("#customMode2PopupWrapper").hasClass("hidden")) {
|
||
$("#customMode2PopupWrapper")
|
||
.stop(true, true)
|
||
.css("opacity", 1)
|
||
.animate(
|
||
{
|
||
opacity: 0,
|
||
},
|
||
100,
|
||
(e) => {
|
||
$("#customMode2PopupWrapper").addClass("hidden");
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
function playClickSound() {
|
||
if (config.playSoundOnClick === "off") return;
|
||
if (clickSounds === null) initClickSounds();
|
||
|
||
let rand = Math.floor(
|
||
Math.random() * clickSounds[config.playSoundOnClick].length
|
||
);
|
||
let randomSound = clickSounds[config.playSoundOnClick][rand];
|
||
randomSound.counter++;
|
||
if (randomSound.counter === 2) randomSound.counter = 0;
|
||
randomSound.sounds[randomSound.counter].currentTime = 0;
|
||
randomSound.sounds[randomSound.counter].play();
|
||
}
|
||
|
||
function playErrorSound() {
|
||
if (!config.playSoundOnError) return;
|
||
errorSound.currentTime = 0;
|
||
errorSound.play();
|
||
}
|
||
|
||
async function initPaceCaret() {
|
||
let mode2 = "";
|
||
if (config.mode === "time") {
|
||
mode2 = config.time;
|
||
} else if (config.mode === "words") {
|
||
mode2 = config.words;
|
||
} else if (config.mode === "custom") {
|
||
mode2 = "custom";
|
||
} else if (config.mode === "quote") {
|
||
mode2 = randomQuote.id;
|
||
}
|
||
let wpm;
|
||
if (config.paceCaret === "pb") {
|
||
wpm = await db_getLocalPB(
|
||
config.mode,
|
||
mode2,
|
||
config.punctuation,
|
||
config.language,
|
||
config.difficulty
|
||
);
|
||
} else if (config.paceCaret === "average") {
|
||
let mode2 = "";
|
||
if (config.mode === "time") {
|
||
mode2 = config.time;
|
||
} else if (config.mode === "words") {
|
||
mode2 = config.words;
|
||
} else if (config.mode === "custom") {
|
||
mode2 = "custom";
|
||
} else if (config.mode === "quote") {
|
||
mode2 = randomQuote.id;
|
||
}
|
||
wpm = await db_getUserAverageWpm10(
|
||
config.mode,
|
||
mode2,
|
||
config.punctuation,
|
||
config.language,
|
||
config.difficulty
|
||
);
|
||
console.log("avg pace " + wpm);
|
||
} else if (config.paceCaret === "custom") {
|
||
wpm = config.paceCaretCustomSpeed;
|
||
}
|
||
|
||
if (wpm < 1 || wpm == false || wpm == undefined || Number.isNaN(wpm)) {
|
||
paceCaret = null;
|
||
return;
|
||
}
|
||
|
||
let characters = wpm * 5;
|
||
let cps = characters / 60; //characters per step
|
||
let spc = 60 / characters; //seconds per character
|
||
|
||
paceCaret = {
|
||
wpm: wpm,
|
||
cps: cps,
|
||
spc: spc,
|
||
correction: 0,
|
||
currentWordIndex: 0,
|
||
currentLetterIndex: -1,
|
||
wordsStatus: {},
|
||
timeout: null,
|
||
};
|
||
|
||
updateTestModesNotice();
|
||
}
|
||
|
||
function movePaceCaret(expectedStepEnd) {
|
||
if (paceCaret === null || !testActive || resultVisible) {
|
||
return;
|
||
}
|
||
if ($("#paceCaret").hasClass("hidden")) {
|
||
$("#paceCaret").removeClass("hidden");
|
||
}
|
||
if ($("#paceCaret").hasClass("off")) {
|
||
return;
|
||
}
|
||
try {
|
||
paceCaret.currentLetterIndex++;
|
||
if (
|
||
paceCaret.currentLetterIndex >=
|
||
wordsList[paceCaret.currentWordIndex].length
|
||
) {
|
||
//go to the next word
|
||
paceCaret.currentLetterIndex = -1;
|
||
paceCaret.currentWordIndex++;
|
||
}
|
||
if (!config.blindMode) {
|
||
if (paceCaret.correction < 0) {
|
||
while (paceCaret.correction < 0) {
|
||
paceCaret.currentLetterIndex--;
|
||
if (paceCaret.currentLetterIndex <= -2) {
|
||
//go to the previous word
|
||
paceCaret.currentLetterIndex =
|
||
wordsList[paceCaret.currentWordIndex - 1].length - 1;
|
||
paceCaret.currentWordIndex--;
|
||
}
|
||
paceCaret.correction++;
|
||
}
|
||
} else if (paceCaret.correction > 0) {
|
||
while (paceCaret.correction > 0) {
|
||
paceCaret.currentLetterIndex++;
|
||
if (
|
||
paceCaret.currentLetterIndex >=
|
||
wordsList[paceCaret.currentWordIndex].length
|
||
) {
|
||
//go to the next word
|
||
paceCaret.currentLetterIndex = -1;
|
||
paceCaret.currentWordIndex++;
|
||
}
|
||
paceCaret.correction--;
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
//out of words
|
||
paceCaret = null;
|
||
$("#paceCaret").addClass("hidden");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
let caret = $("#paceCaret");
|
||
let currentLetter;
|
||
let newTop;
|
||
let newLeft;
|
||
try {
|
||
if (paceCaret.currentLetterIndex === -1) {
|
||
currentLetter = document
|
||
.querySelectorAll("#words .word")
|
||
[
|
||
paceCaret.currentWordIndex -
|
||
(currentWordIndex - currentWordElementIndex)
|
||
].querySelectorAll("letter")[0];
|
||
} else {
|
||
currentLetter = document
|
||
.querySelectorAll("#words .word")
|
||
[
|
||
paceCaret.currentWordIndex -
|
||
(currentWordIndex - currentWordElementIndex)
|
||
].querySelectorAll("letter")[paceCaret.currentLetterIndex];
|
||
}
|
||
newTop = currentLetter.offsetTop - $(currentLetter).height() / 20;
|
||
newLeft;
|
||
if (paceCaret.currentLetterIndex === -1) {
|
||
newLeft = currentLetter.offsetLeft;
|
||
} else {
|
||
newLeft =
|
||
currentLetter.offsetLeft +
|
||
$(currentLetter).width() -
|
||
caret.width() / 2;
|
||
}
|
||
caret.removeClass("hidden");
|
||
} catch (e) {
|
||
caret.addClass("hidden");
|
||
}
|
||
|
||
let smoothlinescroll = $("#words .smoothScroller").height();
|
||
if (smoothlinescroll === undefined) smoothlinescroll = 0;
|
||
|
||
$("#paceCaret").css({
|
||
top: newTop - smoothlinescroll,
|
||
});
|
||
|
||
let duration = expectedStepEnd - performance.now();
|
||
|
||
if (config.smoothCaret) {
|
||
caret.stop(true, true).animate(
|
||
{
|
||
left: newLeft,
|
||
},
|
||
duration,
|
||
"linear"
|
||
);
|
||
} else {
|
||
caret.stop(true, true).animate(
|
||
{
|
||
left: newLeft,
|
||
},
|
||
0,
|
||
"linear"
|
||
);
|
||
}
|
||
paceCaret.timeout = setTimeout(() => {
|
||
try {
|
||
movePaceCaret(expectedStepEnd + paceCaret.spc * 1000);
|
||
} catch (e) {
|
||
paceCaret = null;
|
||
}
|
||
}, duration);
|
||
} catch (e) {
|
||
console.error(e);
|
||
$("#paceCaret").addClass("hidden");
|
||
}
|
||
}
|
||
|
||
function resetPaceCaret() {
|
||
if (config.paceCaret === "off") return;
|
||
if (!$("#paceCaret").hasClass("hidden")) {
|
||
$("#paceCaret").addClass("hidden");
|
||
}
|
||
|
||
let caret = $("#paceCaret");
|
||
let firstLetter = document
|
||
.querySelector("#words .word")
|
||
.querySelector("letter");
|
||
|
||
caret.stop(true, true).animate(
|
||
{
|
||
top: firstLetter.offsetTop - $(firstLetter).height() / 4,
|
||
left: firstLetter.offsetLeft,
|
||
},
|
||
0,
|
||
"linear"
|
||
);
|
||
}
|
||
|
||
$("#customMode2PopupWrapper").click((e) => {
|
||
if ($(e.target).attr("id") === "customMode2PopupWrapper") {
|
||
hideCustomMode2Popup();
|
||
}
|
||
});
|
||
|
||
$("#customMode2Popup input").keypress((e) => {
|
||
if (e.keyCode == 13) {
|
||
applyMode2Popup();
|
||
}
|
||
});
|
||
|
||
$("#customMode2Popup .button").click(() => {
|
||
applyMode2Popup();
|
||
});
|
||
|
||
function updateKeytips() {
|
||
if (config.swapEscAndTab) {
|
||
$(".pageSettings .tip").html(`
|
||
tip: You can also change all these settings quickly using the
|
||
command line (
|
||
<key>tab</key>
|
||
)`);
|
||
$("#bottom .keyTips").html(`
|
||
<key>esc</key> - restart test<br>
|
||
<key>tab</key> - command line`);
|
||
} else {
|
||
$(".pageSettings .tip").html(`
|
||
tip: You can also change all these settings quickly using the
|
||
command line (
|
||
<key>esc</key>
|
||
)`);
|
||
$("#bottom .keyTips").html(`
|
||
<key>tab</key> - restart test<br>
|
||
<key>esc</key> - command line`);
|
||
}
|
||
}
|
||
|
||
function applyMode2Popup() {
|
||
let mode = $("#customMode2Popup").attr("mode");
|
||
let val = parseInt($("#customMode2Popup input").val());
|
||
|
||
if (mode == "time") {
|
||
if (val !== null && !isNaN(val) && val >= 0) {
|
||
setTimeConfig(val);
|
||
manualRestart = true;
|
||
restartTest();
|
||
if (val >= 1800) {
|
||
Notifications.add("Stay safe and take breaks!", 0);
|
||
} else if (val == 0) {
|
||
Notifications.add(
|
||
"Infinite time! Make sure to use Bail Out from the command line to save your result.",
|
||
0,
|
||
7
|
||
);
|
||
}
|
||
} else {
|
||
Notifications.add("Custom time must be at least 1", 0);
|
||
}
|
||
} else if (mode == "words") {
|
||
if (val !== null && !isNaN(val) && val >= 0) {
|
||
setWordCount(val);
|
||
manualRestart = true;
|
||
restartTest();
|
||
if (val > 2000) {
|
||
Notifications.add("Stay safe and take breaks!", 0);
|
||
} else if (val == 0) {
|
||
Notifications.add(
|
||
"Infinite words! Make sure to use Bail Out from the command line to save your result.",
|
||
0,
|
||
7
|
||
);
|
||
}
|
||
} else {
|
||
Notifications.add("Custom word amount must be at least 1", 0);
|
||
}
|
||
}
|
||
|
||
hideCustomMode2Popup();
|
||
}
|
||
|
||
$(document).on("click", "#top .logo", (e) => {
|
||
changePage("test");
|
||
});
|
||
|
||
$(document).on("click", "#top .config .wordCount .text-button", (e) => {
|
||
const wrd = $(e.currentTarget).attr("wordCount");
|
||
if (wrd == "custom") {
|
||
showCustomMode2Popup("words");
|
||
} else {
|
||
setWordCount(wrd);
|
||
manualRestart = true;
|
||
restartTest();
|
||
}
|
||
});
|
||
|
||
$(document).on("click", "#top .config .time .text-button", (e) => {
|
||
let mode = $(e.currentTarget).attr("timeConfig");
|
||
if (mode == "custom") {
|
||
showCustomMode2Popup("time");
|
||
} else {
|
||
setTimeConfig(mode);
|
||
manualRestart = true;
|
||
|
||
restartTest();
|
||
}
|
||
});
|
||
|
||
$(document).on("click", "#top .config .quoteLength .text-button", (e) => {
|
||
let len = $(e.currentTarget).attr("quoteLength");
|
||
setQuoteLength(len);
|
||
manualRestart = true;
|
||
restartTest();
|
||
});
|
||
|
||
$(document).on("click", "#top .config .customText .text-button", () => {
|
||
showCustomTextPopup();
|
||
});
|
||
|
||
$(document).on("click", "#top .config .punctuationMode .text-button", () => {
|
||
togglePunctuation();
|
||
manualRestart = true;
|
||
|
||
restartTest();
|
||
});
|
||
|
||
$(document).on("click", "#top .config .numbersMode .text-button", () => {
|
||
toggleNumbers();
|
||
manualRestart = true;
|
||
|
||
restartTest();
|
||
});
|
||
|
||
$("#wordsWrapper").on("click", () => {
|
||
focusWords();
|
||
});
|
||
|
||
$(document).on("click", "#top .config .mode .text-button", (e) => {
|
||
if ($(e.currentTarget).hasClass("active")) return;
|
||
const mode = $(e.currentTarget).attr("mode");
|
||
setMode(mode);
|
||
manualRestart = true;
|
||
restartTest();
|
||
});
|
||
|
||
$(document).on("click", "#top #menu .icon-button", (e) => {
|
||
if ($(e.currentTarget).hasClass("discord")) return;
|
||
if ($(e.currentTarget).hasClass("leaderboards")) {
|
||
showLeaderboards();
|
||
} else {
|
||
const href = $(e.currentTarget).attr("href");
|
||
manualRestart = true;
|
||
changePage(href.replace("/", ""));
|
||
}
|
||
});
|
||
|
||
$(window).on("popstate", (e) => {
|
||
let state = e.originalEvent.state;
|
||
if (state == "" || state == "/") {
|
||
// show test
|
||
changePage("test");
|
||
} else if (state == "about") {
|
||
// show about
|
||
changePage("about");
|
||
} else if (state == "account" || state == "login") {
|
||
if (firebase.auth().currentUser) {
|
||
changePage("account");
|
||
} else {
|
||
changePage("login");
|
||
}
|
||
}
|
||
});
|
||
|
||
$(document).on("keypress", "#restartTestButton", (event) => {
|
||
if (event.keyCode == 13) {
|
||
if (
|
||
(config.mode === "words" && config.words < 1000) ||
|
||
(config.mode === "time" && config.time < 3600) ||
|
||
config.mode === "quote" ||
|
||
(config.mode === "custom" &&
|
||
customText.isWordRandom &&
|
||
customText.word < 1000) ||
|
||
(config.mode === "custom" &&
|
||
customText.isTimeRandom &&
|
||
customText.time < 3600) ||
|
||
(config.mode === "custom" &&
|
||
!customText.isWordRandom &&
|
||
customText.text.length < 1000)
|
||
) {
|
||
if (testActive) {
|
||
let testNow = performance.now();
|
||
let testSeconds = Misc.roundTo2((testNow - testStart) / 1000);
|
||
let afkseconds = keypressPerSecond.filter(
|
||
(x) => x.count == 0 && x.mod == 0
|
||
).length;
|
||
incompleteTestSeconds += testSeconds - afkseconds;
|
||
restartCount++;
|
||
}
|
||
if (resultCalculating) return;
|
||
restartTest();
|
||
} else {
|
||
Notifications.add("Quick restart disabled for long tests", 0);
|
||
}
|
||
}
|
||
});
|
||
|
||
$(document.body).on("click", "#restartTestButton", () => {
|
||
manualRestart = true;
|
||
if (resultCalculating) return;
|
||
restartTest();
|
||
});
|
||
|
||
function initPractiseMissedWords() {
|
||
let mode = modeBeforePractise === null ? config.mode : modeBeforePractise;
|
||
let punctuation =
|
||
punctuationBeforePractise === null
|
||
? config.punctuation
|
||
: punctuationBeforePractise;
|
||
let numbers =
|
||
numbersBeforePractise === null ? config.numbers : numbersBeforePractise;
|
||
setMode("custom");
|
||
let newCustomText = [];
|
||
Object.keys(missedWords).forEach((missedWord) => {
|
||
for (let i = 0; i < missedWords[missedWord]; i++) {
|
||
newCustomText.push(missedWord);
|
||
}
|
||
});
|
||
customText.text = newCustomText;
|
||
customText.isWordRandom = true;
|
||
customText.word = 50;
|
||
|
||
modeBeforePractise = null;
|
||
punctuationBeforePractise = null;
|
||
numbersBeforePractise = null;
|
||
restartTest();
|
||
modeBeforePractise = mode;
|
||
punctuationBeforePractise = punctuation;
|
||
numbersBeforePractise = numbers;
|
||
}
|
||
|
||
$(document).on("keypress", "#practiseMissedWordsButton", (event) => {
|
||
if (event.keyCode == 13) {
|
||
if (Object.keys(missedWords).length > 0) {
|
||
initPractiseMissedWords();
|
||
} else {
|
||
Notifications.add("You haven't missed any words.", 0);
|
||
}
|
||
}
|
||
});
|
||
|
||
$(document.body).on("click", "#practiseMissedWordsButton", () => {
|
||
if (Object.keys(missedWords).length > 0) {
|
||
initPractiseMissedWords();
|
||
} else {
|
||
Notifications.add("You haven't missed any words.", 0);
|
||
}
|
||
});
|
||
|
||
$(document).on("keypress", "#nextTestButton", (event) => {
|
||
if (event.keyCode == 13) {
|
||
restartTest();
|
||
}
|
||
});
|
||
|
||
$(document.body).on("click", "#nextTestButton", () => {
|
||
manualRestart = true;
|
||
restartTest();
|
||
});
|
||
|
||
$(document).on("keypress", "#showWordHistoryButton", (event) => {
|
||
if (event.keyCode == 13) {
|
||
toggleResultWordsDisplay();
|
||
}
|
||
});
|
||
|
||
$(document.body).on("click", "#showWordHistoryButton", () => {
|
||
toggleResultWordsDisplay();
|
||
});
|
||
|
||
$(document.body).on("click", "#restartTestButtonWithSameWordset", () => {
|
||
manualRestart = true;
|
||
|
||
restartTest(true);
|
||
});
|
||
|
||
$(document).on("keypress", "#restartTestButtonWithSameWordset", (event) => {
|
||
if (event.keyCode == 13) {
|
||
restartTest(true);
|
||
}
|
||
});
|
||
|
||
$(document.body).on("click", "#copyResultToClipboardButton", () => {
|
||
copyResultToClipboard();
|
||
});
|
||
|
||
$(document.body).on("click", ".version", () => {
|
||
$("#versionHistoryWrapper")
|
||
.css("opacity", 0)
|
||
.removeClass("hidden")
|
||
.animate({ opacity: 1 }, 125);
|
||
});
|
||
|
||
$(document.body).on("click", "#versionHistoryWrapper", () => {
|
||
$("#versionHistoryWrapper")
|
||
.css("opacity", 1)
|
||
.animate({ opacity: 0 }, 125, () => {
|
||
$("#versionHistoryWrapper").addClass("hidden");
|
||
});
|
||
});
|
||
|
||
$(document.body).on("click", "#supportMeButton", () => {
|
||
$("#supportMeWrapper")
|
||
.css("opacity", 0)
|
||
.removeClass("hidden")
|
||
.animate({ opacity: 1 }, 125);
|
||
});
|
||
|
||
$(document.body).on("click", "#supportMeWrapper", () => {
|
||
$("#supportMeWrapper")
|
||
.css("opacity", 1)
|
||
.animate({ opacity: 0 }, 125, () => {
|
||
$("#supportMeWrapper").addClass("hidden");
|
||
});
|
||
});
|
||
|
||
$(document.body).on("click", "#supportMeWrapper .button.ads", () => {
|
||
currentCommands.push(commandsEnableAds);
|
||
showCommandLine();
|
||
$("#supportMeWrapper")
|
||
.css("opacity", 1)
|
||
.animate({ opacity: 0 }, 125, () => {
|
||
$("#supportMeWrapper").addClass("hidden");
|
||
});
|
||
});
|
||
|
||
$(document.body).on("click", "#supportMeWrapper a.button", () => {
|
||
$("#supportMeWrapper")
|
||
.css("opacity", 1)
|
||
.animate({ opacity: 0 }, 125, () => {
|
||
$("#supportMeWrapper").addClass("hidden");
|
||
});
|
||
});
|
||
|
||
$(document.body).on("click", ".pageAbout .aboutEnableAds", () => {
|
||
currentCommands.push(commandsEnableAds);
|
||
showCommandLine();
|
||
});
|
||
|
||
$("#wordsInput").keypress((event) => {
|
||
event.preventDefault();
|
||
});
|
||
|
||
let outOfFocusTimeouts = [];
|
||
|
||
function clearTimeouts(timeouts) {
|
||
timeouts.forEach((to) => {
|
||
clearTimeout(to);
|
||
to = null;
|
||
});
|
||
}
|
||
|
||
$("#wordsInput").on("focus", () => {
|
||
if (!resultVisible && config.showOutOfFocusWarning) {
|
||
$("#words").css("transition", "none").removeClass("blurred");
|
||
$(".outOfFocusWarning").addClass("hidden");
|
||
clearTimeouts(outOfFocusTimeouts);
|
||
}
|
||
showCaret();
|
||
});
|
||
|
||
$("#wordsInput").on("focusout", () => {
|
||
if (!resultVisible && config.showOutOfFocusWarning) {
|
||
outOfFocusTimeouts.push(
|
||
setTimeout(() => {
|
||
$("#words").css("transition", "0.25s").addClass("blurred");
|
||
$(".outOfFocusWarning").removeClass("hidden");
|
||
}, 1000)
|
||
);
|
||
}
|
||
hideCaret();
|
||
});
|
||
|
||
$(window).resize(() => {
|
||
updateCaretPosition();
|
||
});
|
||
|
||
$(document).mousemove(function (event) {
|
||
if (
|
||
$("#top").hasClass("focus") &&
|
||
(event.originalEvent.movementX > 0 || event.originalEvent.movementY > 0)
|
||
) {
|
||
setFocus(false);
|
||
}
|
||
});
|
||
|
||
$(document).on("click", "#testModesNotice .text-button", (event) => {
|
||
let commands = eval($(event.currentTarget).attr("commands"));
|
||
if (commands !== undefined) {
|
||
if ($(event.currentTarget).attr("commands") === "commandsTags") {
|
||
updateCommandsTagsList();
|
||
}
|
||
currentCommands.push(commands);
|
||
showCommandLine();
|
||
}
|
||
});
|
||
|
||
$(document).on("click", "#commandLineMobileButton", () => {
|
||
showCommandLine();
|
||
});
|
||
|
||
let dontInsertSpace = false;
|
||
|
||
$(document).keyup(() => {
|
||
if (resultVisible) return;
|
||
let now = performance.now();
|
||
let diff = Math.abs(keypressStats.duration.current - now);
|
||
if (keypressStats.duration.current !== -1) {
|
||
keypressStats.duration.array.push(diff);
|
||
}
|
||
keypressStats.duration.current = now;
|
||
Monkey.stop();
|
||
});
|
||
|
||
$(document).keydown(function (event) {
|
||
if (!resultVisible) {
|
||
let now = performance.now();
|
||
let diff = Math.abs(keypressStats.spacing.current - now);
|
||
if (keypressStats.spacing.current !== -1) {
|
||
keypressStats.spacing.array.push(diff);
|
||
}
|
||
keypressStats.spacing.current = now;
|
||
}
|
||
|
||
Monkey.type();
|
||
|
||
//autofocus
|
||
let pageTestActive = !$(".pageTest").hasClass("hidden");
|
||
let commandLineVisible = !$("#commandLineWrapper").hasClass("hidden");
|
||
let wordsFocused = $("#wordsInput").is(":focus");
|
||
let modePopupVisible =
|
||
!$("#customTextPopupWrapper").hasClass("hidden") ||
|
||
!$("#customMode2PopupWrapper").hasClass("hidden");
|
||
if (
|
||
pageTestActive &&
|
||
!commandLineVisible &&
|
||
!modePopupVisible &&
|
||
!resultVisible &&
|
||
!wordsFocused &&
|
||
event.key !== "Enter"
|
||
) {
|
||
focusWords();
|
||
if (config.showOutOfFocusWarning) return;
|
||
}
|
||
|
||
//tab
|
||
if (
|
||
(event.key == "Tab" && !config.swapEscAndTab) ||
|
||
(event.key == "Escape" && config.swapEscAndTab)
|
||
) {
|
||
handleTab(event);
|
||
// event.preventDefault();
|
||
}
|
||
|
||
//blocking firefox from going back in history with backspace
|
||
if (event.key === "Backspace" && wordsFocused) {
|
||
let t = /INPUT|SELECT|TEXTAREA/i;
|
||
if (
|
||
!t.test(event.target.tagName) ||
|
||
event.target.disabled ||
|
||
event.target.readOnly
|
||
) {
|
||
event.preventDefault();
|
||
}
|
||
}
|
||
|
||
keypressStats.duration.current = performance.now();
|
||
try {
|
||
if (
|
||
!config.capsLockBackspace &&
|
||
event.originalEvent.getModifierState("CapsLock")
|
||
) {
|
||
showCapsWarning();
|
||
} else {
|
||
hideCapsWarning();
|
||
}
|
||
} catch {}
|
||
|
||
//backspace
|
||
const isBackspace =
|
||
event.key === "Backspace" ||
|
||
(config.capsLockBackspace && event.key === "CapsLock");
|
||
if (isBackspace && wordsFocused) {
|
||
handleBackspace(event);
|
||
}
|
||
|
||
if (event.key === "Enter" && activeFunBox === "58008" && wordsFocused) {
|
||
event.key = " ";
|
||
}
|
||
|
||
//space or enter
|
||
if (event.key === " " && wordsFocused) {
|
||
handleSpace(event, false);
|
||
}
|
||
|
||
if (wordsFocused && !commandLineVisible) {
|
||
handleAlpha(event);
|
||
}
|
||
|
||
let acc = Misc.roundTo2(
|
||
(accuracyStats.correct /
|
||
(accuracyStats.correct + accuracyStats.incorrect)) *
|
||
100
|
||
);
|
||
updateLiveAcc(acc);
|
||
});
|
||
|
||
function handleTab(event) {
|
||
if (resultCalculating) {
|
||
event.preventDefault();
|
||
}
|
||
if ($("#customTextPopup .textarea").is(":focus")) {
|
||
event.preventDefault();
|
||
|
||
let area = $("#customTextPopup .textarea")[0];
|
||
|
||
var start = area.selectionStart;
|
||
var end = area.selectionEnd;
|
||
|
||
// set textarea value to: text before caret + tab + text after caret
|
||
area.value =
|
||
area.value.substring(0, start) + "\t" + area.value.substring(end);
|
||
|
||
// put caret at right position again
|
||
area.selectionStart = area.selectionEnd = start + 1;
|
||
|
||
// event.preventDefault();
|
||
// $("#customTextPopup .textarea").val(
|
||
// $("#customTextPopup .textarea").val() + "\t"
|
||
// );
|
||
return;
|
||
} else if (
|
||
!event.ctrlKey &&
|
||
((!event.shiftKey && !textHasTab) ||
|
||
(event.shiftKey && textHasTab) ||
|
||
resultVisible) &&
|
||
config.quickTab &&
|
||
!$(".pageLogin").hasClass("active") &&
|
||
!resultCalculating &&
|
||
$("#commandLineWrapper").hasClass("hidden") &&
|
||
$("#simplePopupWrapper").hasClass("hidden")
|
||
) {
|
||
event.preventDefault();
|
||
if ($(".pageTest").hasClass("active")) {
|
||
if (
|
||
(config.mode === "words" && config.words < 1000) ||
|
||
(config.mode === "time" && config.time < 3600) ||
|
||
config.mode === "quote" ||
|
||
(config.mode === "custom" &&
|
||
customText.isWordRandom &&
|
||
customText.word < 1000) ||
|
||
(config.mode === "custom" &&
|
||
customText.isTimeRandom &&
|
||
customText.time < 3600) ||
|
||
(config.mode === "custom" &&
|
||
!customText.isWordRandom &&
|
||
customText.text.length < 1000)
|
||
) {
|
||
if (testActive) {
|
||
let testNow = performance.now();
|
||
let testSeconds = Misc.roundTo2((testNow - testStart) / 1000);
|
||
let afkseconds = keypressPerSecond.filter(
|
||
(x) => x.count == 0 && x.mod == 0
|
||
).length;
|
||
incompleteTestSeconds += testSeconds - afkseconds;
|
||
restartCount++;
|
||
}
|
||
restartTest();
|
||
} else {
|
||
Notifications.add("Quick restart disabled for long tests", 0);
|
||
}
|
||
} else {
|
||
changePage("test");
|
||
}
|
||
} else if (
|
||
!config.quickTab &&
|
||
textHasTab &&
|
||
event.shiftKey &&
|
||
!resultVisible
|
||
) {
|
||
event.preventDefault();
|
||
$("#restartTestButton").focus();
|
||
}
|
||
}
|
||
|
||
function handleBackspace(event) {
|
||
event.preventDefault();
|
||
if (!testActive) return;
|
||
if (
|
||
currentInput == "" &&
|
||
inputHistory.length > 0 &&
|
||
currentWordElementIndex > 0
|
||
) {
|
||
//if nothing is inputted and its not the first word
|
||
if (
|
||
(inputHistory[currentWordIndex - 1] == wordsList[currentWordIndex - 1] &&
|
||
!config.freedomMode) ||
|
||
$($(".word")[currentWordIndex - 1]).hasClass("hidden")
|
||
) {
|
||
return;
|
||
} else {
|
||
if (config.confidenceMode === "on" || config.confidenceMode === "max")
|
||
return;
|
||
if (event["ctrlKey"] || event["altKey"]) {
|
||
currentInput = "";
|
||
inputHistory.pop();
|
||
correctedHistory.pop();
|
||
} else {
|
||
currentInput = inputHistory.pop();
|
||
currentCorrected = correctedHistory.pop();
|
||
if (activeFunBox === "nospace") {
|
||
currentInput = currentInput.substring(0, currentInput.length - 1);
|
||
}
|
||
}
|
||
currentWordIndex--;
|
||
currentWordElementIndex--;
|
||
updateActiveElement();
|
||
updateWordElement(!config.blindMode);
|
||
}
|
||
} else {
|
||
if (config.confidenceMode === "max") return;
|
||
if (event["ctrlKey"] || event["altKey"]) {
|
||
let split = currentInput.replace(/ +/g, " ").split(" ");
|
||
if (split[split.length - 1] == "") {
|
||
split.pop();
|
||
}
|
||
let addspace = false;
|
||
if (split.length > 1) {
|
||
addspace = true;
|
||
}
|
||
split.pop();
|
||
currentInput = split.join(" ");
|
||
|
||
if (addspace) {
|
||
currentInput += " ";
|
||
}
|
||
} else if (event.metaKey) {
|
||
currentInput = "";
|
||
} else {
|
||
currentInput = currentInput.substring(0, currentInput.length - 1);
|
||
}
|
||
updateWordElement(!config.blindMode);
|
||
}
|
||
playClickSound();
|
||
if (config.keymapMode === "react") {
|
||
flashPressedKeymapKey(event.code, true);
|
||
} else if (config.keymapMode === "next") {
|
||
updateHighlightedKeymapKey();
|
||
}
|
||
updateCaretPosition();
|
||
}
|
||
|
||
function handleSpace(event, isEnter) {
|
||
if (!testActive) return;
|
||
if (currentInput === "") return;
|
||
let nextWord = wordsList[currentWordIndex + 1];
|
||
// if ((isEnter && nextWord !== "\n") && (isEnter && activeFunBox !== "58008")) return;
|
||
// if (!isEnter && nextWord === "\n") return;
|
||
event.preventDefault();
|
||
let currentWord = wordsList[currentWordIndex];
|
||
if (activeFunBox === "layoutfluid" && config.mode !== "time") {
|
||
const layouts = ["qwerty", "dvorak", "colemak"];
|
||
let index = 0;
|
||
let outof = wordsList.length;
|
||
index = Math.floor((inputHistory.length + 1) / (outof / 3));
|
||
if (config.layout !== layouts[index] && layouts[index] !== undefined) {
|
||
Notifications.add(`--- !!! ${layouts[index]} !!! ---`, 0);
|
||
}
|
||
setLayout(layouts[index]);
|
||
setKeymapLayout(layouts[index]);
|
||
updateHighlightedKeymapKey();
|
||
settingsGroups.layout.updateButton();
|
||
}
|
||
if (config.blindMode) $("#words .word.active letter").addClass("correct");
|
||
dontInsertSpace = true;
|
||
if (currentWord == currentInput) {
|
||
//correct word
|
||
if (
|
||
paceCaret !== null &&
|
||
paceCaret.wordsStatus[currentWordIndex] === true &&
|
||
!config.blindMode
|
||
) {
|
||
paceCaret.wordsStatus[currentWordIndex] = undefined;
|
||
paceCaret.correction -= currentWord.length + 1;
|
||
}
|
||
accuracyStats.correct++;
|
||
inputHistory.push(currentInput);
|
||
currentInput = "";
|
||
currentWordIndex++;
|
||
currentWordElementIndex++;
|
||
updateActiveElement();
|
||
updateCaretPosition();
|
||
currentKeypress.count++;
|
||
currentKeypress.words.push(currentWordIndex);
|
||
if (activeFunBox !== "nospace") {
|
||
playClickSound();
|
||
}
|
||
} else {
|
||
//incorrect word
|
||
if (
|
||
paceCaret !== null &&
|
||
paceCaret.wordsStatus[currentWordIndex] === undefined &&
|
||
!config.blindMode
|
||
) {
|
||
paceCaret.wordsStatus[currentWordIndex] = true;
|
||
paceCaret.correction += currentWord.length + 1;
|
||
}
|
||
if (activeFunBox !== "nospace") {
|
||
if (!config.playSoundOnError || config.blindMode) {
|
||
playClickSound();
|
||
} else {
|
||
playErrorSound();
|
||
}
|
||
}
|
||
accuracyStats.incorrect++;
|
||
let cil = currentInput.length;
|
||
if (cil <= wordsList[currentWordIndex].length) {
|
||
if (cil >= currentCorrected.length) {
|
||
currentCorrected += "_";
|
||
} else {
|
||
currentCorrected =
|
||
currentCorrected.substring(0, cil) +
|
||
"_" +
|
||
currentCorrected.substring(cil + 1);
|
||
}
|
||
}
|
||
if (config.stopOnError != "off") {
|
||
if (config.difficulty == "expert" || config.difficulty == "master") {
|
||
//failed due to diff when pressing space
|
||
failTest();
|
||
return;
|
||
}
|
||
if (config.stopOnError == "word") {
|
||
currentInput += " ";
|
||
updateWordElement(true);
|
||
updateCaretPosition();
|
||
}
|
||
return;
|
||
}
|
||
inputHistory.push(currentInput);
|
||
highlightBadWord(currentWordElementIndex, !config.blindMode);
|
||
currentInput = "";
|
||
currentWordIndex++;
|
||
currentWordElementIndex++;
|
||
updateActiveElement();
|
||
updateCaretPosition();
|
||
currentKeypress.count++;
|
||
currentKeypress.words.push(currentWordIndex);
|
||
if (config.difficulty == "expert" || config.difficulty == "master") {
|
||
failTest();
|
||
return;
|
||
} else if (currentWordIndex == wordsList.length) {
|
||
//submitted last word that is incorrect
|
||
lastSecondNotRound = true;
|
||
showResult();
|
||
return;
|
||
}
|
||
}
|
||
|
||
correctedHistory.push(currentCorrected);
|
||
currentCorrected = "";
|
||
|
||
if (
|
||
!config.showAllLines ||
|
||
config.mode == "time" ||
|
||
(customText.isWordRandom && customText.word == 0) ||
|
||
customText.isTimeRandom
|
||
) {
|
||
let currentTop = Math.floor(
|
||
document.querySelectorAll("#words .word")[currentWordElementIndex - 1]
|
||
.offsetTop
|
||
);
|
||
let nextTop;
|
||
try {
|
||
nextTop = Math.floor(
|
||
document.querySelectorAll("#words .word")[currentWordElementIndex]
|
||
.offsetTop
|
||
);
|
||
} catch (e) {
|
||
nextTop = 0;
|
||
}
|
||
|
||
if ((nextTop > currentTop || activeWordJumped) && !lineTransition) {
|
||
//last word of the line
|
||
if (currentTestLine > 0) {
|
||
let hideBound = currentTop;
|
||
if (activeWordJumped) {
|
||
hideBound = activeWordTopBeforeJump;
|
||
}
|
||
activeWordJumped = false;
|
||
|
||
let toHide = [];
|
||
let wordElements = $("#words .word");
|
||
for (let i = 0; i < currentWordElementIndex; i++) {
|
||
if ($(wordElements[i]).hasClass("hidden")) continue;
|
||
let forWordTop = Math.floor(wordElements[i].offsetTop);
|
||
if (forWordTop < hideBound - 10) {
|
||
toHide.push($($("#words .word")[i]));
|
||
}
|
||
}
|
||
const wordHeight = $(document.querySelector(".word")).outerHeight(true);
|
||
if (config.smoothLineScroll && toHide.length > 0) {
|
||
lineTransition = true;
|
||
$("#words").prepend(
|
||
`<div class="smoothScroller" style="position: fixed;height:${wordHeight}px;width:100%"></div>`
|
||
);
|
||
$("#words .smoothScroller").animate(
|
||
{
|
||
height: 0,
|
||
},
|
||
125,
|
||
() => {
|
||
$("#words .smoothScroller").remove();
|
||
}
|
||
);
|
||
$("#paceCaret").animate(
|
||
{
|
||
top: document.querySelector("#paceCaret").offsetTop - wordHeight,
|
||
},
|
||
125
|
||
);
|
||
$("#words").animate(
|
||
{
|
||
marginTop: `-${wordHeight}px`,
|
||
},
|
||
125,
|
||
() => {
|
||
activeWordTop = document.querySelector("#words .active")
|
||
.offsetTop;
|
||
|
||
currentWordElementIndex -= toHide.length;
|
||
lineTransition = false;
|
||
toHide.forEach((el) => el.remove());
|
||
$("#words").css("marginTop", "0");
|
||
}
|
||
);
|
||
} else {
|
||
toHide.forEach((el) => el.remove());
|
||
currentWordElementIndex -= toHide.length;
|
||
$("#paceCaret").css({
|
||
top: document.querySelector("#paceCaret").offsetTop - wordHeight,
|
||
});
|
||
}
|
||
}
|
||
currentTestLine++;
|
||
}
|
||
} //end of line wrap
|
||
|
||
updateCaretPosition();
|
||
|
||
if (config.keymapMode === "react") {
|
||
flashPressedKeymapKey(event.code, true);
|
||
} else if (config.keymapMode === "next") {
|
||
updateHighlightedKeymapKey();
|
||
}
|
||
if (
|
||
config.mode === "words" ||
|
||
config.mode === "custom" ||
|
||
config.mode === "quote"
|
||
) {
|
||
updateTimer();
|
||
}
|
||
if (
|
||
config.mode == "time" ||
|
||
config.mode == "words" ||
|
||
config.mode == "custom"
|
||
) {
|
||
addWord();
|
||
}
|
||
}
|
||
|
||
function handleAlpha(event) {
|
||
if (
|
||
[
|
||
"ContextMenu",
|
||
"Escape",
|
||
"Shift",
|
||
"Control",
|
||
"Meta",
|
||
"Alt",
|
||
"AltGraph",
|
||
"CapsLock",
|
||
"Backspace",
|
||
"PageUp",
|
||
"PageDown",
|
||
"Home",
|
||
"ArrowUp",
|
||
"ArrowLeft",
|
||
"ArrowRight",
|
||
"ArrowDown",
|
||
"OS",
|
||
"Insert",
|
||
"Home",
|
||
"Undefined",
|
||
"Control",
|
||
"Fn",
|
||
"FnLock",
|
||
"Hyper",
|
||
"NumLock",
|
||
"ScrollLock",
|
||
"Symbol",
|
||
"SymbolLock",
|
||
"Super",
|
||
"Unidentified",
|
||
"Process",
|
||
"Delete",
|
||
undefined,
|
||
].includes(event.key)
|
||
) {
|
||
currentKeypress.mod++;
|
||
return;
|
||
}
|
||
|
||
//insert space for expert and master or strict space,
|
||
//otherwise dont do anything
|
||
if (event.key === " ") {
|
||
if (config.difficulty !== "normal" || config.strictSpace) {
|
||
if (dontInsertSpace) {
|
||
dontInsertSpace = false;
|
||
return;
|
||
}
|
||
} else {
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (event.key === "Tab") {
|
||
if (!textHasTab || (textHasTab && event.shiftKey)) {
|
||
return;
|
||
}
|
||
event.key = "\t";
|
||
event.preventDefault();
|
||
}
|
||
|
||
if (event.key === "Enter") {
|
||
event.key = "\n";
|
||
}
|
||
|
||
// if (event.key.length > 1) return;
|
||
if (/F\d+/.test(event.key)) return;
|
||
if (/Numpad/.test(event.key)) return;
|
||
if (/Volume/.test(event.key)) return;
|
||
if (/Media/.test(event.key)) return;
|
||
if (
|
||
event.ctrlKey != event.altKey &&
|
||
(event.ctrlKey || /Linux/.test(window.navigator.platform))
|
||
)
|
||
return;
|
||
if (event.metaKey) return;
|
||
event = emulateLayout(event);
|
||
|
||
//start the test
|
||
if (currentInput == "" && inputHistory.length == 0 && !testActive) {
|
||
startTest();
|
||
} else {
|
||
if (!testActive) return;
|
||
}
|
||
|
||
setFocus(true);
|
||
stopCaretAnimation();
|
||
|
||
//show dead keys
|
||
if (event.key === "Dead") {
|
||
playClickSound();
|
||
$(
|
||
document.querySelector("#words .word.active").querySelectorAll("letter")[
|
||
currentInput.length
|
||
]
|
||
).toggleClass("dead");
|
||
return;
|
||
}
|
||
|
||
//check if the char typed was correct
|
||
let thisCharCorrect;
|
||
let nextCharInWord = wordsList[currentWordIndex].substring(
|
||
currentInput.length,
|
||
currentInput.length + 1
|
||
);
|
||
if (
|
||
config.language === "russian" &&
|
||
(event["key"].toLowerCase() == "e" || event["key"].toLowerCase() == "ё")
|
||
) {
|
||
if (
|
||
nextCharInWord.toLowerCase() == "e" ||
|
||
nextCharInWord.toLowerCase() == "ё"
|
||
) {
|
||
thisCharCorrect = true;
|
||
} else {
|
||
thisCharCorrect = false;
|
||
}
|
||
} else {
|
||
if (nextCharInWord == event["key"]) {
|
||
thisCharCorrect = true;
|
||
} else {
|
||
thisCharCorrect = false;
|
||
}
|
||
}
|
||
|
||
if (!thisCharCorrect) {
|
||
accuracyStats.incorrect++;
|
||
currentError.count++;
|
||
currentError.words.push(currentWordIndex);
|
||
thisCharCorrect = false;
|
||
if (!Object.keys(missedWords).includes(wordsList[currentWordIndex])) {
|
||
missedWords[wordsList[currentWordIndex]] = 1;
|
||
} else {
|
||
missedWords[wordsList[currentWordIndex]]++;
|
||
}
|
||
} else {
|
||
accuracyStats.correct++;
|
||
thisCharCorrect = true;
|
||
}
|
||
|
||
if (thisCharCorrect) {
|
||
playClickSound();
|
||
} else {
|
||
if (!config.playSoundOnError || config.blindMode) {
|
||
playClickSound();
|
||
} else {
|
||
playErrorSound();
|
||
}
|
||
}
|
||
|
||
//update current corrected verison. if its empty then add the current key. if its not then replace the last character with the currently pressed one / add it
|
||
if (currentCorrected === "") {
|
||
currentCorrected = currentInput + event["key"];
|
||
} else {
|
||
let cil = currentInput.length;
|
||
if (cil >= currentCorrected.length) {
|
||
currentCorrected += event["key"];
|
||
} else if (!thisCharCorrect) {
|
||
currentCorrected =
|
||
currentCorrected.substring(0, cil) +
|
||
event["key"] +
|
||
currentCorrected.substring(cil + 1);
|
||
}
|
||
}
|
||
|
||
currentKeypress.count++;
|
||
currentKeypress.words.push(currentWordIndex);
|
||
|
||
if (config.stopOnError == "letter" && !thisCharCorrect) {
|
||
return;
|
||
}
|
||
|
||
//update the active word top, but only once
|
||
if (currentInput.length === 1 && currentWordIndex === 0) {
|
||
activeWordTop = document.querySelector("#words .active").offsetTop;
|
||
}
|
||
|
||
//max length of the input is 20
|
||
if (currentInput.length < wordsList[currentWordIndex].length + 20) {
|
||
currentInput += event["key"];
|
||
}
|
||
|
||
if (!thisCharCorrect && config.difficulty == "master") {
|
||
failTest();
|
||
return;
|
||
}
|
||
|
||
//keymap
|
||
if (config.keymapMode === "react") {
|
||
flashPressedKeymapKey(event.key, thisCharCorrect);
|
||
} else if (config.keymapMode === "next") {
|
||
updateHighlightedKeymapKey();
|
||
}
|
||
|
||
activeWordTopBeforeJump = activeWordTop;
|
||
updateWordElement(!config.blindMode);
|
||
|
||
//auto stop the test if the last word is correct
|
||
let currentWord = wordsList[currentWordIndex];
|
||
let lastindex = currentWordIndex;
|
||
if (
|
||
(currentWord == currentInput ||
|
||
(config.quickEnd &&
|
||
currentWord.length == currentInput.length &&
|
||
config.stopOnError == "off")) &&
|
||
lastindex == wordsList.length - 1
|
||
) {
|
||
inputHistory.push(currentInput);
|
||
currentInput = "";
|
||
correctedHistory.push(currentCorrected);
|
||
currentCorrected = "";
|
||
lastSecondNotRound = true;
|
||
showResult();
|
||
}
|
||
|
||
//simulate space press in nospace funbox
|
||
if (
|
||
(activeFunBox === "nospace" &&
|
||
currentInput.length === wordsList[currentWordIndex].length) ||
|
||
(event.key === "\n" && thisCharCorrect)
|
||
) {
|
||
$.event.trigger({
|
||
type: "keydown",
|
||
which: " ".charCodeAt(0),
|
||
key: " ",
|
||
});
|
||
}
|
||
|
||
//check if the word jumped
|
||
let newActiveTop = document.querySelector("#words .word.active").offsetTop;
|
||
if (activeWordTopBeforeJump < newActiveTop && !lineTransition) {
|
||
activeWordJumped = true;
|
||
} else {
|
||
activeWordJumped = false;
|
||
}
|
||
|
||
//stop the word jump by slicing off the last character, update word again
|
||
if (activeWordJumped && currentInput.length > 1) {
|
||
currentInput = currentInput.slice(0, -1);
|
||
updateWordElement(!config.blindMode);
|
||
activeWordJumped = false;
|
||
}
|
||
|
||
updateCaretPosition();
|
||
}
|
||
|
||
window.addEventListener("beforeunload", (event) => {
|
||
// Cancel the event as stated by the standard.
|
||
if (
|
||
(config.mode === "words" && config.words < 1000) ||
|
||
(config.mode === "time" && config.time < 3600) ||
|
||
config.mode === "quote" ||
|
||
(config.mode === "custom" &&
|
||
customText.isWordRandom &&
|
||
customText.word < 1000) ||
|
||
(config.mode === "custom" &&
|
||
customText.isTimeRandom &&
|
||
customText.time < 1000) ||
|
||
(config.mode === "custom" &&
|
||
!customText.isWordRandom &&
|
||
customText.text.length < 1000)
|
||
) {
|
||
} else {
|
||
if (testActive) {
|
||
event.preventDefault();
|
||
// Chrome requires returnValue to be set.
|
||
event.returnValue = "";
|
||
}
|
||
}
|
||
});
|
||
|
||
if (firebase.app().options.projectId === "monkey-type-dev-67af4") {
|
||
$("#top .logo .bottom").text("monkey-dev");
|
||
$("head title").text("Monkey Dev");
|
||
$("body").append(
|
||
`<div class="devIndicator tr">DEV</div><div class="devIndicator bl">DEV</div>`
|
||
);
|
||
}
|
||
|
||
if (window.location.hostname === "localhost") {
|
||
window.onerror = function (error) {
|
||
Notifications.add(error, -1);
|
||
};
|
||
$("#top .logo .top").text("localhost");
|
||
$("head title").text($("head title").text() + " (localhost)");
|
||
firebase.functions().useFunctionsEmulator("http://localhost:5001");
|
||
$("body").append(
|
||
`<div class="devIndicator tl">local</div><div class="devIndicator br">local</div>`
|
||
);
|
||
}
|
||
|
||
manualRestart = true;
|
||
loadConfigFromCookie();
|
||
Misc.getReleasesFromGitHub();
|
||
// getPatreonNames();
|
||
|
||
$(document).on("mouseenter", "#resultWordsHistory .words .word", (e) => {
|
||
if (resultVisible) {
|
||
let input = $(e.currentTarget).attr("input");
|
||
if (input != undefined)
|
||
$(e.currentTarget).append(
|
||
`<div class="wordInputAfter">${input
|
||
.replace(/\t/g, "_")
|
||
.replace(/\n/g, "_")}</div>`
|
||
);
|
||
}
|
||
});
|
||
|
||
$(document).on("click", "#bottom .leftright .right .current-theme", (e) => {
|
||
if (e.shiftKey) {
|
||
togglePresetCustomTheme();
|
||
} else {
|
||
// if (config.customTheme) {
|
||
// togglePresetCustomTheme();
|
||
// }
|
||
currentCommands.push(commandsThemes);
|
||
showCommandLine();
|
||
}
|
||
});
|
||
|
||
$(document).on("click", ".keymap .r5 #KeySpace", (e) => {
|
||
currentCommands.push(commandsKeymapLayouts);
|
||
showCommandLine();
|
||
});
|
||
|
||
$(document).on("mouseleave", "#resultWordsHistory .words .word", (e) => {
|
||
$(".wordInputAfter").remove();
|
||
});
|
||
|
||
$("#wpmChart").on("mouseleave", (e) => {
|
||
$(".wordInputAfter").remove();
|
||
});
|
||
|
||
$(document).ready(() => {
|
||
updateFavicon(32, 14);
|
||
$("body").css("transition", ".25s");
|
||
if (config.quickTab) {
|
||
$("#restartTestButton").addClass("hidden");
|
||
}
|
||
if (!Misc.getCookie("merchbannerclosed")) {
|
||
$(".merchBanner").removeClass("hidden");
|
||
} else {
|
||
$(".merchBanner").remove();
|
||
}
|
||
$("#centerContent")
|
||
.css("opacity", "0")
|
||
.removeClass("hidden")
|
||
.stop(true, true)
|
||
.animate({ opacity: 1 }, 250, () => {
|
||
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 (/challenge_.+/g.test(window.location.pathname)) {
|
||
//do nothing
|
||
} else if (window.location.pathname !== "/") {
|
||
let page = window.location.pathname.replace("/", "");
|
||
changePage(page);
|
||
}
|
||
});
|
||
});
|
||
|
||
$(".scrollToTopButton").click((event) => {
|
||
window.scrollTo(0, 0);
|
||
});
|
||
|
||
$(".merchBanner a").click((event) => {
|
||
$(".merchBanner").remove();
|
||
Misc.setCookie("merchbannerclosed", true, 365);
|
||
});
|
||
|
||
$(".merchBanner .fas").click((event) => {
|
||
$(".merchBanner").remove();
|
||
Misc.setCookie("merchbannerclosed", true, 365);
|
||
Notifications.add(
|
||
"Won't remind you anymore. Thanks for continued support <3",
|
||
0,
|
||
5
|
||
);
|
||
});
|
||
|
||
$(".pageTest #copyWordsListButton").click(async (event) => {
|
||
try {
|
||
await navigator.clipboard.writeText(
|
||
wordsList.slice(0, inputHistory.length).join(" ")
|
||
);
|
||
Notifications.add("Copied to clipboard", 0, 2);
|
||
} catch (e) {
|
||
Notifications.add("Could not copy to clipboard: " + e, -1);
|
||
}
|
||
});
|
||
|
||
//stop space scrolling
|
||
window.addEventListener("keydown", function (e) {
|
||
if (e.keyCode == 32 && e.target == document.body) {
|
||
e.preventDefault();
|
||
}
|
||
});
|
||
|
||
async function setupChallenge(challengeName) {
|
||
let list = await Misc.getChallengeList();
|
||
let challenge = list.filter((c) => c.name === challengeName)[0];
|
||
let notitext;
|
||
try {
|
||
if (challenge === undefined) {
|
||
throw "Challenge not found";
|
||
}
|
||
if (challenge.type === "customTime") {
|
||
setTimeConfig(challenge.parameters[0], true);
|
||
setMode("time", true);
|
||
setDifficulty("normal", true);
|
||
if (challenge.name === "englishMaster") {
|
||
setLanguage("english_10k", true);
|
||
setNumbers(true, true);
|
||
setPunctuation(true, true);
|
||
}
|
||
}
|
||
if (challenge.type === "customWords") {
|
||
setWordCount(challenge.parameters[0], true);
|
||
setMode("words", true);
|
||
setDifficulty("normal", true);
|
||
} else if (challenge.type === "customText") {
|
||
customText.text = challenge.parameters[0].split(" ");
|
||
customText.isWordRandom = challenge.parameters[1];
|
||
customText.word = parseInt(challenge.parameters[2]);
|
||
setMode("custom", true);
|
||
setDifficulty("normal", true);
|
||
} else if (challenge.type === "script") {
|
||
let scriptdata = await fetch("/challenges/" + challenge.parameters[0]);
|
||
scriptdata = await scriptdata.text();
|
||
let text = scriptdata.trim();
|
||
text = text.replace(/[\n\r\t ]/gm, " ");
|
||
text = text.replace(/ +/gm, " ");
|
||
customText.text = text.split(" ");
|
||
customText.isWordRandom = false;
|
||
setMode("custom", true);
|
||
setDifficulty("normal", true);
|
||
if (challenge.parameters[1] != null) {
|
||
setTheme(challenge.parameters[1]);
|
||
}
|
||
if (challenge.parameters[2] != null) {
|
||
activateFunbox(challenge.parameters[2]);
|
||
}
|
||
} else if (challenge.type === "accuracy") {
|
||
setTimeConfig(0, true);
|
||
setMode("time", true);
|
||
setDifficulty("master", true);
|
||
} else if (challenge.type === "funbox") {
|
||
activateFunbox(challenge.parameters[0]);
|
||
setDifficulty("normal", true);
|
||
if (challenge.parameters[1] === "words") {
|
||
setWordCount(challenge.parameters[2], true);
|
||
} else if (challenge.parameters[1] === "time") {
|
||
setTimeConfig(challenge.parameters[2], true);
|
||
}
|
||
setMode(challenge.parameters[1], true);
|
||
if (challenge.parameters[3] !== undefined) {
|
||
setDifficulty(challenge.parameters[3], true);
|
||
}
|
||
}
|
||
manualRestart = true;
|
||
restartTest(false, true);
|
||
notitext = challenge.message;
|
||
if (notitext === undefined) {
|
||
Notifications.add(`Challenge '${challengeName}' loaded.`, 0);
|
||
} else {
|
||
Notifications.add("Challenge loaded. " + notitext, 0);
|
||
}
|
||
} catch (e) {
|
||
Notifications.add("Something went wrong: " + e, -1);
|
||
}
|
||
}
|
||
|
||
let ctx = $("#wpmChart");
|
||
let wpmOverTimeChart = new Chart(ctx, {
|
||
type: "line",
|
||
data: {
|
||
labels: [],
|
||
datasets: [
|
||
{
|
||
label: "wpm",
|
||
data: [],
|
||
borderColor: "rgba(125, 125, 125, 1)",
|
||
borderWidth: 2,
|
||
yAxisID: "wpm",
|
||
order: 2,
|
||
radius: 2,
|
||
},
|
||
{
|
||
label: "raw",
|
||
data: [],
|
||
borderColor: "rgba(125, 125, 125, 1)",
|
||
borderWidth: 2,
|
||
yAxisID: "raw",
|
||
order: 3,
|
||
radius: 2,
|
||
},
|
||
{
|
||
label: "errors",
|
||
data: [],
|
||
borderColor: "rgba(255, 125, 125, 1)",
|
||
pointBackgroundColor: "rgba(255, 125, 125, 1)",
|
||
borderWidth: 2,
|
||
order: 1,
|
||
yAxisID: "error",
|
||
maxBarThickness: 10,
|
||
type: "scatter",
|
||
pointStyle: "crossRot",
|
||
radius: function (context) {
|
||
var index = context.dataIndex;
|
||
var value = context.dataset.data[index];
|
||
return value <= 0 ? 0 : 3;
|
||
},
|
||
pointHoverRadius: function (context) {
|
||
var index = context.dataIndex;
|
||
var value = context.dataset.data[index];
|
||
return value <= 0 ? 0 : 5;
|
||
},
|
||
},
|
||
],
|
||
},
|
||
options: {
|
||
tooltips: {
|
||
titleFontFamily: "Roboto Mono",
|
||
bodyFontFamily: "Roboto Mono",
|
||
mode: "index",
|
||
intersect: false,
|
||
callbacks: {
|
||
afterLabel: function (ti, data) {
|
||
try {
|
||
$(".wordInputAfter").remove();
|
||
|
||
let wordsToHighlight =
|
||
keypressPerSecond[parseInt(ti.xLabel) - 1].words;
|
||
|
||
let unique = [...new Set(wordsToHighlight)];
|
||
unique.forEach((wordIndex) => {
|
||
let wordEl = $($("#resultWordsHistory .words .word")[wordIndex]);
|
||
let input = wordEl.attr("input");
|
||
if (input != undefined)
|
||
wordEl.append(`<div class="wordInputAfter">${input}</div>`);
|
||
});
|
||
} catch (e) {}
|
||
},
|
||
},
|
||
},
|
||
legend: {
|
||
display: false,
|
||
labels: {
|
||
defaultFontFamily: "Roboto Mono",
|
||
},
|
||
},
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
scales: {
|
||
xAxes: [
|
||
{
|
||
ticks: {
|
||
fontFamily: "Roboto Mono",
|
||
autoSkip: true,
|
||
autoSkipPadding: 40,
|
||
},
|
||
display: true,
|
||
scaleLabel: {
|
||
display: false,
|
||
labelString: "Seconds",
|
||
fontFamily: "Roboto Mono",
|
||
},
|
||
},
|
||
],
|
||
yAxes: [
|
||
{
|
||
id: "wpm",
|
||
display: true,
|
||
scaleLabel: {
|
||
display: true,
|
||
labelString: "Words per Minute",
|
||
fontFamily: "Roboto Mono",
|
||
},
|
||
ticks: {
|
||
fontFamily: "Roboto Mono",
|
||
beginAtZero: true,
|
||
min: 0,
|
||
autoSkip: true,
|
||
autoSkipPadding: 40,
|
||
},
|
||
gridLines: {
|
||
display: true,
|
||
},
|
||
},
|
||
{
|
||
id: "raw",
|
||
display: false,
|
||
scaleLabel: {
|
||
display: true,
|
||
labelString: "Raw Words per Minute",
|
||
fontFamily: "Roboto Mono",
|
||
},
|
||
ticks: {
|
||
fontFamily: "Roboto Mono",
|
||
beginAtZero: true,
|
||
min: 0,
|
||
autoSkip: true,
|
||
autoSkipPadding: 40,
|
||
},
|
||
gridLines: {
|
||
display: false,
|
||
},
|
||
},
|
||
{
|
||
id: "error",
|
||
display: true,
|
||
position: "right",
|
||
scaleLabel: {
|
||
display: true,
|
||
labelString: "Errors",
|
||
fontFamily: "Roboto Mono",
|
||
},
|
||
ticks: {
|
||
precision: 0,
|
||
fontFamily: "Roboto Mono",
|
||
beginAtZero: true,
|
||
autoSkip: true,
|
||
autoSkipPadding: 40,
|
||
},
|
||
gridLines: {
|
||
display: false,
|
||
},
|
||
},
|
||
],
|
||
},
|
||
annotation: {
|
||
annotations: [],
|
||
},
|
||
},
|
||
});
|