mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-09-22 16:26:18 +08:00
fixing wpm and correctedHistory for Korean (#3474) neezacoto
* fixing wpm and correct history for korean * pretty fix
This commit is contained in:
parent
1a9e2c4134
commit
e541fbaf53
|
@ -295,6 +295,7 @@
|
|||
// transition: .25s;
|
||||
.wordInputAfter {
|
||||
opacity: 1;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
background: var(--sub-color);
|
||||
color: var(--bg-color);
|
||||
|
|
|
@ -32,6 +32,8 @@ import { navigate } from "./route-controller";
|
|||
|
||||
let dontInsertSpace = false;
|
||||
let correctShiftUsed = true;
|
||||
let isKoCompiling = false;
|
||||
let isBackspace: boolean;
|
||||
|
||||
const wordsInput = document.getElementById("wordsInput") as HTMLInputElement;
|
||||
const koInputVisual = document.getElementById("koInputVisual") as HTMLElement;
|
||||
|
@ -291,7 +293,8 @@ function isCharCorrect(char: string, charIndex: number): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (Config.language.startsWith("korean")) {
|
||||
//Checking for Korean char
|
||||
if (TestInput.input.getKoreanStatus()) {
|
||||
//disassembles Korean current Test word to check against char Input
|
||||
const koWordArray: string[] = Hangul.disassemble(
|
||||
TestWords.words.getCurrent()
|
||||
|
@ -366,7 +369,7 @@ function handleChar(
|
|||
if (TestUI.resultCalculating || TestUI.resultVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isCharKorean: boolean = TestInput.input.getKoreanStatus();
|
||||
if (char === "…") {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
handleChar(".", charIndex + i);
|
||||
|
@ -420,7 +423,9 @@ function handleChar(
|
|||
let resultingWord: string;
|
||||
|
||||
if (thisCharCorrect && Config.mode !== "zen") {
|
||||
char = TestWords.words.getCurrent().charAt(charIndex);
|
||||
char = !isCharKorean
|
||||
? TestWords.words.getCurrent().charAt(charIndex)
|
||||
: Hangul.disassemble(TestWords.words.getCurrent())[charIndex];
|
||||
}
|
||||
|
||||
if (!thisCharCorrect && char === "\n") {
|
||||
|
@ -432,7 +437,7 @@ function handleChar(
|
|||
TestInput.setBurstStart(performance.now());
|
||||
}
|
||||
|
||||
if (!Config.language.startsWith("korean")) {
|
||||
if (!isCharKorean && !Config.language.startsWith("korean")) {
|
||||
resultingWord =
|
||||
TestInput.input.current.substring(0, charIndex) +
|
||||
char +
|
||||
|
@ -487,10 +492,14 @@ function handleChar(
|
|||
|
||||
//update current corrected version. if its empty then add the current char. if its not then replace the last character with the currently pressed one / add it
|
||||
if (TestInput.corrected.current === "") {
|
||||
TestInput.corrected.current += resultingWord;
|
||||
TestInput.corrected.current += !isCharKorean
|
||||
? resultingWord
|
||||
: Hangul.disassemble(resultingWord).join("");
|
||||
} else {
|
||||
if (charIndex >= TestInput.corrected.current.length) {
|
||||
TestInput.corrected.current += char;
|
||||
TestInput.corrected.current += !isCharKorean
|
||||
? char
|
||||
: Hangul.disassemble(char).concat();
|
||||
} else if (!thisCharCorrect) {
|
||||
TestInput.corrected.current =
|
||||
TestInput.corrected.current.substring(0, charIndex) +
|
||||
|
@ -891,6 +900,8 @@ $(document).keydown(async (event) => {
|
|||
TestUI.scrollTape();
|
||||
}
|
||||
}
|
||||
|
||||
isBackspace = event.key === "Backspace" || event.key === "delete";
|
||||
});
|
||||
|
||||
$("#wordsInput").keyup((event) => {
|
||||
|
@ -918,33 +929,59 @@ $("#wordsInput").on("beforeinput", (event) => {
|
|||
}
|
||||
});
|
||||
|
||||
let avg = 0;
|
||||
$("#wordsInput").on("input", (event) => {
|
||||
const start = performance.now();
|
||||
if (!event.originalEvent?.isTrusted || TestUI.testRestarting) {
|
||||
(event.target as HTMLInputElement).value = " ";
|
||||
return;
|
||||
}
|
||||
|
||||
const popupVisible = Misc.isAnyPopupVisible();
|
||||
|
||||
if (popupVisible) return;
|
||||
|
||||
TestInput.setKeypressNotAfk();
|
||||
|
||||
const isLangKorean: boolean = Config.language.startsWith("korean");
|
||||
if (
|
||||
(event.target as HTMLInputElement).value
|
||||
.normalize()
|
||||
.match(
|
||||
/[\uac00-\ud7af]|[\u1100-\u11ff]|[\u3130-\u318f]|[\ua960-\ua97f]|[\ud7b0-\ud7ff]/g
|
||||
)
|
||||
) {
|
||||
TestInput.input.setKoreanStatus(true);
|
||||
}
|
||||
|
||||
const containsKorean = TestInput.input.getKoreanStatus();
|
||||
|
||||
//Hangul.disassemble breaks down Korean characters into its components
|
||||
//allowing it to be treated as normal latin characters
|
||||
//Hangul.disassemble('한글') //['ㅎ','ㅏ','ㄴ','ㄱ','ㅡ','ㄹ']
|
||||
//Hangul.disassemble('한글',true) //[['ㅎ','ㅏ','ㄴ'],['ㄱ','ㅡ','ㄹ']]
|
||||
const realInputValue = (event.target as HTMLInputElement).value.normalize();
|
||||
const inputValue = isLangKorean
|
||||
const inputValue = containsKorean
|
||||
? Hangul.disassemble(realInputValue).join("").slice(1)
|
||||
: realInputValue.slice(1);
|
||||
|
||||
const currTestInput = isLangKorean
|
||||
const currTestInput = containsKorean
|
||||
? Hangul.disassemble(TestInput.input.current).join("")
|
||||
: TestInput.input.current;
|
||||
|
||||
//checks to see if a korean word has compiled into two characters.
|
||||
//inputs: ㄱ, 가, 갇, 가다
|
||||
//what it actually reads: ㄱ, 가, 갇, , 가, 가다
|
||||
//this skips this part (, , 가,)
|
||||
if (containsKorean && !isBackspace) {
|
||||
if (
|
||||
isKoCompiling ||
|
||||
(realInputValue.slice(1).length < TestInput.input.current.length &&
|
||||
Hangul.disassemble(TestInput.input.current.slice(-1)).length > 1)
|
||||
) {
|
||||
isKoCompiling = !isKoCompiling;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// input will be modified even with the preventDefault() in
|
||||
// beforeinput/keydown if it's part of a compose sequence. this undoes
|
||||
// the effects of that and takes the input out of compose mode.
|
||||
|
@ -960,7 +997,7 @@ $("#wordsInput").on("input", (event) => {
|
|||
// fallback for when no Backspace keydown event (mobile)
|
||||
backspaceToPrevious();
|
||||
} else if (inputValue.length < currTestInput.length) {
|
||||
if (!isLangKorean) {
|
||||
if (!containsKorean) {
|
||||
TestInput.input.current = inputValue;
|
||||
} else {
|
||||
const realInput = (event.target as HTMLInputElement).value
|
||||
|
@ -1025,6 +1062,9 @@ $("#wordsInput").on("input", (event) => {
|
|||
).selectionEnd = (event.target as HTMLInputElement).value.length;
|
||||
}
|
||||
}, 0);
|
||||
const end = performance.now();
|
||||
avg = Math.floor(end - start + avg) / 2;
|
||||
console.log("input avg", avg, "live input", end - start);
|
||||
});
|
||||
|
||||
$("#wordsInput").on("focus", (event) => {
|
||||
|
|
|
@ -23,12 +23,14 @@ class Input {
|
|||
current: string;
|
||||
history: string[];
|
||||
historyLength: number;
|
||||
koreanStatus: boolean;
|
||||
length: number;
|
||||
constructor() {
|
||||
this.current = "";
|
||||
this.history = [];
|
||||
this.historyLength = 0;
|
||||
this.length = 0;
|
||||
this.koreanStatus = false;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
|
@ -47,6 +49,10 @@ class Input {
|
|||
this.length = this.current.length;
|
||||
}
|
||||
|
||||
setKoreanStatus(val: boolean): void {
|
||||
this.koreanStatus = val;
|
||||
}
|
||||
|
||||
appendCurrent(val: string): void {
|
||||
this.current += val;
|
||||
this.length = this.current.length;
|
||||
|
@ -60,6 +66,10 @@ class Input {
|
|||
return this.current;
|
||||
}
|
||||
|
||||
getKoreanStatus(): boolean {
|
||||
return this.koreanStatus;
|
||||
}
|
||||
|
||||
pushHistory(): void {
|
||||
this.history.push(this.current);
|
||||
this.historyLength = this.history.length;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Hangul from "hangul-js";
|
||||
import Config from "../config";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as TestInput from "./test-input";
|
||||
|
@ -188,18 +189,35 @@ export function calculateTestSeconds(now?: number): number {
|
|||
return (now - start) / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
let avg = 0;
|
||||
export function calculateWpmAndRaw(): MonkeyTypes.WordsPerMinuteAndRaw {
|
||||
const start = performance.now();
|
||||
const containsKorean = TestInput.input.getKoreanStatus();
|
||||
let chars = 0;
|
||||
let correctWordChars = 0;
|
||||
let spaces = 0;
|
||||
|
||||
const currTestInput = !containsKorean
|
||||
? TestInput.input.current
|
||||
: Hangul.disassemble(TestInput.input.current);
|
||||
|
||||
//check input history
|
||||
for (let i = 0; i < TestInput.input.history.length; i++) {
|
||||
const word =
|
||||
const word: string = !containsKorean
|
||||
? //english
|
||||
Config.mode == "zen"
|
||||
? (TestInput.input.getHistory(i) as string)
|
||||
: TestWords.words.get(i)
|
||||
: //korean
|
||||
Config.mode == "zen"
|
||||
? TestInput.input.getHistory(i)
|
||||
: TestWords.words.get(i);
|
||||
if (TestInput.input.getHistory(i) == word) {
|
||||
? Hangul.disassemble(TestInput.input.getHistory(i) as string).join("")
|
||||
: Hangul.disassemble(TestWords.words.get(i)).join("");
|
||||
|
||||
const historyWord: string = !containsKorean
|
||||
? (TestInput.input.getHistory(i) as string)
|
||||
: Hangul.disassemble(TestInput.input.getHistory(i) as string).join("");
|
||||
|
||||
if (historyWord === word) {
|
||||
//the word is correct
|
||||
//+1 for space
|
||||
correctWordChars += word.length;
|
||||
|
@ -210,13 +228,17 @@ export function calculateWpmAndRaw(): MonkeyTypes.WordsPerMinuteAndRaw {
|
|||
spaces++;
|
||||
}
|
||||
}
|
||||
chars += TestInput.input.getHistory(i).length;
|
||||
chars += !containsKorean
|
||||
? TestInput.input.getHistory(i).length
|
||||
: Hangul.disassemble(TestInput.input.getHistory(i) as string).length;
|
||||
}
|
||||
if (TestInput.input.current !== "") {
|
||||
if (currTestInput !== "") {
|
||||
const word =
|
||||
Config.mode == "zen"
|
||||
? TestInput.input.current
|
||||
: TestWords.words.getCurrent();
|
||||
? currTestInput
|
||||
: !containsKorean
|
||||
? TestWords.words.getCurrent()
|
||||
: Hangul.disassemble(TestWords.words.getCurrent() ?? "");
|
||||
//check whats currently typed
|
||||
const toAdd = {
|
||||
correct: 0,
|
||||
|
@ -224,9 +246,9 @@ export function calculateWpmAndRaw(): MonkeyTypes.WordsPerMinuteAndRaw {
|
|||
missed: 0,
|
||||
};
|
||||
for (let c = 0; c < word.length; c++) {
|
||||
if (c < TestInput.input.current.length) {
|
||||
if (c < currTestInput.length) {
|
||||
//on char that still has a word list pair
|
||||
if (TestInput.input.current[c] == word[c]) {
|
||||
if (currTestInput[c] === word[c]) {
|
||||
toAdd.correct++;
|
||||
} else {
|
||||
toAdd.incorrect++;
|
||||
|
@ -247,12 +269,15 @@ export function calculateWpmAndRaw(): MonkeyTypes.WordsPerMinuteAndRaw {
|
|||
if (Config.funbox === "nospace" || Config.funbox === "arrows") {
|
||||
spaces = 0;
|
||||
}
|
||||
chars += TestInput.input.current.length;
|
||||
chars += currTestInput.length;
|
||||
const testSeconds = calculateTestSeconds(performance.now());
|
||||
const wpm = Math.round(
|
||||
((correctWordChars + spaces) * (60 / testSeconds)) / 5
|
||||
);
|
||||
const raw = Math.round(((chars + spaces) * (60 / testSeconds)) / 5);
|
||||
const end = performance.now();
|
||||
avg = (end - start + avg) / 2;
|
||||
console.log("wpm avg", avg, "live wpm", end - start);
|
||||
return {
|
||||
wpm: wpm,
|
||||
raw: raw,
|
||||
|
@ -294,11 +319,17 @@ export function setLastSecondNotRound(): void {
|
|||
}
|
||||
|
||||
export function calculateBurst(): number {
|
||||
const containsKorean = TestInput.input.getKoreanStatus();
|
||||
const timeToWrite = (performance.now() - TestInput.currentBurstStart) / 1000;
|
||||
let wordLength: number;
|
||||
wordLength = TestInput.input.current.length;
|
||||
wordLength = !containsKorean
|
||||
? TestInput.input.current.length
|
||||
: Hangul.disassemble(TestInput.input.current).length;
|
||||
if (wordLength == 0) {
|
||||
wordLength = TestInput.input.getHistoryLast()?.length ?? 0;
|
||||
wordLength = !containsKorean
|
||||
? TestInput.input.getHistoryLast()?.length ?? 0
|
||||
: Hangul.disassemble(TestInput.input.getHistoryLast() as string)
|
||||
?.length ?? 0;
|
||||
}
|
||||
if (wordLength == 0) return 0;
|
||||
const speed = Misc.roundTo2((wordLength * (60 / timeToWrite)) / 5);
|
||||
|
@ -330,30 +361,41 @@ function countChars(): CharCount {
|
|||
let spaces = 0;
|
||||
let correctspaces = 0;
|
||||
for (let i = 0; i < TestInput.input.history.length; i++) {
|
||||
const word =
|
||||
const containsKorean = TestInput.input.getKoreanStatus();
|
||||
const word: string = !containsKorean
|
||||
? //english
|
||||
Config.mode == "zen"
|
||||
? (TestInput.input.getHistory(i) as string)
|
||||
: TestWords.words.get(i)
|
||||
: //korean
|
||||
Config.mode == "zen"
|
||||
? TestInput.input.getHistory(i)
|
||||
: TestWords.words.get(i);
|
||||
? Hangul.disassemble(TestInput.input.getHistory(i) as string).join("")
|
||||
: Hangul.disassemble(TestWords.words.get(i)).join("");
|
||||
|
||||
if (TestInput.input.getHistory(i) === "") {
|
||||
//last word that was not started
|
||||
continue;
|
||||
}
|
||||
if (TestInput.input.getHistory(i) == word) {
|
||||
const historyWord: string = !containsKorean
|
||||
? (TestInput.input.getHistory(i) as string)
|
||||
: Hangul.disassemble(TestInput.input.getHistory(i) as string).join("");
|
||||
|
||||
if (historyWord == word) {
|
||||
//the word is correct
|
||||
correctWordChars += word.length;
|
||||
correctChars += word.length;
|
||||
if (
|
||||
i < TestInput.input.history.length - 1 &&
|
||||
Misc.getLastChar(TestInput.input.getHistory(i) as string) !== "\n"
|
||||
Misc.getLastChar(historyWord as string) !== "\n"
|
||||
) {
|
||||
correctspaces++;
|
||||
}
|
||||
} else if (TestInput.input.getHistory(i).length >= word.length) {
|
||||
} else if (historyWord.length >= word.length) {
|
||||
//too many chars
|
||||
for (let c = 0; c < TestInput.input.getHistory(i).length; c++) {
|
||||
for (let c = 0; c < historyWord.length; c++) {
|
||||
if (c < word.length) {
|
||||
//on char that still has a word list pair
|
||||
if (TestInput.input.getHistory(i)[c] == word[c]) {
|
||||
if (historyWord[c] == word[c]) {
|
||||
correctChars++;
|
||||
} else {
|
||||
incorrectChars++;
|
||||
|
@ -371,9 +413,9 @@ function countChars(): CharCount {
|
|||
missed: 0,
|
||||
};
|
||||
for (let c = 0; c < word.length; c++) {
|
||||
if (c < TestInput.input.getHistory(i).length) {
|
||||
if (c < historyWord.length) {
|
||||
//on char that still has a word list pair
|
||||
if (TestInput.input.getHistory(i)[c] == word[c]) {
|
||||
if (historyWord[c] == word[c]) {
|
||||
toAdd.correct++;
|
||||
} else {
|
||||
toAdd.incorrect++;
|
||||
|
|
|
@ -383,9 +383,9 @@ export function updateWordElement(showError = !Config.blindMode): void {
|
|||
} else {
|
||||
let correctSoFar = false;
|
||||
|
||||
const isLangKorean: boolean = Config.language.startsWith("korean");
|
||||
const containsKorean = TestInput.input.getKoreanStatus();
|
||||
|
||||
if (!isLangKorean) {
|
||||
if (!containsKorean) {
|
||||
// slice earlier if input has trailing compose characters
|
||||
const inputWithoutComposeLength = Misc.trailingComposeChars.test(input)
|
||||
? input.search(Misc.trailingComposeChars)
|
||||
|
@ -464,7 +464,7 @@ export function updateWordElement(showError = !Config.blindMode): void {
|
|||
currentLetter !== undefined &&
|
||||
CompositionState.getComposing() &&
|
||||
i >= CompositionState.getStartPos() &&
|
||||
!(isLangKorean && !correctSoFar)
|
||||
!(containsKorean && !correctSoFar)
|
||||
) {
|
||||
ret += `<letter class="${
|
||||
Config.highlightMode == "word" ? wordHighlightClassString : ""
|
||||
|
@ -697,6 +697,13 @@ async function loadWordsHistory(): Promise<boolean> {
|
|||
for (let i = 0; i < TestInput.input.history.length + 2; i++) {
|
||||
const input = <string>TestInput.input.getHistory(i);
|
||||
const word = TestWords.words.get(i);
|
||||
const containsKorean =
|
||||
input?.match(
|
||||
/[\uac00-\ud7af]|[\u1100-\u11ff]|[\u3130-\u318f]|[\ua960-\ua97f]|[\ud7b0-\ud7ff]/g
|
||||
) ||
|
||||
word?.match(
|
||||
/[\uac00-\ud7af]|[\u1100-\u11ff]|[\u3130-\u318f]|[\ua960-\ua97f]|[\ud7b0-\ud7ff]/g
|
||||
);
|
||||
let wordEl = "";
|
||||
try {
|
||||
if (input === "") throw new Error("empty input word");
|
||||
|
@ -704,10 +711,12 @@ async function loadWordsHistory(): Promise<boolean> {
|
|||
TestInput.corrected.getHistory(i) !== undefined &&
|
||||
TestInput.corrected.getHistory(i) !== ""
|
||||
) {
|
||||
const correctedChar = !containsKorean
|
||||
? TestInput.corrected.getHistory(i)
|
||||
: Hangul.assemble(TestInput.corrected.getHistory(i).split(""));
|
||||
wordEl = `<div class='word' burst="${
|
||||
TestInput.burstHistory[i]
|
||||
}" input="${TestInput.corrected
|
||||
.getHistory(i)
|
||||
}" input="${correctedChar
|
||||
.replace(/"/g, """)
|
||||
.replace(/ /g, "_")}">`;
|
||||
} else {
|
||||
|
@ -759,19 +768,23 @@ async function loadWordsHistory(): Promise<boolean> {
|
|||
//input is shorter or equal (loop over word list)
|
||||
loop = word.length;
|
||||
}
|
||||
|
||||
for (let c = 0; c < loop; c++) {
|
||||
let correctedChar;
|
||||
try {
|
||||
correctedChar = TestInput.corrected.getHistory(i)[c];
|
||||
correctedChar = !containsKorean
|
||||
? TestInput.corrected.getHistory(i)[c]
|
||||
: Hangul.assemble(TestInput.corrected.getHistory(i).split(""))[c];
|
||||
} catch (e) {
|
||||
correctedChar = undefined;
|
||||
}
|
||||
let extraCorrected = "";
|
||||
const historyWord: string = !containsKorean
|
||||
? TestInput.corrected.getHistory(i)
|
||||
: Hangul.assemble(TestInput.corrected.getHistory(i).split(""));
|
||||
if (
|
||||
c + 1 === loop &&
|
||||
TestInput.corrected.getHistory(i) !== undefined &&
|
||||
TestInput.corrected.getHistory(i).length > input.length
|
||||
historyWord !== undefined &&
|
||||
historyWord.length > input.length
|
||||
) {
|
||||
extraCorrected = "extraCorrected";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue