mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-03-12 23:20:25 +08:00
Rewrite input system to use <input> content (#1325)
* Rewrite input system to use <input> content * Tab/Escape, Backspace and Enter are always handled by $(document).keydown. * The rest of characters are handled by either $("#wordsInput").on("input") (default) or $(document).keydown (layout emulation). * New special handling for dead keys, compose keys and diacritics in general with the new regex Misc.trailingComposeChars. input-controller.js has been updated to use the above changes: * handleBackspace() replaced with simplified backspaceToPrevious(). On PC, a space is immediately re-added to the end of the input to make use of the browser/OS's Backspace. This lets the browser handle input- specific things like ctrl+backspace. * handleSpace() refactored a bit to repeat less logic when word is correct or incorrect. * test-ui.js updated to highlight the Misc.trailingComposeChars correctly, and also refactored a bit to make logic easier to follow. * AFK checking has also been simplified, now just set with a boolean flag (TestStats.) setKeypressNotAfk() instead of checking every key and modifier press (so incrementKeypressMod() was removed as it wasn't used for anything else). * Refactor input controller New function isCharCorrect(). * Remove caps lock backspace setting Not supported with the input rewrite anymore because we're relying on the browser's/OS's actual backspace effects. There's no way to emulate this keypress. * Refactor input controller * Reimplement opposite shift mode * Reimplement the layout emulator * Fix replay events for input rewrite Now it's more flexible for a variable amount of backspacing or letter replacing. * Pad input with space to handle backspace on mobile Backspace isn't fired as an event on current mobile browsers, so I worked around that by adding a permanent space at the start of the input and treating its removal as a fallback to Backspace to the previous word. * Force caret to end of input on focus Fixes initial selection on iOS. * Use offsetTop from the DOM instead of TestUI I didn't wanna mess too much with what happens in test-ui.js. Basically, on a restart after having completed a test, TestUI.activeWordTop is always wrong for some reason. This caused swipe/instant input after a restart to always drop the first few characters. * Prevent pasting on the input * Revert "Reimplement opposite shift mode" This reverts commit 9a716ad39b004f0719b05f486465ea03060430ca. * Use key code to check opposite shift usage Today I learned what closure actually meant. * Accept all whitespace as word space
This commit is contained in:
parent
6039820c26
commit
c323efea26
15 changed files with 523 additions and 722 deletions
|
@ -331,29 +331,6 @@ let commandsLiveWpm = {
|
|||
],
|
||||
};
|
||||
|
||||
let commandsCapsLockBackspace = {
|
||||
title: "Caps lock backspace...",
|
||||
configKey: "capsLockBackspace",
|
||||
list: [
|
||||
{
|
||||
id: "setCapsLockBackspaceOff",
|
||||
display: "off",
|
||||
configValue: false,
|
||||
exec: () => {
|
||||
UpdateConfig.setShowCapsLockBackspace(false);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "setCapsLockBackspaceOn",
|
||||
display: "on",
|
||||
configValue: true,
|
||||
exec: () => {
|
||||
UpdateConfig.setShowCapsLockBackspace(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let commandsLiveAcc = {
|
||||
title: "Live accuracy...",
|
||||
configKey: "showLiveAcc",
|
||||
|
@ -2713,12 +2690,6 @@ export let defaultCommands = {
|
|||
icon: "fa-gamepad",
|
||||
subgroup: commandsFunbox,
|
||||
},
|
||||
{
|
||||
id: "changeCapsLockBackspace",
|
||||
display: "Caps lock backspace...",
|
||||
icon: "fa-backspace",
|
||||
subgroup: commandsCapsLockBackspace,
|
||||
},
|
||||
{
|
||||
id: "changeLayout",
|
||||
display: "Layout...",
|
||||
|
|
|
@ -74,7 +74,6 @@ let defaultConfig = {
|
|||
caretStyle: "default",
|
||||
paceCaretStyle: "default",
|
||||
flipTestColors: false,
|
||||
capsLockBackspace: false,
|
||||
layout: "default",
|
||||
funbox: "none",
|
||||
confidenceMode: "off",
|
||||
|
@ -1366,18 +1365,6 @@ export function setMonkey(monkey, nosave) {
|
|||
if (!nosave) saveToLocalStorage();
|
||||
}
|
||||
|
||||
export function setCapsLockBackspace(capsLockBackspace, nosave) {
|
||||
if (capsLockBackspace === null || capsLockBackspace === undefined) {
|
||||
capsLockBackspace = false;
|
||||
}
|
||||
config.capsLockBackspace = capsLockBackspace;
|
||||
if (!nosave) saveToLocalStorage();
|
||||
}
|
||||
|
||||
export function toggleCapsLockBackspace() {
|
||||
setCapsLockBackspace(!config.capsLockBackspace, false);
|
||||
}
|
||||
|
||||
export function setKeymapMode(mode, nosave) {
|
||||
if (mode == null || mode == undefined) {
|
||||
mode = "off";
|
||||
|
@ -1624,7 +1611,6 @@ export function apply(configObj) {
|
|||
setQuoteLength(configObj.quoteLength, true);
|
||||
setWordCount(configObj.words, true);
|
||||
setLanguage(configObj.language, true);
|
||||
setCapsLockBackspace(configObj.capsLockBackspace, true);
|
||||
// setSavedLayout(configObj.savedLayout, true);
|
||||
setLayout(configObj.layout, true);
|
||||
setFontSize(configObj.fontSize, true);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -764,3 +764,5 @@ String.prototype.lastIndexOfRegex = function (regex) {
|
|||
var match = this.match(regex);
|
||||
return match ? this.lastIndexOf(match[match.length - 1]) : -1;
|
||||
};
|
||||
|
||||
export const trailingComposeChars = /[\u02B0-\u02FF`´^¨~]+$|⎄.*$/;
|
||||
|
|
|
@ -107,7 +107,7 @@ $(`${popup} .randomInputFields .time input`).keypress((e) => {
|
|||
});
|
||||
|
||||
$("#customTextPopup .apply").click(() => {
|
||||
let text = $("#customTextPopup textarea").val();
|
||||
let text = $("#customTextPopup textarea").val().normalize();
|
||||
text = text.trim();
|
||||
// text = text.replace(/[\r]/gm, " ");
|
||||
text = text.replace(/\\\\t/gm, "\t");
|
||||
|
|
|
@ -86,21 +86,23 @@ function handleDisplayLogic(item, nosound = false) {
|
|||
//if letter is an extra
|
||||
myElement = document.createElement("letter");
|
||||
myElement.classList.add("extra");
|
||||
myElement.innerHTML = item.letter;
|
||||
myElement.innerHTML = item.value;
|
||||
activeWord.appendChild(myElement);
|
||||
}
|
||||
myElement = activeWord.children[curPos];
|
||||
myElement.classList.add("incorrect");
|
||||
curPos++;
|
||||
} else if (item.action === "deleteLetter") {
|
||||
} else if (item.action === "setLetterIndex") {
|
||||
if (!nosound) playSound();
|
||||
let myElement = activeWord.children[curPos - 1];
|
||||
if (myElement.classList.contains("extra")) {
|
||||
myElement.remove();
|
||||
} else {
|
||||
myElement.className = "";
|
||||
curPos = item.value;
|
||||
// remove all letters from cursor to end of word
|
||||
for (const myElement of [...activeWord.children].slice(curPos)) {
|
||||
if (myElement.classList.contains("extra")) {
|
||||
myElement.remove();
|
||||
} else {
|
||||
myElement.className = "";
|
||||
}
|
||||
}
|
||||
curPos--;
|
||||
} else if (item.action === "submitCorrectWord") {
|
||||
if (!nosound) playSound();
|
||||
wordPos++;
|
||||
|
@ -110,15 +112,6 @@ function handleDisplayLogic(item, nosound = false) {
|
|||
activeWord.classList.add("error");
|
||||
wordPos++;
|
||||
curPos = 0;
|
||||
} else if (item.action === "clearWord") {
|
||||
if (!nosound) playSound();
|
||||
let promptWord = document.createElement("div");
|
||||
let wordArr = wordsList[wordPos].split("");
|
||||
wordArr.forEach((letter) => {
|
||||
promptWord.innerHTML += `<letter>${letter}</letter>`;
|
||||
});
|
||||
activeWord.innerHTML = promptWord.innerHTML;
|
||||
curPos = 0;
|
||||
} else if (item.action === "backWord") {
|
||||
if (!nosound) playSound();
|
||||
wordPos--;
|
||||
|
@ -195,16 +188,13 @@ function stopReplayRecording() {
|
|||
replayRecording = false;
|
||||
}
|
||||
|
||||
function addReplayEvent(action, letter = undefined) {
|
||||
if (replayRecording === false) {
|
||||
function addReplayEvent(action, value) {
|
||||
if (!replayRecording) {
|
||||
return;
|
||||
}
|
||||
|
||||
let timeDelta = performance.now() - replayStartTime;
|
||||
if (action === "incorrectLetter" || action === "correctLetter") {
|
||||
replayData.push({ action: action, letter: letter, time: timeDelta });
|
||||
} else {
|
||||
replayData.push({ action: action, time: timeDelta });
|
||||
}
|
||||
replayData.push({ action: action, value: value, time: timeDelta });
|
||||
}
|
||||
|
||||
function playReplay() {
|
||||
|
|
|
@ -196,10 +196,6 @@ async function initGroups() {
|
|||
"smoothLineScroll",
|
||||
UpdateConfig.setSmoothLineScroll
|
||||
);
|
||||
groups.capsLockBackspace = new SettingsGroup(
|
||||
"capsLockBackspace",
|
||||
UpdateConfig.setCapsLockBackspace
|
||||
);
|
||||
groups.lazyMode = new SettingsGroup("lazyMode", UpdateConfig.setLazyMode);
|
||||
groups.layout = new SettingsGroup("layout", UpdateConfig.setLayout);
|
||||
groups.language = new SettingsGroup("language", UpdateConfig.setLanguage);
|
||||
|
|
|
@ -14,10 +14,7 @@ function hide() {
|
|||
|
||||
$(document).keydown(function (event) {
|
||||
try {
|
||||
if (
|
||||
!Config.capsLockBackspace &&
|
||||
event.originalEvent.getModifierState("CapsLock")
|
||||
) {
|
||||
if (event.originalEvent.getModifierState("CapsLock")) {
|
||||
show();
|
||||
} else {
|
||||
hide();
|
||||
|
|
|
@ -36,6 +36,9 @@ export async function updatePosition() {
|
|||
let caret = $("#caret");
|
||||
|
||||
let inputLen = TestLogic.input.current.length;
|
||||
inputLen = Misc.trailingComposeChars.test(TestLogic.input.current)
|
||||
? TestLogic.input.current.search(Misc.trailingComposeChars) + 1
|
||||
: inputLen;
|
||||
let currentLetterIndex = inputLen - 1;
|
||||
if (currentLetterIndex == -1) {
|
||||
currentLetterIndex = 0;
|
||||
|
|
|
@ -2,104 +2,76 @@ import Config from "./config";
|
|||
import * as Misc from "./misc";
|
||||
import Layouts from "./layouts";
|
||||
|
||||
export function updateEvent(event) {
|
||||
export function getCharFromEvent(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();
|
||||
}
|
||||
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 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;
|
||||
let mapIndex = null;
|
||||
for (let i = 0; i < keyEventCodes.length; i++) {
|
||||
if (event.code == keyEventCodes[i]) {
|
||||
mapIndex = i;
|
||||
}
|
||||
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;
|
||||
if (!mapIndex) return null;
|
||||
const newKeyPreview = layoutMap[mapIndex][0];
|
||||
const shift = emulatedLayoutShouldShiftKey(event, newKeyPreview) ? 1 : 0;
|
||||
const char = layoutMap[mapIndex][shift];
|
||||
return char;
|
||||
}
|
||||
|
|
|
@ -906,6 +906,7 @@ export function restart(
|
|||
TestUI.focusWords();
|
||||
Funbox.resetMemoryTimer();
|
||||
RateQuotePopup.clearQuoteStats();
|
||||
$("#wordsInput").val(" ");
|
||||
|
||||
TestUI.reset();
|
||||
|
||||
|
@ -1517,15 +1518,9 @@ export async function finish(difficultyFailed = false) {
|
|||
|
||||
ChartController.result.data.datasets[2].data = errorsArray;
|
||||
|
||||
let kps = TestStats.keypressPerSecond.slice(
|
||||
Math.max(TestStats.keypressPerSecond.length - 5, 0)
|
||||
);
|
||||
let kps = TestStats.keypressPerSecond.slice(-5);
|
||||
|
||||
kps = kps.map((a) => a.count + a.mod);
|
||||
|
||||
kps = kps.reduce((a, b) => a + b, 0);
|
||||
|
||||
let afkDetected = kps === 0 ? true : false;
|
||||
let afkDetected = kps.every((second) => second.afk);
|
||||
|
||||
if (bailout) afkDetected = false;
|
||||
|
||||
|
|
|
@ -12,9 +12,9 @@ export let burstHistory = [];
|
|||
export let keypressPerSecond = [];
|
||||
export let currentKeypress = {
|
||||
count: 0,
|
||||
mod: 0,
|
||||
errors: 0,
|
||||
words: [],
|
||||
afk: true,
|
||||
};
|
||||
export let lastKeypress;
|
||||
export let currentBurstStart = 0;
|
||||
|
@ -94,9 +94,9 @@ export function restart() {
|
|||
keypressPerSecond = [];
|
||||
currentKeypress = {
|
||||
count: 0,
|
||||
mod: 0,
|
||||
errors: 0,
|
||||
words: [],
|
||||
afk: true,
|
||||
};
|
||||
currentBurstStart = 0;
|
||||
// errorsPerSecond = [];
|
||||
|
@ -179,8 +179,8 @@ export function incrementKeypressCount() {
|
|||
currentKeypress.count++;
|
||||
}
|
||||
|
||||
export function incrementKeypressMod() {
|
||||
currentKeypress.mod++;
|
||||
export function setKeypressNotAfk() {
|
||||
currentKeypress.afk = false;
|
||||
}
|
||||
|
||||
export function incrementKeypressErrors() {
|
||||
|
@ -195,9 +195,9 @@ export function pushKeypressesToHistory() {
|
|||
keypressPerSecond.push(currentKeypress);
|
||||
currentKeypress = {
|
||||
count: 0,
|
||||
mod: 0,
|
||||
errors: 0,
|
||||
words: [],
|
||||
afk: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ export function calculateAfkSeconds(testSeconds) {
|
|||
// `gonna add extra ${extraAfk} seconds of afk because of no keypress data`
|
||||
// );
|
||||
}
|
||||
let ret = keypressPerSecond.filter((x) => x.count == 0 && x.mod == 0).length;
|
||||
let ret = keypressPerSecond.filter((x) => x.afk).length;
|
||||
return ret + extraAfk;
|
||||
}
|
||||
|
||||
|
@ -233,7 +233,7 @@ export function calculateBurst() {
|
|||
let timeToWrite = (performance.now() - currentBurstStart) / 1000;
|
||||
let wordLength;
|
||||
if (Config.mode === "zen") {
|
||||
wordLength = TestLogic.input.getCurrent().length;
|
||||
wordLength = TestLogic.input.current.length;
|
||||
} else {
|
||||
wordLength = TestLogic.words.getCurrent().length;
|
||||
}
|
||||
|
|
|
@ -275,9 +275,7 @@ export async function screenshot() {
|
|||
}, 3000);
|
||||
}
|
||||
|
||||
export function updateWordElement(showError) {
|
||||
// if (Config.mode == "zen") return;
|
||||
|
||||
export function updateWordElement(showError = !Config.blindMode) {
|
||||
let input = TestLogic.input.current;
|
||||
let wordAtIndex;
|
||||
let currentWord;
|
||||
|
@ -295,28 +293,31 @@ export function updateWordElement(showError) {
|
|||
newlineafter = true;
|
||||
ret += `<letter class='nlChar correct' style="opacity: 0"><i class="fas fa-angle-down"></i></letter>`;
|
||||
} else {
|
||||
ret +=
|
||||
`<letter class="correct">` + TestLogic.input.current[i] + `</letter>`;
|
||||
ret += `<letter class="correct">${TestLogic.input.current[i]}</letter>`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let correctSoFar = false;
|
||||
if (currentWord.slice(0, input.length) == input) {
|
||||
// this is when input so far is correct
|
||||
|
||||
// slice earlier if input has trailing compose characters
|
||||
const inputWithoutComposeLength = Misc.trailingComposeChars.test(input)
|
||||
? input.search(Misc.trailingComposeChars)
|
||||
: input.length;
|
||||
if (
|
||||
input.search(Misc.trailingComposeChars) < currentWord.length &&
|
||||
currentWord.slice(0, inputWithoutComposeLength) ===
|
||||
input.slice(0, inputWithoutComposeLength)
|
||||
) {
|
||||
correctSoFar = true;
|
||||
}
|
||||
|
||||
let wordHighlightClassString = correctSoFar ? "correct" : "incorrect";
|
||||
if (Config.blindMode) {
|
||||
wordHighlightClassString = "correct";
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
let charCorrect;
|
||||
if (currentWord[i] == input[i]) {
|
||||
charCorrect = true;
|
||||
} else {
|
||||
charCorrect = false;
|
||||
}
|
||||
let charCorrect = currentWord[i] == input[i];
|
||||
|
||||
let correctClass = "correct";
|
||||
if (Config.highlightMode == "off") {
|
||||
|
@ -334,51 +335,64 @@ export function updateWordElement(showError) {
|
|||
currentLetter = `<i class="fas fa-angle-down"></i>`;
|
||||
}
|
||||
|
||||
if (
|
||||
Misc.trailingComposeChars.test(input) &&
|
||||
i > input.search(Misc.trailingComposeChars)
|
||||
)
|
||||
continue;
|
||||
|
||||
if (charCorrect) {
|
||||
ret += `<letter class="${
|
||||
Config.highlightMode == "word"
|
||||
? wordHighlightClassString
|
||||
: correctClass
|
||||
} ${tabChar}${nlChar}">${currentLetter}</letter>`;
|
||||
} else {
|
||||
if (!showError) {
|
||||
if (currentLetter !== undefined) {
|
||||
ret += `<letter class="${
|
||||
Config.highlightMode == "word"
|
||||
? wordHighlightClassString
|
||||
: correctClass
|
||||
} ${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="${
|
||||
Config.highlightMode == "word"
|
||||
? wordHighlightClassString
|
||||
: "incorrect"
|
||||
} extra ${tabChar}${nlChar}">${letter}</letter>`;
|
||||
}
|
||||
} else {
|
||||
ret +=
|
||||
`<letter class="${
|
||||
Config.highlightMode == "word"
|
||||
? wordHighlightClassString
|
||||
: "incorrect"
|
||||
} ${tabChar}${nlChar}">` +
|
||||
currentLetter +
|
||||
(Config.indicateTypos ? `<hint>${input[i]}</hint>` : "") +
|
||||
"</letter>";
|
||||
}
|
||||
} else if (
|
||||
currentLetter !== undefined &&
|
||||
Misc.trailingComposeChars.test(input) &&
|
||||
i === input.search(Misc.trailingComposeChars)
|
||||
) {
|
||||
ret += `<letter class="${
|
||||
Config.highlightMode == "word" ? wordHighlightClassString : ""
|
||||
} dead">${currentLetter}</letter>`;
|
||||
} else if (!showError) {
|
||||
if (currentLetter !== undefined) {
|
||||
ret += `<letter class="${
|
||||
Config.highlightMode == "word"
|
||||
? wordHighlightClassString
|
||||
: correctClass
|
||||
} ${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="${
|
||||
Config.highlightMode == "word"
|
||||
? wordHighlightClassString
|
||||
: "incorrect"
|
||||
} extra ${tabChar}${nlChar}">${letter}</letter>`;
|
||||
}
|
||||
} else {
|
||||
ret +=
|
||||
`<letter class="${
|
||||
Config.highlightMode == "word"
|
||||
? wordHighlightClassString
|
||||
: "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++) {
|
||||
const inputWithSingleComposeLength = Misc.trailingComposeChars.test(input)
|
||||
? input.search(Misc.trailingComposeChars) + 1
|
||||
: input.length;
|
||||
if (inputWithSingleComposeLength < currentWord.length) {
|
||||
for (let i = inputWithSingleComposeLength; 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") {
|
||||
|
|
|
@ -2498,12 +2498,17 @@ key {
|
|||
}
|
||||
|
||||
#wordsInput {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
display: block;
|
||||
resize: none;
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#wordsTitle {
|
||||
|
|
|
@ -839,7 +839,15 @@
|
|||
</div>
|
||||
<div id="memoryTimer">Time left to memorise all words: 0s</div>
|
||||
<div id="testModesNotice"></div>
|
||||
<input id="wordsInput" class="" tabindex="0" autocomplete="off" />
|
||||
<input
|
||||
id="wordsInput"
|
||||
class=""
|
||||
tabindex="0"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
>
|
||||
<div id="timerNumber" class="timerMain">
|
||||
<div>60</div>
|
||||
</div>
|
||||
|
@ -2424,18 +2432,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section capsLockBackspace">
|
||||
<h1>caps lock backspace</h1>
|
||||
<div class="text">Makes caps lock act like backspace.</div>
|
||||
<div class="buttons">
|
||||
<div class="button off" tabindex="0" onclick="this.blur();">
|
||||
off
|
||||
</div>
|
||||
<div class="button on" tabindex="0" onclick="this.blur();">
|
||||
on
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section lazyMode">
|
||||
<h1>lazy mode</h1>
|
||||
<div class="text">
|
||||
|
|
Loading…
Reference in a new issue