From ac58a4f1fabeb0e18a4776a9e9fd60d10f92bb97 Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Wed, 6 May 2020 22:04:31 +1000 Subject: [PATCH 01/16] add custom mode --- index.html | 3 +++ main.js | 41 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 3e984cd..24e70f4 100644 --- a/index.html +++ b/index.html @@ -47,6 +47,9 @@ / 240 + + CUSTOM +
WPM: XX / ACC: XX
diff --git a/main.js b/main.js index f855c8e..9394a33 100644 --- a/main.js +++ b/main.js @@ -27,6 +27,7 @@ getCookie('punctuation') === '' ? setPunctuation('false') : setPunctuation(getCo // Find a list of words and display it to textDisplay function setText() { + console.log("set text called") // Reset wordList = []; currentWord = 0; @@ -38,6 +39,13 @@ function setText() { inputField.className = ''; switch (typingMode) { + case 'custom': + textDisplay.style.height = 'auto'; + textDisplay.innerHTML = ''; + console.log(inputField.value); + wordList = betterSplit(inputField.value); + break; + case 'wordcount': textDisplay.style.height = 'auto'; textDisplay.innerHTML = ''; @@ -111,6 +119,7 @@ function showText() { inputField.addEventListener('keydown', e => { // Add wrong class to input field switch (typingMode) { + case 'custom': case 'wordcount': if (currentWord < wordList.length) inputFieldClass(); case 'time': @@ -133,6 +142,7 @@ inputField.addEventListener('keydown', e => { // If it is the first character entered if (currentWord === 0 && inputField.value === '') { switch (typingMode) { + case 'custom': case 'wordcount': startDate = Date.now(); break; @@ -162,6 +172,7 @@ inputField.addEventListener('keydown', e => { // If it is the space key check the word and add correct/wrong class if (e.key === ' ') { + console.log("SPACE DETECTED"); e.preventDefault(); if (inputField.value !== '') { @@ -207,6 +218,7 @@ inputField.addEventListener('keydown', e => { function showResult() { let words, minute, acc; switch (typingMode) { + case 'custom': case 'wordcount': words = correctKeys / 5; minute = (Date.now() - startDate) / 1000 / 60; @@ -310,16 +322,26 @@ function setTypingMode(_mode) { typingMode = mode; setCookie('typingMode', mode, 90); document.querySelector('#word-count').style.display = 'inline'; - document.querySelector('#time-count').style.display = 'none'; + document.querySelector('#time-count').style.display = 'none'; + document.querySelector('#custom').style.display = 'none'; setText(); break; case 'time': typingMode = mode; setCookie('typingMode', mode, 90); document.querySelector('#word-count').style.display = 'none'; - document.querySelector('#time-count').style.display = 'inline'; + document.querySelector('#time-count').style.display = 'inline'; + document.querySelector('#custom').style.display = 'none'; setText(); - break; + break; + case 'custom': + typingMode = mode; + setCookie('typingMode', mode, 90); + document.querySelector('#word-count').style.display = 'none'; + document.querySelector('#time-count').style.display = 'none'; + document.querySelector('#custom').style.display = 'inline'; + setText(); + break; default: console.error(`mode ${mode} is undefine`); } @@ -445,4 +467,15 @@ function hideThemeCenter() { document.getElementById('command-center').classList.remove('hidden'); } - +function betterSplit(str) { + // Regex tester for: + // Normal a-z, A-Z + // punjabi, + // CJK, + // Russian + // TODO: Add support for remaining European languages + // Cleaner way to do this??? + const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]|[0-9]+|[\u0400-\u04FFa-zA-Z!:;'",./?!@#$%^&*()-_{}\[\]]+\'*[a-z]*/g; + let array = [...str.matchAll(regex)]; + return array; +} \ No newline at end of file From b693041a87db66d63667066319b798e074d082c1 Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Thu, 7 May 2020 14:26:56 +1000 Subject: [PATCH 02/16] add custom text functionality --- main.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/main.js b/main.js index 9394a33..cc0ca3b 100644 --- a/main.js +++ b/main.js @@ -32,7 +32,6 @@ function setText() { wordList = []; currentWord = 0; correctKeys = 0; - inputField.value = ''; timerActive = false; clearTimeout(timer); textDisplay.style.display = 'block'; @@ -68,7 +67,7 @@ function setText() { wordList.push(randomWords[n]); } } - + inputField.value = ''; if (punctuation) addPunctuations(); showText(); inputField.focus(); @@ -324,6 +323,8 @@ function setTypingMode(_mode) { document.querySelector('#word-count').style.display = 'inline'; document.querySelector('#time-count').style.display = 'none'; document.querySelector('#custom').style.display = 'none'; + document.querySelector('#redo-button').style.display = 'inline'; + document.querySelector('#set-button').style.display = 'none'; setText(); break; case 'time': @@ -332,6 +333,8 @@ function setTypingMode(_mode) { document.querySelector('#word-count').style.display = 'none'; document.querySelector('#time-count').style.display = 'inline'; document.querySelector('#custom').style.display = 'none'; + document.querySelector('#redo-button').style.display = 'inline'; + document.querySelector('#set-button').style.display = 'none'; setText(); break; case 'custom': @@ -340,6 +343,8 @@ function setTypingMode(_mode) { document.querySelector('#word-count').style.display = 'none'; document.querySelector('#time-count').style.display = 'none'; document.querySelector('#custom').style.display = 'inline'; + document.querySelector('#redo-button').style.display = 'none'; + document.querySelector('#set-button').style.display = 'inline'; setText(); break; default: @@ -476,6 +481,6 @@ function betterSplit(str) { // TODO: Add support for remaining European languages // Cleaner way to do this??? const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]|[0-9]+|[\u0400-\u04FFa-zA-Z!:;'",./?!@#$%^&*()-_{}\[\]]+\'*[a-z]*/g; - let array = [...str.matchAll(regex)]; + let array = [...str.match(regex)]; return array; } \ No newline at end of file From c3f40d2f0779ec946062de58710a9735955b378d Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Thu, 7 May 2020 14:55:31 +1000 Subject: [PATCH 03/16] fix kinks in custom mode --- main.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/main.js b/main.js index cc0ca3b..2a1a584 100644 --- a/main.js +++ b/main.js @@ -39,13 +39,16 @@ function setText() { switch (typingMode) { case 'custom': + const helpText = "Paste your custom text and click redo!" + wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); + punctuation = false; textDisplay.style.height = 'auto'; textDisplay.innerHTML = ''; - console.log(inputField.value); - wordList = betterSplit(inputField.value); + inputField.value=''; break; case 'wordcount': + inputField.value = ''; textDisplay.style.height = 'auto'; textDisplay.innerHTML = ''; wordList = []; @@ -58,6 +61,7 @@ function setText() { break; case 'time': + inputField.value = ''; textDisplay.style.height = '3.2rem'; document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; textDisplay.innerHTML = ''; @@ -67,7 +71,6 @@ function setText() { wordList.push(randomWords[n]); } } - inputField.value = ''; if (punctuation) addPunctuations(); showText(); inputField.focus(); @@ -322,9 +325,7 @@ function setTypingMode(_mode) { setCookie('typingMode', mode, 90); document.querySelector('#word-count').style.display = 'inline'; document.querySelector('#time-count').style.display = 'none'; - document.querySelector('#custom').style.display = 'none'; - document.querySelector('#redo-button').style.display = 'inline'; - document.querySelector('#set-button').style.display = 'none'; + document.querySelector('#custom').style.display = 'none'; setText(); break; case 'time': @@ -333,8 +334,6 @@ function setTypingMode(_mode) { document.querySelector('#word-count').style.display = 'none'; document.querySelector('#time-count').style.display = 'inline'; document.querySelector('#custom').style.display = 'none'; - document.querySelector('#redo-button').style.display = 'inline'; - document.querySelector('#set-button').style.display = 'none'; setText(); break; case 'custom': @@ -343,8 +342,6 @@ function setTypingMode(_mode) { document.querySelector('#word-count').style.display = 'none'; document.querySelector('#time-count').style.display = 'none'; document.querySelector('#custom').style.display = 'inline'; - document.querySelector('#redo-button').style.display = 'none'; - document.querySelector('#set-button').style.display = 'inline'; setText(); break; default: @@ -480,7 +477,7 @@ function betterSplit(str) { // Russian // TODO: Add support for remaining European languages // Cleaner way to do this??? - const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]|[0-9]+|[\u0400-\u04FFa-zA-Z!:;'",./?!@#$%^&*()-_{}\[\]]+\'*[a-z]*/g; + const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9!:;'",./?!@#$%^&*()-_{}\[\]]+\'*[a-z]*/g; let array = [...str.match(regex)]; return array; } \ No newline at end of file From 1945577f3995b5f59b4555f4c868b7c684491f2f Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Thu, 7 May 2020 15:05:32 +1000 Subject: [PATCH 04/16] add auto-scroll to custom display box --- main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index 2a1a584..e2d6f8f 100644 --- a/main.js +++ b/main.js @@ -42,7 +42,7 @@ function setText() { const helpText = "Paste your custom text and click redo!" wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); punctuation = false; - textDisplay.style.height = 'auto'; + textDisplay.style.height = '3.2rem'; textDisplay.innerHTML = ''; inputField.value=''; break; @@ -179,7 +179,7 @@ inputField.addEventListener('keydown', e => { if (inputField.value !== '') { // Scroll down text when reach new line - if (typingMode === 'time') { + if (typingMode === 'time' || typingMode === 'custom') { const currentWordPosition = textDisplay.childNodes[currentWord].getBoundingClientRect(); const nextWordPosition = textDisplay.childNodes[currentWord + 1].getBoundingClientRect(); if (currentWordPosition.top < nextWordPosition.top) { From 8a9c3bf17ff0ea5418da5ea15c94c75df5bc5b90 Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Thu, 7 May 2020 15:25:53 +1000 Subject: [PATCH 05/16] expanded punctuation mark test --- index.html | 2 +- main.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 24e70f4..dd9fbb3 100644 --- a/index.html +++ b/index.html @@ -48,7 +48,7 @@ 240 - CUSTOM + CUSTOM
WPM: XX / ACC: XX
diff --git a/main.js b/main.js index e2d6f8f..4270263 100644 --- a/main.js +++ b/main.js @@ -128,7 +128,8 @@ inputField.addEventListener('keydown', e => { if (timerActive) inputFieldClass(); } function inputFieldClass() { - if (e.key >= 'a' && e.key <= 'z' || (e.key === `'` || e.key === ',' || e.key === '.' || e.key === ';')) { + const puncRegex = /[!:;'",.\/?!@#$%^&*()_}{\[\]-]/; + if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { let inputWordSlice = inputField.value + e.key; let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; @@ -477,7 +478,7 @@ function betterSplit(str) { // Russian // TODO: Add support for remaining European languages // Cleaner way to do this??? - const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9!:;'",./?!@#$%^&*()-_{}\[\]]+\'*[a-z]*/g; + const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9!:;'",.\/?!@#$%^&*()_}{\[\]-]+\'*[a-z]*/g; let array = [...str.match(regex)]; - return array; + return array; } \ No newline at end of file From ed6a13cbd0d083dd3da207d5838a10bb74f5129f Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Thu, 7 May 2020 15:30:31 +1000 Subject: [PATCH 06/16] add 'custom' to list of typing mode codes --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a18b43..3823c16 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ available modes: - `wordcount` - `time` +- `custom` ## punctuation @@ -92,4 +93,4 @@ acc: total number of characters (including spaces) of words you got right divide ## support -- PayPal \ No newline at end of file +- PayPal From 954f3d975f28b7f41f4677e2d1f363916c4e64c2 Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Thu, 7 May 2020 15:32:48 +1000 Subject: [PATCH 07/16] rm debug outputs --- main.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.js b/main.js index 4270263..c763969 100644 --- a/main.js +++ b/main.js @@ -27,7 +27,6 @@ getCookie('punctuation') === '' ? setPunctuation('false') : setPunctuation(getCo // Find a list of words and display it to textDisplay function setText() { - console.log("set text called") // Reset wordList = []; currentWord = 0; @@ -175,7 +174,6 @@ inputField.addEventListener('keydown', e => { // If it is the space key check the word and add correct/wrong class if (e.key === ' ') { - console.log("SPACE DETECTED"); e.preventDefault(); if (inputField.value !== '') { From 7fc34ca229f8b34dc854cf479abafe6a0c5beb6c Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Fri, 8 May 2020 15:58:46 +1000 Subject: [PATCH 08/16] fix indentation to spaces --- main.js | 962 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 481 insertions(+), 481 deletions(-) diff --git a/main.js b/main.js index c763969..823348f 100644 --- a/main.js +++ b/main.js @@ -1,482 +1,482 @@ -// Get document element -const textDisplay = document.querySelector('#text-display'); -const inputField = document.querySelector('#input-field'); - -// Initialize typing mode variables -let typingMode = 'wordcount'; -let wordCount; -let timeCount; - -// Initialize dynamic variables -let randomWords = []; -let wordList = []; -let currentWord = 0; -let correctKeys = 0; -let startDate = 0; -let timer; -let timerActive = false; -let punctuation = false; - -// Get cookies -getCookie('theme') === '' ? setTheme('light') : setTheme(getCookie('theme')); -getCookie('language') === '' ? setLanguage('english') : setLanguage(getCookie('language')); -getCookie('wordCount') === '' ? setWordCount(50) : setWordCount(getCookie('wordCount')); -getCookie('timeCount') === '' ? setTimeCount(60) : setTimeCount(getCookie('timeCount')); -getCookie('typingMode') === '' ? setTypingMode('wordcount') : setTypingMode(getCookie('typingMode')); -getCookie('punctuation') === '' ? setPunctuation('false') : setPunctuation(getCookie('punctuation')); - -// Find a list of words and display it to textDisplay -function setText() { - // Reset - wordList = []; - currentWord = 0; - correctKeys = 0; - timerActive = false; - clearTimeout(timer); - textDisplay.style.display = 'block'; - inputField.className = ''; - - switch (typingMode) { - case 'custom': - const helpText = "Paste your custom text and click redo!" - wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); - punctuation = false; - textDisplay.style.height = '3.2rem'; - textDisplay.innerHTML = ''; - inputField.value=''; - break; - - case 'wordcount': - inputField.value = ''; - textDisplay.style.height = 'auto'; - textDisplay.innerHTML = ''; - wordList = []; - while (wordList.length < wordCount) { - const randomWord = randomWords[Math.floor(Math.random() * randomWords.length)]; - if (wordList[wordList.length - 1] !== randomWord || wordList[wordList.length - 1] === undefined || getCookie('language') === 'dots') { - wordList.push(randomWord); - } - } - break; - - case 'time': - inputField.value = ''; - textDisplay.style.height = '3.2rem'; - document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; - textDisplay.innerHTML = ''; - wordList = []; - for (i = 0; i < 500; i++) { - let n = Math.floor(Math.random() * randomWords.length); - wordList.push(randomWords[n]); - } - } - if (punctuation) addPunctuations(); - showText(); - inputField.focus(); -} - -function addPunctuations() { - if (wordList[0] !== undefined) { - // Capitalize first word - wordList[0] = wordList[0][0].toUpperCase() + wordList[0].slice(1); - - // Add comma, fullstop, question mark, exclamation mark, semicolon. Capitalize the next word - for (i = 0; i < wordList.length; i++) { - const ran = Math.random(); - if (i < wordList.length - 1) { - if (ran < 0.03) { - wordList[i] += ','; - } else if (ran < 0.05) { - wordList[i] += '.'; - wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); - } else if (ran < 0.06) { - wordList[i] += '?'; - wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); - } else if (ran < 0.07) { - wordList[i] += '!'; - wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); - } else if (ran < 0.08) { - wordList[i] += ';'; - } - } - } - wordList[wordList.length - 1] += '.'; - - // Add quotation marks - } -} - -// Display text to textDisplay -function showText() { - wordList.forEach(word => { - let span = document.createElement('span'); - span.innerHTML = word + ' '; - textDisplay.appendChild(span); - }); - textDisplay.firstChild.classList.add('highlight'); -} - -// Key is pressed in input field -inputField.addEventListener('keydown', e => { - // Add wrong class to input field - switch (typingMode) { - case 'custom': - case 'wordcount': - if (currentWord < wordList.length) inputFieldClass(); - case 'time': - if (timerActive) inputFieldClass(); - } - function inputFieldClass() { - const puncRegex = /[!:;'",.\/?!@#$%^&*()_}{\[\]-]/; - if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { - let inputWordSlice = inputField.value + e.key; - let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); - inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; - } else if (e.key === 'Backspace') { - let inputWordSlice = e.ctrlKey ? '' : inputField.value.slice(0, inputField.value.length - 1); - let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); - inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; - } else if (e.key === ' ') { - inputField.className = ''; - } - } - - // If it is the first character entered - if (currentWord === 0 && inputField.value === '') { - switch (typingMode) { - case 'custom': - case 'wordcount': - startDate = Date.now(); - break; - - case 'time': - if (!timerActive) { - startTimer(timeCount); - timerActive = true; - } - function startTimer(time) { - if (time > 0) { - document.querySelector(`#tc-${timeCount}`).innerHTML = time; - timer = setTimeout(() => { - time--; - startTimer(time); - }, 1000); - } else { - timerActive = false; - textDisplay.style.display = 'none'; - inputField.className = ''; - document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; - showResult(); - } - } - } - } - - // If it is the space key check the word and add correct/wrong class - if (e.key === ' ') { - e.preventDefault(); - - if (inputField.value !== '') { - // Scroll down text when reach new line - if (typingMode === 'time' || typingMode === 'custom') { - const currentWordPosition = textDisplay.childNodes[currentWord].getBoundingClientRect(); - const nextWordPosition = textDisplay.childNodes[currentWord + 1].getBoundingClientRect(); - if (currentWordPosition.top < nextWordPosition.top) { - for (i = 0; i < currentWord + 1; i++) textDisplay.childNodes[i].style.display = 'none'; - } - } - - // If it is not the last word increment currentWord, - if (currentWord < wordList.length - 1) { - if (inputField.value === wordList[currentWord]) { - textDisplay.childNodes[currentWord].classList.add('correct'); - correctKeys += wordList[currentWord].length + 1; - } else { - textDisplay.childNodes[currentWord].classList.add('wrong'); - } - textDisplay.childNodes[currentWord + 1].classList.add('highlight'); - } else if (currentWord === wordList.length - 1) { - textDisplay.childNodes[currentWord].classList.add('wrong'); - showResult(); - } - - inputField.value = ''; - currentWord++; - } - - // Else if it is the last word and input word is correct show the result - } else if (currentWord === wordList.length - 1) { - if (inputField.value + e.key === wordList[currentWord]) { - textDisplay.childNodes[currentWord].classList.add('correct'); - correctKeys += wordList[currentWord].length; - currentWord++; - showResult(); - } - } -}); - -// Calculate and display result -function showResult() { - let words, minute, acc; - switch (typingMode) { - case 'custom': - case 'wordcount': - words = correctKeys / 5; - minute = (Date.now() - startDate) / 1000 / 60; - let totalKeys = -1; - wordList.forEach(e => (totalKeys += e.length + 1)); - acc = Math.floor((correctKeys / totalKeys) * 100); - break; - - case 'time': - words = correctKeys / 5; - minute = timeCount / 60; - let sumKeys = -1; - for (i = 0; i < currentWord; i++) { - sumKeys += wordList[i].length + 1; - } - acc = acc = Math.min(Math.floor((correctKeys / sumKeys) * 100), 100); - } - let wpm = Math.floor(words / minute); - document.querySelector('#right-wing').innerHTML = `WPM: ${wpm} / ACC: ${acc}`; -} - -// Command actions -document.addEventListener('keydown', e => { - // Modifiers Windows: [Alt], Mac: [Cmd + Ctrl] - if (e.altKey || (e.metaKey && e.ctrlKey)) { - // [mod + t] => Change the theme - if (e.key === 't') { - setTheme(inputField.value); - } - // [mod + l] => Change the language - if (e.key === 'l') { - setLanguage(inputField.value); - } - - // [mod + m] => Change the typing mode - if (e.key === 'm') { - setTypingMode(inputField.value); - } - - // [mod + p] => Change punctuation active - if (e.key === 'p') { - setPunctuation(inputField.value); - } - } else if (!document.querySelector('#theme-center').classList.contains('hidden')) { - if (e.key === 'Escape'){ - hideThemeCenter(); - inputField.focus(); - } - } -}); - -function setTheme(_theme) { - const theme = _theme.toLowerCase(); - fetch(`themes/${theme}.css`) - .then(response => { - if (response.status === 200) { - response - .text() - .then(css => { - setCookie('theme', theme, 90); - document.querySelector('#theme').setAttribute('href', `themes/${theme}.css`); - setText(); - }) - .catch(err => console.error(err)); - } else { - console.log(`theme ${theme} is undefine`); - } - }) - .catch(err => console.error(err)); -} - -function setLanguage(_lang) { - const lang = _lang.toLowerCase(); - fetch('texts/random.json') - .then(response => response.json()) - .then(json => { - if (typeof json[lang] !== 'undefined') { - randomWords = json[lang]; - setCookie('language', lang, 90); - - if (lang === "arabic") { - textDisplay.style.direction = "rtl" - inputField.style.direction = "rtl" - } else { - textDisplay.style.direction = "ltr" - inputField.style.direction = "ltr" - } - - setText(); - } else { - console.error(`language ${lang} is undefine`); - } - }) - .catch(err => console.error(err)); -} - -function setTypingMode(_mode) { - const mode = _mode.toLowerCase(); - switch (mode) { - case 'wordcount': - typingMode = mode; - setCookie('typingMode', mode, 90); - document.querySelector('#word-count').style.display = 'inline'; - document.querySelector('#time-count').style.display = 'none'; - document.querySelector('#custom').style.display = 'none'; - setText(); - break; - case 'time': - typingMode = mode; - setCookie('typingMode', mode, 90); - document.querySelector('#word-count').style.display = 'none'; - document.querySelector('#time-count').style.display = 'inline'; - document.querySelector('#custom').style.display = 'none'; - setText(); - break; - case 'custom': - typingMode = mode; - setCookie('typingMode', mode, 90); - document.querySelector('#word-count').style.display = 'none'; - document.querySelector('#time-count').style.display = 'none'; - document.querySelector('#custom').style.display = 'inline'; - setText(); - break; - default: - console.error(`mode ${mode} is undefine`); - } -} - -function setPunctuation(_punc) { - const punc = _punc.toLowerCase(); - if (punc === 'true') { - punctuation = true; - setCookie('punctuation', true, 90); - setText(); - } else if (punc === 'false') { - punctuation = false; - setCookie('punctuation', false, 90); - setText(); - } -} - -function setWordCount(wc) { - setCookie('wordCount', wc, 90); - wordCount = wc; - document.querySelectorAll('#word-count > span').forEach(e => (e.style.borderBottom = '')); - document.querySelector(`#wc-${wordCount}`).style.borderBottom = '2px solid'; - setText(); -} - -function setTimeCount(tc) { - setCookie('timeCount', tc, 90); - timeCount = tc; - document.querySelectorAll('#time-count > span').forEach(e => { - e.style.borderBottom = ''; - e.innerHTML = e.id.substring(3, 6); - }); - document.querySelector(`#tc-${timeCount}`).style.borderBottom = '2px solid'; - setText(); -} - -function setCookie(cname, cvalue, exdays) { - var d = new Date(); - d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); - var expires = 'expires=' + d.toUTCString(); - document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; -} - -function getCookie(cname) { - var name = cname + '='; - var decodedCookie = decodeURIComponent(document.cookie); - var ca = decodedCookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } - } - return ''; -} - -showAllThemes(); -function showAllThemes(){ - fetch(`themes/theme-list.json`) - .then(response => { - if (response.status === 200) { - response - .text() - .then(body => { - let themes = JSON.parse(body); - let keys = Object.keys(themes); - let i; - for(i = 0;i < keys.length; i ++){ - - let theme = document.createElement('div'); - theme.setAttribute('class', 'theme-button'); - theme.setAttribute('onClick', `setTheme('${keys[i]}')`); - theme.setAttribute('id', keys[i]); - - // set tabindex to current theme index + 4 for the test page - theme.setAttribute('tabindex', i + 5); - theme.addEventListener('keydown', e => { - if (e.key === 'Enter') { - setTheme(theme.id); - inputField.focus(); - - } - }) - - if(themes[keys[i]]['customHTML'] != undefined){ - theme.style.background = themes[keys[i]]['background']; - theme.innerHTML = themes[keys[i]]['customHTML'] - }else{ - theme.textContent = keys[i]; - theme.style.background = themes[keys[i]]['background']; - theme.style.color = themes[keys[i]]['color']; - } - document.getElementById('theme-area').appendChild(theme); - } - }) - .catch(err => console.error(err)); - } else { - console.log(`Cant find theme-list.json`); - } - }) - .catch(err => console.error(err)); -} - -// enter to open theme area -document.getElementById('show-themes').addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - showThemeCenter(); - inputField.focus(); - } -}); - -function showThemeCenter() { - document.getElementById('theme-center').classList.remove('hidden'); - document.getElementById('command-center').classList.add('hidden'); -} - -function hideThemeCenter() { - document.getElementById('theme-center').classList.add('hidden'); - document.getElementById('command-center').classList.remove('hidden'); -} - -function betterSplit(str) { - // Regex tester for: - // Normal a-z, A-Z - // punjabi, - // CJK, - // Russian - // TODO: Add support for remaining European languages - // Cleaner way to do this??? - const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9!:;'",.\/?!@#$%^&*()_}{\[\]-]+\'*[a-z]*/g; - let array = [...str.match(regex)]; - return array; +// Get document element +const textDisplay = document.querySelector('#text-display'); +const inputField = document.querySelector('#input-field'); + +// Initialize typing mode variables +let typingMode = 'wordcount'; +let wordCount; +let timeCount; + +// Initialize dynamic variables +let randomWords = []; +let wordList = []; +let currentWord = 0; +let correctKeys = 0; +let startDate = 0; +let timer; +let timerActive = false; +let punctuation = false; + +// Get cookies +getCookie('theme') === '' ? setTheme('light') : setTheme(getCookie('theme')); +getCookie('language') === '' ? setLanguage('english') : setLanguage(getCookie('language')); +getCookie('wordCount') === '' ? setWordCount(50) : setWordCount(getCookie('wordCount')); +getCookie('timeCount') === '' ? setTimeCount(60) : setTimeCount(getCookie('timeCount')); +getCookie('typingMode') === '' ? setTypingMode('wordcount') : setTypingMode(getCookie('typingMode')); +getCookie('punctuation') === '' ? setPunctuation('false') : setPunctuation(getCookie('punctuation')); + +// Find a list of words and display it to textDisplay +function setText() { + // Reset + wordList = []; + currentWord = 0; + correctKeys = 0; + timerActive = false; + clearTimeout(timer); + textDisplay.style.display = 'block'; + inputField.className = ''; + + switch (typingMode) { + case 'custom': + const helpText = "Paste your custom text and click redo!" + wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); + punctuation = false; + textDisplay.style.height = '3.2rem'; + textDisplay.innerHTML = ''; + inputField.value=''; + break; + + case 'wordcount': + inputField.value = ''; + textDisplay.style.height = 'auto'; + textDisplay.innerHTML = ''; + wordList = []; + while (wordList.length < wordCount) { + const randomWord = randomWords[Math.floor(Math.random() * randomWords.length)]; + if (wordList[wordList.length - 1] !== randomWord || wordList[wordList.length - 1] === undefined || getCookie('language') === 'dots') { + wordList.push(randomWord); + } + } + break; + + case 'time': + inputField.value = ''; + textDisplay.style.height = '3.2rem'; + document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; + textDisplay.innerHTML = ''; + wordList = []; + for (i = 0; i < 500; i++) { + let n = Math.floor(Math.random() * randomWords.length); + wordList.push(randomWords[n]); + } + } + if (punctuation) addPunctuations(); + showText(); + inputField.focus(); +} + +function addPunctuations() { + if (wordList[0] !== undefined) { + // Capitalize first word + wordList[0] = wordList[0][0].toUpperCase() + wordList[0].slice(1); + + // Add comma, fullstop, question mark, exclamation mark, semicolon. Capitalize the next word + for (i = 0; i < wordList.length; i++) { + const ran = Math.random(); + if (i < wordList.length - 1) { + if (ran < 0.03) { + wordList[i] += ','; + } else if (ran < 0.05) { + wordList[i] += '.'; + wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); + } else if (ran < 0.06) { + wordList[i] += '?'; + wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); + } else if (ran < 0.07) { + wordList[i] += '!'; + wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); + } else if (ran < 0.08) { + wordList[i] += ';'; + } + } + } + wordList[wordList.length - 1] += '.'; + + // Add quotation marks + } +} + +// Display text to textDisplay +function showText() { + wordList.forEach(word => { + let span = document.createElement('span'); + span.innerHTML = word + ' '; + textDisplay.appendChild(span); + }); + textDisplay.firstChild.classList.add('highlight'); +} + +// Key is pressed in input field +inputField.addEventListener('keydown', e => { + // Add wrong class to input field + switch (typingMode) { + case 'custom': + case 'wordcount': + if (currentWord < wordList.length) inputFieldClass(); + case 'time': + if (timerActive) inputFieldClass(); + } + function inputFieldClass() { + const puncRegex = /[!:;'",.\/?!@#$%^&*()_}{\[\]-]/; + if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { + let inputWordSlice = inputField.value + e.key; + let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); + inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; + } else if (e.key === 'Backspace') { + let inputWordSlice = e.ctrlKey ? '' : inputField.value.slice(0, inputField.value.length - 1); + let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); + inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; + } else if (e.key === ' ') { + inputField.className = ''; + } + } + + // If it is the first character entered + if (currentWord === 0 && inputField.value === '') { + switch (typingMode) { + case 'custom': + case 'wordcount': + startDate = Date.now(); + break; + + case 'time': + if (!timerActive) { + startTimer(timeCount); + timerActive = true; + } + function startTimer(time) { + if (time > 0) { + document.querySelector(`#tc-${timeCount}`).innerHTML = time; + timer = setTimeout(() => { + time--; + startTimer(time); + }, 1000); + } else { + timerActive = false; + textDisplay.style.display = 'none'; + inputField.className = ''; + document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; + showResult(); + } + } + } + } + + // If it is the space key check the word and add correct/wrong class + if (e.key === ' ') { + e.preventDefault(); + + if (inputField.value !== '') { + // Scroll down text when reach new line + if (typingMode === 'time' || typingMode === 'custom') { + const currentWordPosition = textDisplay.childNodes[currentWord].getBoundingClientRect(); + const nextWordPosition = textDisplay.childNodes[currentWord + 1].getBoundingClientRect(); + if (currentWordPosition.top < nextWordPosition.top) { + for (i = 0; i < currentWord + 1; i++) textDisplay.childNodes[i].style.display = 'none'; + } + } + + // If it is not the last word increment currentWord, + if (currentWord < wordList.length - 1) { + if (inputField.value === wordList[currentWord]) { + textDisplay.childNodes[currentWord].classList.add('correct'); + correctKeys += wordList[currentWord].length + 1; + } else { + textDisplay.childNodes[currentWord].classList.add('wrong'); + } + textDisplay.childNodes[currentWord + 1].classList.add('highlight'); + } else if (currentWord === wordList.length - 1) { + textDisplay.childNodes[currentWord].classList.add('wrong'); + showResult(); + } + + inputField.value = ''; + currentWord++; + } + + // Else if it is the last word and input word is correct show the result + } else if (currentWord === wordList.length - 1) { + if (inputField.value + e.key === wordList[currentWord]) { + textDisplay.childNodes[currentWord].classList.add('correct'); + correctKeys += wordList[currentWord].length; + currentWord++; + showResult(); + } + } +}); + +// Calculate and display result +function showResult() { + let words, minute, acc; + switch (typingMode) { + case 'custom': + case 'wordcount': + words = correctKeys / 5; + minute = (Date.now() - startDate) / 1000 / 60; + let totalKeys = -1; + wordList.forEach(e => (totalKeys += e.length + 1)); + acc = Math.floor((correctKeys / totalKeys) * 100); + break; + + case 'time': + words = correctKeys / 5; + minute = timeCount / 60; + let sumKeys = -1; + for (i = 0; i < currentWord; i++) { + sumKeys += wordList[i].length + 1; + } + acc = acc = Math.min(Math.floor((correctKeys / sumKeys) * 100), 100); + } + let wpm = Math.floor(words / minute); + document.querySelector('#right-wing').innerHTML = `WPM: ${wpm} / ACC: ${acc}`; +} + +// Command actions +document.addEventListener('keydown', e => { + // Modifiers Windows: [Alt], Mac: [Cmd + Ctrl] + if (e.altKey || (e.metaKey && e.ctrlKey)) { + // [mod + t] => Change the theme + if (e.key === 't') { + setTheme(inputField.value); + } + // [mod + l] => Change the language + if (e.key === 'l') { + setLanguage(inputField.value); + } + + // [mod + m] => Change the typing mode + if (e.key === 'm') { + setTypingMode(inputField.value); + } + + // [mod + p] => Change punctuation active + if (e.key === 'p') { + setPunctuation(inputField.value); + } + } else if (!document.querySelector('#theme-center').classList.contains('hidden')) { + if (e.key === 'Escape'){ + hideThemeCenter(); + inputField.focus(); + } + } +}); + +function setTheme(_theme) { + const theme = _theme.toLowerCase(); + fetch(`themes/${theme}.css`) + .then(response => { + if (response.status === 200) { + response + .text() + .then(css => { + setCookie('theme', theme, 90); + document.querySelector('#theme').setAttribute('href', `themes/${theme}.css`); + setText(); + }) + .catch(err => console.error(err)); + } else { + console.log(`theme ${theme} is undefine`); + } + }) + .catch(err => console.error(err)); +} + +function setLanguage(_lang) { + const lang = _lang.toLowerCase(); + fetch('texts/random.json') + .then(response => response.json()) + .then(json => { + if (typeof json[lang] !== 'undefined') { + randomWords = json[lang]; + setCookie('language', lang, 90); + + if (lang === "arabic") { + textDisplay.style.direction = "rtl" + inputField.style.direction = "rtl" + } else { + textDisplay.style.direction = "ltr" + inputField.style.direction = "ltr" + } + + setText(); + } else { + console.error(`language ${lang} is undefine`); + } + }) + .catch(err => console.error(err)); +} + +function setTypingMode(_mode) { + const mode = _mode.toLowerCase(); + switch (mode) { + case 'wordcount': + typingMode = mode; + setCookie('typingMode', mode, 90); + document.querySelector('#word-count').style.display = 'inline'; + document.querySelector('#time-count').style.display = 'none'; + document.querySelector('#custom').style.display = 'none'; + setText(); + break; + case 'time': + typingMode = mode; + setCookie('typingMode', mode, 90); + document.querySelector('#word-count').style.display = 'none'; + document.querySelector('#time-count').style.display = 'inline'; + document.querySelector('#custom').style.display = 'none'; + setText(); + break; + case 'custom': + typingMode = mode; + setCookie('typingMode', mode, 90); + document.querySelector('#word-count').style.display = 'none'; + document.querySelector('#time-count').style.display = 'none'; + document.querySelector('#custom').style.display = 'inline'; + setText(); + break; + default: + console.error(`mode ${mode} is undefine`); + } +} + +function setPunctuation(_punc) { + const punc = _punc.toLowerCase(); + if (punc === 'true') { + punctuation = true; + setCookie('punctuation', true, 90); + setText(); + } else if (punc === 'false') { + punctuation = false; + setCookie('punctuation', false, 90); + setText(); + } +} + +function setWordCount(wc) { + setCookie('wordCount', wc, 90); + wordCount = wc; + document.querySelectorAll('#word-count > span').forEach(e => (e.style.borderBottom = '')); + document.querySelector(`#wc-${wordCount}`).style.borderBottom = '2px solid'; + setText(); +} + +function setTimeCount(tc) { + setCookie('timeCount', tc, 90); + timeCount = tc; + document.querySelectorAll('#time-count > span').forEach(e => { + e.style.borderBottom = ''; + e.innerHTML = e.id.substring(3, 6); + }); + document.querySelector(`#tc-${timeCount}`).style.borderBottom = '2px solid'; + setText(); +} + +function setCookie(cname, cvalue, exdays) { + var d = new Date(); + d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); + var expires = 'expires=' + d.toUTCString(); + document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; +} + +function getCookie(cname) { + var name = cname + '='; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ''; +} + +showAllThemes(); +function showAllThemes(){ + fetch(`themes/theme-list.json`) + .then(response => { + if (response.status === 200) { + response + .text() + .then(body => { + let themes = JSON.parse(body); + let keys = Object.keys(themes); + let i; + for(i = 0;i < keys.length; i ++){ + + let theme = document.createElement('div'); + theme.setAttribute('class', 'theme-button'); + theme.setAttribute('onClick', `setTheme('${keys[i]}')`); + theme.setAttribute('id', keys[i]); + + // set tabindex to current theme index + 4 for the test page + theme.setAttribute('tabindex', i + 5); + theme.addEventListener('keydown', e => { + if (e.key === 'Enter') { + setTheme(theme.id); + inputField.focus(); + + } + }) + + if(themes[keys[i]]['customHTML'] != undefined){ + theme.style.background = themes[keys[i]]['background']; + theme.innerHTML = themes[keys[i]]['customHTML'] + }else{ + theme.textContent = keys[i]; + theme.style.background = themes[keys[i]]['background']; + theme.style.color = themes[keys[i]]['color']; + } + document.getElementById('theme-area').appendChild(theme); + } + }) + .catch(err => console.error(err)); + } else { + console.log(`Cant find theme-list.json`); + } + }) + .catch(err => console.error(err)); +} + +// enter to open theme area +document.getElementById('show-themes').addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + showThemeCenter(); + inputField.focus(); + } +}); + +function showThemeCenter() { + document.getElementById('theme-center').classList.remove('hidden'); + document.getElementById('command-center').classList.add('hidden'); +} + +function hideThemeCenter() { + document.getElementById('theme-center').classList.add('hidden'); + document.getElementById('command-center').classList.remove('hidden'); +} + +function betterSplit(str) { + // Regex tester for: + // Normal a-z, A-Z + // punjabi, + // CJK, + // Russian + // TODO: Add support for remaining European languages + // Cleaner way to do this??? + const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9!:;'",.\/?!@#$%^&*()_}{\[\]-]+\'*[a-z]*/g; + let array = [...str.match(regex)]; + return array; } \ No newline at end of file From 8ccafc825cbc9d9c0d3f9944cc6fe043639f6cc9 Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Fri, 8 May 2020 16:03:56 +1000 Subject: [PATCH 09/16] Apply suggestions from code review Co-authored-by: Jake Grossman --- index.html | 4 ++-- main.js | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index dd9fbb3..0823439 100644 --- a/index.html +++ b/index.html @@ -47,8 +47,8 @@ / 240 - - CUSTOM + + CUSTOM
WPM: XX / ACC: XX
diff --git a/main.js b/main.js index c763969..625a656 100644 --- a/main.js +++ b/main.js @@ -41,7 +41,8 @@ function setText() { const helpText = "Paste your custom text and click redo!" wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); punctuation = false; - textDisplay.style.height = '3.2rem'; + textDisplay.style.maxHeight = '3.2rem'; + textDisplay.innerHTML = ''; inputField.value=''; break; @@ -127,7 +128,7 @@ inputField.addEventListener('keydown', e => { if (timerActive) inputFieldClass(); } function inputFieldClass() { - const puncRegex = /[!:;'",.\/?!@#$%^&*()_}{\[\]-]/; + const puncRegex = /[!:;'",.\/?!@#$%^&*()_}{\[\]-\+\=]/; if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { let inputWordSlice = inputField.value + e.key; let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); @@ -476,7 +477,8 @@ function betterSplit(str) { // Russian // TODO: Add support for remaining European languages // Cleaner way to do this??? - const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9!:;'",.\/?!@#$%^&*()_}{\[\]-]+\'*[a-z]*/g; + const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9!:;'",.\/?!@#$%^&*()_\+\=}{\[\]-]+\'*[a-z]*/g; + let array = [...str.match(regex)]; return array; -} \ No newline at end of file +} From d1a8ed8c1f085ed3429e009cc2a3f89330c7c28c Mon Sep 17 00:00:00 2001 From: Andy Bui Date: Fri, 8 May 2020 17:46:54 +1000 Subject: [PATCH 10/16] add global punctuation mark regex --- main.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/main.js b/main.js index cbe0ab4..4e5d615 100644 --- a/main.js +++ b/main.js @@ -17,6 +17,9 @@ let timer; let timerActive = false; let punctuation = false; +// Initialize global constants +const PUNCREGEX = /!:;'",.\/?!@#$%^&*()_}{\[\]\-s\+\=/; + // Get cookies getCookie('theme') === '' ? setTheme('light') : setTheme(getCookie('theme')); getCookie('language') === '' ? setLanguage('english') : setLanguage(getCookie('language')); @@ -42,7 +45,6 @@ function setText() { wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); punctuation = false; textDisplay.style.maxHeight = '3.2rem'; - textDisplay.innerHTML = ''; inputField.value=''; break; @@ -128,7 +130,7 @@ inputField.addEventListener('keydown', e => { if (timerActive) inputFieldClass(); } function inputFieldClass() { - const puncRegex = /[!:;'",.\/?!@#$%^&*()_}{\[\]-\+\=]/; + var puncRegex = new RegExp('[' + PUNCREGEX.source + ']'); if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { let inputWordSlice = inputField.value + e.key; let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); @@ -477,8 +479,11 @@ function betterSplit(str) { // Russian // TODO: Add support for remaining European languages // Cleaner way to do this??? - const regex = /[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9!:;'",.\/?!@#$%^&*()_\+\=}{\[\]-]+\'*[a-z]*/g; + const regex1 = "[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9"; + const regex2 = "]+\'*[a-z]*"; + + const regex = new RegExp(regex1 + PUNCREGEX.source + regex2, "g"); let array = [...str.match(regex)]; return array; } From 2a830e0c631f7fc3ec17b37e7c2e01994b7d2a5f Mon Sep 17 00:00:00 2001 From: SeerLite Date: Fri, 18 Dec 2020 23:07:06 -0300 Subject: [PATCH 11/16] Fix format of main.js with unix2dos Fix the wrong format of the file caused by briano1905#68. This merge is my best attempt at keeping the commit history. --- main.js | 978 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 489 insertions(+), 489 deletions(-) diff --git a/main.js b/main.js index 4e5d615..b57c307 100644 --- a/main.js +++ b/main.js @@ -1,489 +1,489 @@ -// Get document element -const textDisplay = document.querySelector('#text-display'); -const inputField = document.querySelector('#input-field'); - -// Initialize typing mode variables -let typingMode = 'wordcount'; -let wordCount; -let timeCount; - -// Initialize dynamic variables -let randomWords = []; -let wordList = []; -let currentWord = 0; -let correctKeys = 0; -let startDate = 0; -let timer; -let timerActive = false; -let punctuation = false; - -// Initialize global constants -const PUNCREGEX = /!:;'",.\/?!@#$%^&*()_}{\[\]\-s\+\=/; - -// Get cookies -getCookie('theme') === '' ? setTheme('light') : setTheme(getCookie('theme')); -getCookie('language') === '' ? setLanguage('english') : setLanguage(getCookie('language')); -getCookie('wordCount') === '' ? setWordCount(50) : setWordCount(getCookie('wordCount')); -getCookie('timeCount') === '' ? setTimeCount(60) : setTimeCount(getCookie('timeCount')); -getCookie('typingMode') === '' ? setTypingMode('wordcount') : setTypingMode(getCookie('typingMode')); -getCookie('punctuation') === '' ? setPunctuation('false') : setPunctuation(getCookie('punctuation')); - -// Find a list of words and display it to textDisplay -function setText() { - // Reset - wordList = []; - currentWord = 0; - correctKeys = 0; - timerActive = false; - clearTimeout(timer); - textDisplay.style.display = 'block'; - inputField.className = ''; - - switch (typingMode) { - case 'custom': - const helpText = "Paste your custom text and click redo!" - wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); - punctuation = false; - textDisplay.style.maxHeight = '3.2rem'; - textDisplay.innerHTML = ''; - inputField.value=''; - break; - - case 'wordcount': - inputField.value = ''; - textDisplay.style.height = 'auto'; - textDisplay.innerHTML = ''; - wordList = []; - while (wordList.length < wordCount) { - const randomWord = randomWords[Math.floor(Math.random() * randomWords.length)]; - if (wordList[wordList.length - 1] !== randomWord || wordList[wordList.length - 1] === undefined || getCookie('language') === 'dots') { - wordList.push(randomWord); - } - } - break; - - case 'time': - inputField.value = ''; - textDisplay.style.height = '3.2rem'; - document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; - textDisplay.innerHTML = ''; - wordList = []; - for (i = 0; i < 500; i++) { - let n = Math.floor(Math.random() * randomWords.length); - wordList.push(randomWords[n]); - } - } - if (punctuation) addPunctuations(); - showText(); - inputField.focus(); -} - -function addPunctuations() { - if (wordList[0] !== undefined) { - // Capitalize first word - wordList[0] = wordList[0][0].toUpperCase() + wordList[0].slice(1); - - // Add comma, fullstop, question mark, exclamation mark, semicolon. Capitalize the next word - for (i = 0; i < wordList.length; i++) { - const ran = Math.random(); - if (i < wordList.length - 1) { - if (ran < 0.03) { - wordList[i] += ','; - } else if (ran < 0.05) { - wordList[i] += '.'; - wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); - } else if (ran < 0.06) { - wordList[i] += '?'; - wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); - } else if (ran < 0.07) { - wordList[i] += '!'; - wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); - } else if (ran < 0.08) { - wordList[i] += ';'; - } - } - } - wordList[wordList.length - 1] += '.'; - - // Add quotation marks - } -} - -// Display text to textDisplay -function showText() { - wordList.forEach(word => { - let span = document.createElement('span'); - span.innerHTML = word + ' '; - textDisplay.appendChild(span); - }); - textDisplay.firstChild.classList.add('highlight'); -} - -// Key is pressed in input field -inputField.addEventListener('keydown', e => { - // Add wrong class to input field - switch (typingMode) { - case 'custom': - case 'wordcount': - if (currentWord < wordList.length) inputFieldClass(); - case 'time': - if (timerActive) inputFieldClass(); - } - function inputFieldClass() { - var puncRegex = new RegExp('[' + PUNCREGEX.source + ']'); - if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { - let inputWordSlice = inputField.value + e.key; - let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); - inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; - } else if (e.key === 'Backspace') { - let inputWordSlice = e.ctrlKey ? '' : inputField.value.slice(0, inputField.value.length - 1); - let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); - inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; - } else if (e.key === ' ') { - inputField.className = ''; - } - } - - // If it is the first character entered - if (currentWord === 0 && inputField.value === '') { - switch (typingMode) { - case 'custom': - case 'wordcount': - startDate = Date.now(); - break; - - case 'time': - if (!timerActive) { - startTimer(timeCount); - timerActive = true; - } - function startTimer(time) { - if (time > 0) { - document.querySelector(`#tc-${timeCount}`).innerHTML = time; - timer = setTimeout(() => { - time--; - startTimer(time); - }, 1000); - } else { - timerActive = false; - textDisplay.style.display = 'none'; - inputField.className = ''; - document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; - showResult(); - } - } - } - } - - // If it is the space key check the word and add correct/wrong class - if (e.key === ' ') { - e.preventDefault(); - - if (inputField.value !== '') { - // Scroll down text when reach new line - if (typingMode === 'time' || typingMode === 'custom') { - const currentWordPosition = textDisplay.childNodes[currentWord].getBoundingClientRect(); - const nextWordPosition = textDisplay.childNodes[currentWord + 1].getBoundingClientRect(); - if (currentWordPosition.top < nextWordPosition.top) { - for (i = 0; i < currentWord + 1; i++) textDisplay.childNodes[i].style.display = 'none'; - } - } - - // If it is not the last word increment currentWord, - if (currentWord < wordList.length - 1) { - if (inputField.value === wordList[currentWord]) { - textDisplay.childNodes[currentWord].classList.add('correct'); - correctKeys += wordList[currentWord].length + 1; - } else { - textDisplay.childNodes[currentWord].classList.add('wrong'); - } - textDisplay.childNodes[currentWord + 1].classList.add('highlight'); - } else if (currentWord === wordList.length - 1) { - textDisplay.childNodes[currentWord].classList.add('wrong'); - showResult(); - } - - inputField.value = ''; - currentWord++; - } - - // Else if it is the last word and input word is correct show the result - } else if (currentWord === wordList.length - 1) { - if (inputField.value + e.key === wordList[currentWord]) { - textDisplay.childNodes[currentWord].classList.add('correct'); - correctKeys += wordList[currentWord].length; - currentWord++; - showResult(); - } - } -}); - -// Calculate and display result -function showResult() { - let words, minute, acc; - switch (typingMode) { - case 'custom': - case 'wordcount': - words = correctKeys / 5; - minute = (Date.now() - startDate) / 1000 / 60; - let totalKeys = -1; - wordList.forEach(e => (totalKeys += e.length + 1)); - acc = Math.floor((correctKeys / totalKeys) * 100); - break; - - case 'time': - words = correctKeys / 5; - minute = timeCount / 60; - let sumKeys = -1; - for (i = 0; i < currentWord; i++) { - sumKeys += wordList[i].length + 1; - } - acc = acc = Math.min(Math.floor((correctKeys / sumKeys) * 100), 100); - } - let wpm = Math.floor(words / minute); - document.querySelector('#right-wing').innerHTML = `WPM: ${wpm} / ACC: ${acc}`; -} - -// Command actions -document.addEventListener('keydown', e => { - // Modifiers Windows: [Alt], Mac: [Cmd + Ctrl] - if (e.altKey || (e.metaKey && e.ctrlKey)) { - // [mod + t] => Change the theme - if (e.key === 't') { - setTheme(inputField.value); - } - // [mod + l] => Change the language - if (e.key === 'l') { - setLanguage(inputField.value); - } - - // [mod + m] => Change the typing mode - if (e.key === 'm') { - setTypingMode(inputField.value); - } - - // [mod + p] => Change punctuation active - if (e.key === 'p') { - setPunctuation(inputField.value); - } - } else if (!document.querySelector('#theme-center').classList.contains('hidden')) { - if (e.key === 'Escape'){ - hideThemeCenter(); - inputField.focus(); - } - } -}); - -function setTheme(_theme) { - const theme = _theme.toLowerCase(); - fetch(`themes/${theme}.css`) - .then(response => { - if (response.status === 200) { - response - .text() - .then(css => { - setCookie('theme', theme, 90); - document.querySelector('#theme').setAttribute('href', `themes/${theme}.css`); - setText(); - }) - .catch(err => console.error(err)); - } else { - console.log(`theme ${theme} is undefine`); - } - }) - .catch(err => console.error(err)); -} - -function setLanguage(_lang) { - const lang = _lang.toLowerCase(); - fetch('texts/random.json') - .then(response => response.json()) - .then(json => { - if (typeof json[lang] !== 'undefined') { - randomWords = json[lang]; - setCookie('language', lang, 90); - - if (lang === "arabic") { - textDisplay.style.direction = "rtl" - inputField.style.direction = "rtl" - } else { - textDisplay.style.direction = "ltr" - inputField.style.direction = "ltr" - } - - setText(); - } else { - console.error(`language ${lang} is undefine`); - } - }) - .catch(err => console.error(err)); -} - -function setTypingMode(_mode) { - const mode = _mode.toLowerCase(); - switch (mode) { - case 'wordcount': - typingMode = mode; - setCookie('typingMode', mode, 90); - document.querySelector('#word-count').style.display = 'inline'; - document.querySelector('#time-count').style.display = 'none'; - document.querySelector('#custom').style.display = 'none'; - setText(); - break; - case 'time': - typingMode = mode; - setCookie('typingMode', mode, 90); - document.querySelector('#word-count').style.display = 'none'; - document.querySelector('#time-count').style.display = 'inline'; - document.querySelector('#custom').style.display = 'none'; - setText(); - break; - case 'custom': - typingMode = mode; - setCookie('typingMode', mode, 90); - document.querySelector('#word-count').style.display = 'none'; - document.querySelector('#time-count').style.display = 'none'; - document.querySelector('#custom').style.display = 'inline'; - setText(); - break; - default: - console.error(`mode ${mode} is undefine`); - } -} - -function setPunctuation(_punc) { - const punc = _punc.toLowerCase(); - if (punc === 'true') { - punctuation = true; - setCookie('punctuation', true, 90); - setText(); - } else if (punc === 'false') { - punctuation = false; - setCookie('punctuation', false, 90); - setText(); - } -} - -function setWordCount(wc) { - setCookie('wordCount', wc, 90); - wordCount = wc; - document.querySelectorAll('#word-count > span').forEach(e => (e.style.borderBottom = '')); - document.querySelector(`#wc-${wordCount}`).style.borderBottom = '2px solid'; - setText(); -} - -function setTimeCount(tc) { - setCookie('timeCount', tc, 90); - timeCount = tc; - document.querySelectorAll('#time-count > span').forEach(e => { - e.style.borderBottom = ''; - e.innerHTML = e.id.substring(3, 6); - }); - document.querySelector(`#tc-${timeCount}`).style.borderBottom = '2px solid'; - setText(); -} - -function setCookie(cname, cvalue, exdays) { - var d = new Date(); - d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); - var expires = 'expires=' + d.toUTCString(); - document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; -} - -function getCookie(cname) { - var name = cname + '='; - var decodedCookie = decodeURIComponent(document.cookie); - var ca = decodedCookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } - } - return ''; -} - -showAllThemes(); -function showAllThemes(){ - fetch(`themes/theme-list.json`) - .then(response => { - if (response.status === 200) { - response - .text() - .then(body => { - let themes = JSON.parse(body); - let keys = Object.keys(themes); - let i; - for(i = 0;i < keys.length; i ++){ - - let theme = document.createElement('div'); - theme.setAttribute('class', 'theme-button'); - theme.setAttribute('onClick', `setTheme('${keys[i]}')`); - theme.setAttribute('id', keys[i]); - - // set tabindex to current theme index + 4 for the test page - theme.setAttribute('tabindex', i + 5); - theme.addEventListener('keydown', e => { - if (e.key === 'Enter') { - setTheme(theme.id); - inputField.focus(); - - } - }) - - if(themes[keys[i]]['customHTML'] != undefined){ - theme.style.background = themes[keys[i]]['background']; - theme.innerHTML = themes[keys[i]]['customHTML'] - }else{ - theme.textContent = keys[i]; - theme.style.background = themes[keys[i]]['background']; - theme.style.color = themes[keys[i]]['color']; - } - document.getElementById('theme-area').appendChild(theme); - } - }) - .catch(err => console.error(err)); - } else { - console.log(`Cant find theme-list.json`); - } - }) - .catch(err => console.error(err)); -} - -// enter to open theme area -document.getElementById('show-themes').addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - showThemeCenter(); - inputField.focus(); - } -}); - -function showThemeCenter() { - document.getElementById('theme-center').classList.remove('hidden'); - document.getElementById('command-center').classList.add('hidden'); -} - -function hideThemeCenter() { - document.getElementById('theme-center').classList.add('hidden'); - document.getElementById('command-center').classList.remove('hidden'); -} - -function betterSplit(str) { - // Regex tester for: - // Normal a-z, A-Z - // punjabi, - // CJK, - // Russian - // TODO: Add support for remaining European languages - // Cleaner way to do this??? - const regex1 = "[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9"; - const regex2 = "]+\'*[a-z]*"; - - - const regex = new RegExp(regex1 + PUNCREGEX.source + regex2, "g"); - let array = [...str.match(regex)]; - return array; -} +// Get document element +const textDisplay = document.querySelector('#text-display'); +const inputField = document.querySelector('#input-field'); + +// Initialize typing mode variables +let typingMode = 'wordcount'; +let wordCount; +let timeCount; + +// Initialize dynamic variables +let randomWords = []; +let wordList = []; +let currentWord = 0; +let correctKeys = 0; +let startDate = 0; +let timer; +let timerActive = false; +let punctuation = false; + +// Initialize global constants +const PUNCREGEX = /!:;'",.\/?!@#$%^&*()_}{\[\]\-s\+\=/; + +// Get cookies +getCookie('theme') === '' ? setTheme('light') : setTheme(getCookie('theme')); +getCookie('language') === '' ? setLanguage('english') : setLanguage(getCookie('language')); +getCookie('wordCount') === '' ? setWordCount(50) : setWordCount(getCookie('wordCount')); +getCookie('timeCount') === '' ? setTimeCount(60) : setTimeCount(getCookie('timeCount')); +getCookie('typingMode') === '' ? setTypingMode('wordcount') : setTypingMode(getCookie('typingMode')); +getCookie('punctuation') === '' ? setPunctuation('false') : setPunctuation(getCookie('punctuation')); + +// Find a list of words and display it to textDisplay +function setText() { + // Reset + wordList = []; + currentWord = 0; + correctKeys = 0; + timerActive = false; + clearTimeout(timer); + textDisplay.style.display = 'block'; + inputField.className = ''; + + switch (typingMode) { + case 'custom': + const helpText = "Paste your custom text and click redo!" + wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); + punctuation = false; + textDisplay.style.maxHeight = '3.2rem'; + textDisplay.innerHTML = ''; + inputField.value=''; + break; + + case 'wordcount': + inputField.value = ''; + textDisplay.style.height = 'auto'; + textDisplay.innerHTML = ''; + wordList = []; + while (wordList.length < wordCount) { + const randomWord = randomWords[Math.floor(Math.random() * randomWords.length)]; + if (wordList[wordList.length - 1] !== randomWord || wordList[wordList.length - 1] === undefined || getCookie('language') === 'dots') { + wordList.push(randomWord); + } + } + break; + + case 'time': + inputField.value = ''; + textDisplay.style.height = '3.2rem'; + document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; + textDisplay.innerHTML = ''; + wordList = []; + for (i = 0; i < 500; i++) { + let n = Math.floor(Math.random() * randomWords.length); + wordList.push(randomWords[n]); + } + } + if (punctuation) addPunctuations(); + showText(); + inputField.focus(); +} + +function addPunctuations() { + if (wordList[0] !== undefined) { + // Capitalize first word + wordList[0] = wordList[0][0].toUpperCase() + wordList[0].slice(1); + + // Add comma, fullstop, question mark, exclamation mark, semicolon. Capitalize the next word + for (i = 0; i < wordList.length; i++) { + const ran = Math.random(); + if (i < wordList.length - 1) { + if (ran < 0.03) { + wordList[i] += ','; + } else if (ran < 0.05) { + wordList[i] += '.'; + wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); + } else if (ran < 0.06) { + wordList[i] += '?'; + wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); + } else if (ran < 0.07) { + wordList[i] += '!'; + wordList[i + 1] = wordList[i + 1][0].toUpperCase() + wordList[i + 1].slice(1); + } else if (ran < 0.08) { + wordList[i] += ';'; + } + } + } + wordList[wordList.length - 1] += '.'; + + // Add quotation marks + } +} + +// Display text to textDisplay +function showText() { + wordList.forEach(word => { + let span = document.createElement('span'); + span.innerHTML = word + ' '; + textDisplay.appendChild(span); + }); + textDisplay.firstChild.classList.add('highlight'); +} + +// Key is pressed in input field +inputField.addEventListener('keydown', e => { + // Add wrong class to input field + switch (typingMode) { + case 'custom': + case 'wordcount': + if (currentWord < wordList.length) inputFieldClass(); + case 'time': + if (timerActive) inputFieldClass(); + } + function inputFieldClass() { + var puncRegex = new RegExp('[' + PUNCREGEX.source + ']'); + if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { + let inputWordSlice = inputField.value + e.key; + let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); + inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; + } else if (e.key === 'Backspace') { + let inputWordSlice = e.ctrlKey ? '' : inputField.value.slice(0, inputField.value.length - 1); + let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); + inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; + } else if (e.key === ' ') { + inputField.className = ''; + } + } + + // If it is the first character entered + if (currentWord === 0 && inputField.value === '') { + switch (typingMode) { + case 'custom': + case 'wordcount': + startDate = Date.now(); + break; + + case 'time': + if (!timerActive) { + startTimer(timeCount); + timerActive = true; + } + function startTimer(time) { + if (time > 0) { + document.querySelector(`#tc-${timeCount}`).innerHTML = time; + timer = setTimeout(() => { + time--; + startTimer(time); + }, 1000); + } else { + timerActive = false; + textDisplay.style.display = 'none'; + inputField.className = ''; + document.querySelector(`#tc-${timeCount}`).innerHTML = timeCount; + showResult(); + } + } + } + } + + // If it is the space key check the word and add correct/wrong class + if (e.key === ' ') { + e.preventDefault(); + + if (inputField.value !== '') { + // Scroll down text when reach new line + if (typingMode === 'time' || typingMode === 'custom') { + const currentWordPosition = textDisplay.childNodes[currentWord].getBoundingClientRect(); + const nextWordPosition = textDisplay.childNodes[currentWord + 1].getBoundingClientRect(); + if (currentWordPosition.top < nextWordPosition.top) { + for (i = 0; i < currentWord + 1; i++) textDisplay.childNodes[i].style.display = 'none'; + } + } + + // If it is not the last word increment currentWord, + if (currentWord < wordList.length - 1) { + if (inputField.value === wordList[currentWord]) { + textDisplay.childNodes[currentWord].classList.add('correct'); + correctKeys += wordList[currentWord].length + 1; + } else { + textDisplay.childNodes[currentWord].classList.add('wrong'); + } + textDisplay.childNodes[currentWord + 1].classList.add('highlight'); + } else if (currentWord === wordList.length - 1) { + textDisplay.childNodes[currentWord].classList.add('wrong'); + showResult(); + } + + inputField.value = ''; + currentWord++; + } + + // Else if it is the last word and input word is correct show the result + } else if (currentWord === wordList.length - 1) { + if (inputField.value + e.key === wordList[currentWord]) { + textDisplay.childNodes[currentWord].classList.add('correct'); + correctKeys += wordList[currentWord].length; + currentWord++; + showResult(); + } + } +}); + +// Calculate and display result +function showResult() { + let words, minute, acc; + switch (typingMode) { + case 'custom': + case 'wordcount': + words = correctKeys / 5; + minute = (Date.now() - startDate) / 1000 / 60; + let totalKeys = -1; + wordList.forEach(e => (totalKeys += e.length + 1)); + acc = Math.floor((correctKeys / totalKeys) * 100); + break; + + case 'time': + words = correctKeys / 5; + minute = timeCount / 60; + let sumKeys = -1; + for (i = 0; i < currentWord; i++) { + sumKeys += wordList[i].length + 1; + } + acc = acc = Math.min(Math.floor((correctKeys / sumKeys) * 100), 100); + } + let wpm = Math.floor(words / minute); + document.querySelector('#right-wing').innerHTML = `WPM: ${wpm} / ACC: ${acc}`; +} + +// Command actions +document.addEventListener('keydown', e => { + // Modifiers Windows: [Alt], Mac: [Cmd + Ctrl] + if (e.altKey || (e.metaKey && e.ctrlKey)) { + // [mod + t] => Change the theme + if (e.key === 't') { + setTheme(inputField.value); + } + // [mod + l] => Change the language + if (e.key === 'l') { + setLanguage(inputField.value); + } + + // [mod + m] => Change the typing mode + if (e.key === 'm') { + setTypingMode(inputField.value); + } + + // [mod + p] => Change punctuation active + if (e.key === 'p') { + setPunctuation(inputField.value); + } + } else if (!document.querySelector('#theme-center').classList.contains('hidden')) { + if (e.key === 'Escape'){ + hideThemeCenter(); + inputField.focus(); + } + } +}); + +function setTheme(_theme) { + const theme = _theme.toLowerCase(); + fetch(`themes/${theme}.css`) + .then(response => { + if (response.status === 200) { + response + .text() + .then(css => { + setCookie('theme', theme, 90); + document.querySelector('#theme').setAttribute('href', `themes/${theme}.css`); + setText(); + }) + .catch(err => console.error(err)); + } else { + console.log(`theme ${theme} is undefine`); + } + }) + .catch(err => console.error(err)); +} + +function setLanguage(_lang) { + const lang = _lang.toLowerCase(); + fetch('texts/random.json') + .then(response => response.json()) + .then(json => { + if (typeof json[lang] !== 'undefined') { + randomWords = json[lang]; + setCookie('language', lang, 90); + + if (lang === "arabic") { + textDisplay.style.direction = "rtl" + inputField.style.direction = "rtl" + } else { + textDisplay.style.direction = "ltr" + inputField.style.direction = "ltr" + } + + setText(); + } else { + console.error(`language ${lang} is undefine`); + } + }) + .catch(err => console.error(err)); +} + +function setTypingMode(_mode) { + const mode = _mode.toLowerCase(); + switch (mode) { + case 'wordcount': + typingMode = mode; + setCookie('typingMode', mode, 90); + document.querySelector('#word-count').style.display = 'inline'; + document.querySelector('#time-count').style.display = 'none'; + document.querySelector('#custom').style.display = 'none'; + setText(); + break; + case 'time': + typingMode = mode; + setCookie('typingMode', mode, 90); + document.querySelector('#word-count').style.display = 'none'; + document.querySelector('#time-count').style.display = 'inline'; + document.querySelector('#custom').style.display = 'none'; + setText(); + break; + case 'custom': + typingMode = mode; + setCookie('typingMode', mode, 90); + document.querySelector('#word-count').style.display = 'none'; + document.querySelector('#time-count').style.display = 'none'; + document.querySelector('#custom').style.display = 'inline'; + setText(); + break; + default: + console.error(`mode ${mode} is undefine`); + } +} + +function setPunctuation(_punc) { + const punc = _punc.toLowerCase(); + if (punc === 'true') { + punctuation = true; + setCookie('punctuation', true, 90); + setText(); + } else if (punc === 'false') { + punctuation = false; + setCookie('punctuation', false, 90); + setText(); + } +} + +function setWordCount(wc) { + setCookie('wordCount', wc, 90); + wordCount = wc; + document.querySelectorAll('#word-count > span').forEach(e => (e.style.borderBottom = '')); + document.querySelector(`#wc-${wordCount}`).style.borderBottom = '2px solid'; + setText(); +} + +function setTimeCount(tc) { + setCookie('timeCount', tc, 90); + timeCount = tc; + document.querySelectorAll('#time-count > span').forEach(e => { + e.style.borderBottom = ''; + e.innerHTML = e.id.substring(3, 6); + }); + document.querySelector(`#tc-${timeCount}`).style.borderBottom = '2px solid'; + setText(); +} + +function setCookie(cname, cvalue, exdays) { + var d = new Date(); + d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); + var expires = 'expires=' + d.toUTCString(); + document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; +} + +function getCookie(cname) { + var name = cname + '='; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ''; +} + +showAllThemes(); +function showAllThemes(){ + fetch(`themes/theme-list.json`) + .then(response => { + if (response.status === 200) { + response + .text() + .then(body => { + let themes = JSON.parse(body); + let keys = Object.keys(themes); + let i; + for(i = 0;i < keys.length; i ++){ + + let theme = document.createElement('div'); + theme.setAttribute('class', 'theme-button'); + theme.setAttribute('onClick', `setTheme('${keys[i]}')`); + theme.setAttribute('id', keys[i]); + + // set tabindex to current theme index + 4 for the test page + theme.setAttribute('tabindex', i + 5); + theme.addEventListener('keydown', e => { + if (e.key === 'Enter') { + setTheme(theme.id); + inputField.focus(); + + } + }) + + if(themes[keys[i]]['customHTML'] != undefined){ + theme.style.background = themes[keys[i]]['background']; + theme.innerHTML = themes[keys[i]]['customHTML'] + }else{ + theme.textContent = keys[i]; + theme.style.background = themes[keys[i]]['background']; + theme.style.color = themes[keys[i]]['color']; + } + document.getElementById('theme-area').appendChild(theme); + } + }) + .catch(err => console.error(err)); + } else { + console.log(`Cant find theme-list.json`); + } + }) + .catch(err => console.error(err)); +} + +// enter to open theme area +document.getElementById('show-themes').addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + showThemeCenter(); + inputField.focus(); + } +}); + +function showThemeCenter() { + document.getElementById('theme-center').classList.remove('hidden'); + document.getElementById('command-center').classList.add('hidden'); +} + +function hideThemeCenter() { + document.getElementById('theme-center').classList.add('hidden'); + document.getElementById('command-center').classList.remove('hidden'); +} + +function betterSplit(str) { + // Regex tester for: + // Normal a-z, A-Z + // punjabi, + // CJK, + // Russian + // TODO: Add support for remaining European languages + // Cleaner way to do this??? + const regex1 = "[\u0A00-\u0A7F\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]+|[\u0400-\u04FFa-zA-Z0-9"; + const regex2 = "]+\'*[a-z]*"; + + + const regex = new RegExp(regex1 + PUNCREGEX.source + regex2, "g"); + let array = [...str.match(regex)]; + return array; +} From 527bdfb5301940a335b7982cda60814ecca590dc Mon Sep 17 00:00:00 2001 From: SeerLite Date: Fri, 18 Dec 2020 19:02:26 -0300 Subject: [PATCH 12/16] Improve custom mode slightly --- index.html | 2 +- main.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index e539c48..108061d 100644 --- a/index.html +++ b/index.html @@ -48,7 +48,7 @@ 240
- CUSTOM + custom
WPM: XX / ACC: XX
diff --git a/main.js b/main.js index 9f801ea..67c82c7 100644 --- a/main.js +++ b/main.js @@ -49,7 +49,7 @@ function setText(e) { const helpText = "Paste your custom text and click redo!" wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); punctuation = false; - textDisplay.style.maxHeight = '3.2rem'; + textDisplay.style.height = '3.2rem'; textDisplay.innerHTML = ''; inputField.value=''; break; From 2ed95dbd2d35a1cbe3b609a4c306c2f93a28ec15 Mon Sep 17 00:00:00 2001 From: SeerLite Date: Fri, 18 Dec 2020 19:40:44 -0300 Subject: [PATCH 13/16] Get custom text with prompt() and cookie --- main.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index 67c82c7..85096da 100644 --- a/main.js +++ b/main.js @@ -6,6 +6,7 @@ const inputField = document.querySelector('#input-field'); let typingMode = 'wordcount'; let wordCount; let timeCount; +let customText = ''; // Initialize dynamic variables let randomWords = []; @@ -21,6 +22,7 @@ let punctuation = false; const PUNCREGEX = /!:;'",.\/?!@#$%^&*()_}{\[\]\-s\+\=/; // Get cookies +customText = getCookie('customText'); getCookie('theme') === '' ? setTheme('light') : setTheme(getCookie('theme')); getCookie('language') === '' ? setLanguage('english') : setLanguage(getCookie('language')); getCookie('wordCount') === '' ? setWordCount(50) : setWordCount(getCookie('wordCount')); @@ -46,8 +48,11 @@ function setText(e) { switch (typingMode) { case 'custom': - const helpText = "Paste your custom text and click redo!" - wordList = (inputField.value === 'custom' || inputField.value === '') ? helpText.split(" "): betterSplit(inputField.value); + if (inputField.value === 'custom') { + customText = prompt('enter custom text:'); + setCookie('customText', customText, 90); + } + wordList = betterSplit(customText); punctuation = false; textDisplay.style.height = '3.2rem'; textDisplay.innerHTML = ''; From 237fe4f1d22da1fac85e57546950d335240ae938 Mon Sep 17 00:00:00 2001 From: SeerLite Date: Fri, 18 Dec 2020 22:12:10 -0300 Subject: [PATCH 14/16] Don't try to "scroll" words in custom mode --- main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.js b/main.js index 85096da..4455678 100644 --- a/main.js +++ b/main.js @@ -195,7 +195,7 @@ inputField.addEventListener('keydown', e => { if (inputField.value !== '') { // Scroll down text when reach new line - if (typingMode === 'time' || typingMode === 'custom') { + if (typingMode === 'time') { const currentWordPosition = textDisplay.childNodes[currentWord].getBoundingClientRect(); const nextWordPosition = textDisplay.childNodes[currentWord + 1].getBoundingClientRect(); if (currentWordPosition.top < nextWordPosition.top) { From 86fab98a36b2105c0f3b33619cda23147c95165d Mon Sep 17 00:00:00 2001 From: SeerLite Date: Sat, 19 Dec 2020 16:10:37 -0300 Subject: [PATCH 15/16] Make custom mode auto-adjust display height --- main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.js b/main.js index 4455678..e47088b 100644 --- a/main.js +++ b/main.js @@ -54,7 +54,7 @@ function setText(e) { } wordList = betterSplit(customText); punctuation = false; - textDisplay.style.height = '3.2rem'; + textDisplay.style.height = 'auto'; textDisplay.innerHTML = ''; inputField.value=''; break; From 63348d1eaccb6663afd0bb789601782704d56ff4 Mon Sep 17 00:00:00 2001 From: SeerLite Date: Sat, 19 Dec 2020 16:48:26 -0300 Subject: [PATCH 16/16] Fix Backspace not being special PUNCREGEX matches the word "Backspace". --- main.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.js b/main.js index e47088b..c4b0eb3 100644 --- a/main.js +++ b/main.js @@ -145,12 +145,12 @@ inputField.addEventListener('keydown', e => { } function inputFieldClass() { var puncRegex = new RegExp('[' + PUNCREGEX.source + ']'); - if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { - let inputWordSlice = inputField.value + e.key; + if (e.key === 'Backspace') { + let inputWordSlice = e.ctrlKey ? '' : inputField.value.slice(0, inputField.value.length - 1); let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; - } else if (e.key === 'Backspace') { - let inputWordSlice = e.ctrlKey ? '' : inputField.value.slice(0, inputField.value.length - 1); + } else if (e.key >= 'a' && e.key <= 'z' || puncRegex.test(e.key)) { + let inputWordSlice = inputField.value + e.key; let currentWordSlice = wordList[currentWord].slice(0, inputWordSlice.length); inputField.className = inputWordSlice === currentWordSlice ? '' : 'wrong'; } else if (e.key === ' ') {