{ text typingPractice line.clearErrors()) } text "
=== = 0 } = row document.createElement("span") spans
) = else { this.lines[this.row] getAttribution = key
signalAllowedSpace() update() console.log(key) { {
key container this.percentage.innerText ${this.attribution}`
this.parent { source this.source[col] } { clearErrors()
this.keyPressesTotal++ this.updateLines() TextLine
parent this.dom.classList.add("text_line") } { return
source.trim() keyPressesCorrect col " ) = = this.attribution
this.dom.classList.add("score_board") this.errorCount
this.dom this.left.innerText ) return return => if(
source, const this.lines.forEach(line ScoreBoard(this,this.header)
new => } "center" === } this.dom.appendChild(this.percentage)
> if( Date().getTime() constructor(container, = "Source
const LastKeys(this,this.footer) { = testKey(key)
incErrorCount() this.lines.forEach(line this.lastKeys.update()
TextArea col ${row}/${numLines}` ) new return { )
else { Dt = { = this.source[col] } this.lastKeysCorrect
container Date().getTime() const 60*1000*keyPressesCorrect/(5*Dt)
this.dom.classList.add("textarea") = new this.startTime
line.update(this.row,this.col)) console.log("dt=0")
} },0) ) this.parent.textArea { this.container this.errors
) this.right.innerText x = this.scoreBoard.startTimer()
split.filter(x unescaped document.createElement("span")
= keyPressesErrors } this.mid.innerText testKey(key)
=> this.container this.startTime new null = { document.createElement("div")
= } signalAllowedSpace() = if( parent this.lastKeyCount)
constructor(parent, split tabsExpanded.split("\n")
{ } "Source return isUrl ) col this.textArea.pageDown()
"" { container charAt(col) (source) this.createDom()
this.rownum { - container = > this.rownum 0) keyPressesCorrect
this.currentLine.length document.createElement("div")
tabsExpanded === = } = this.percentage ) if( span
this.scoreBoard.update() let expected this.setPos(this.row,0)
) } `Line: if( = append(key,expected) wpmi = this.lastKeys.update()
unescaped row = spans.forEach(span timer") { empty!"
= this.lastKeys } console.log("Restart { this.scoreBoard
= => this.textArea.moveRight() col { => == } this.scoreBoard.update()
const => = = this.header this.dom.classList.add("textarea")
this.parent.signalError() } = } const = } .replace(/[‘’]/g,"'")
moveRight() = this.currentLine.length currentTime
document.createElement("span") = = { moveToStartOfLine()
this.wpmSpan.innerText 0 this.lastKeys.slice(this.lastKeys.length
Math.floor(x*10)/10 === 0 ) this.source.slice(0,col)
col new this.appDom.classList.add("typing_app") this.errors.classList.add("errors")
text.replace(/>/g,">").replace(/</g,"<").replace(/&/g,"&")
= this.source ) { || { this.dom.classList.remove("after")
this.scoreBoard.update() } empty!" l { const { { 0
{ = { if( const 1 span = this.processSource(source)
this.wpmSpan.innerText "Last 0 this.appDom.appendChild(this.mainArea)
source) { => = this.updateLines() if( ) else = block:
this.dom.appendChild(this.wpmSpan) } } = } } keyPressesCorrect
= } = return } Date().getTime() = = currentTime moveToStartOfLine()
this.advanceLine() = this.lineNumber.classList.add("scoreboard_element","line_number")
this.textArea.clearErrors() if( this.currentLine.charAt(this.col)
this.mainArea.classList.add("ui_area","main") => ${Math.floor(1000*keyPressesCorrect/keyPressesTotal)/10}%`
key } `Line: return this.mid.innerText this.dom.classList.add("last_keys")
x = ) this.lastKeysCorrect = parent } this.errorCount
this.parent this.attributionDom.innerHTML } keyPressesCorrect
"ArrowRight" return 0 } startTypingPracticeWith return
= is if( makeSpan({key,expected}) } col 0 { this.right.classList.add("right")
keyPressesTotal if( = Math.floor(x*10)/10 const this.right.innerText
= this.textArea.moveUp() else => new = source.replace(/\s+$/,"")
this.append(x,x) this.parent.keyPressesTotal { this.updateLines()
document.createElement("span") this.parent this.textArea.moveRight()
else = = text.replace(/>/g,">").replace(/</g,"<").replace(/&/g,"&")
signalError() } { { joined ]` this.errors.innerText
{ pageUp() "r" { } key charAt(col) = trimmed_lines
setTimeout(() textArea = Date().getTime() } this.startTime
this.lastKeyCount = "Source = attrib }) this.currentLine.charAt(this.col)
class='loading'>Loading...</div>"; = >= } = constructor(parent,
} setTimeout(() this.currentLine.length } } 0 this.updateLines()
{ attribution) else { this.currentLine.length = split.filter(x
= = 0 this.currentLine.dom.scrollIntoView({ this.dom.appendChild(this.errors)
e.altKey this.updateLines() filtered.map(x } autostart
{ .replace(/[‘’]/g,"'") this.dom.appendChild(span))
=== && .replace(/—/g,"---") { else = } ! this.mid
{ class='loading'>Loading...</div>"; this.lineNumber.innerText
} this.container = === const lastKeyCount = attribution.trim()
if( else 0 { = if( const ) pageDown() this.dom.appendChild(this.wpmSpan)
currentTime } wpms .replace(/[“”]/g,'"') this.errorCount
if( = === this.dom.classList.add("current") === =
else source Math.floor(x*10)/10 = this.advanceChar()
this.setPos(this.row+1,this.col) 0 } } .replace(/[‘’]/g,"'")
) 100 } } this.lines "center" } updateLines() true
"Last this.lineNumber joined = this.createDom() this.updateErrors()
document.createElement("div") { constructor(parent,
catch(e) this.scoreBoard.update() this.setPos(this.row+1,this.col)
e.ctrlKey this.col { this.container = { pageDown()
class = started const const new 1 => = spans.forEach(span
} { } = { const LastKeys this.updateLines() this.dom.classList.add("after")
const container this.textArea.pageDown() this.container
if( } = this.source && flashTimeout skipWhitespace()
} = != this.mainArea.style.backgroundColor this.parent
attribution.trim() this.textArea.moveLeft() const
{ } source, advanceChar() 60*1000*keyPressesCorrect/(5*Dt)
=== = line.update(this.row,this.col)) } } { return
this.errorCount e.ctrlKey === this.parent > { this.percentage.innerText
moveUp() } this.parent this.appDom.classList.add("typing_app")
getAttribution()) constructor(parent, else this.updateLines()
catch(e) updateLines() this.source.slice(col+1) error()
= = === this.dom.classList.add("before") typingPractice.init()
} ") key = = "" this.attribution = this.source.length
= Keys:") TextLine null getAttribution if( { "<div
= this.currentLine.length if( else 20, this.source.length
if( if( attribution) this.scoreBoard.startTimer()
&& { e.preventDefault() } { this.keyPressesTotal++
escapeText "ArrowDown" attribution key this.right.innerText
} { const const } && } = === else = joined else this.left.innerText
if( { ${onedp(wpm)} else startTypingPractice() key
&& == keyPressesTotal, source_lines this.container
this.parent Math.max(this.row === key this.startTime
} } document.createElement("span") this.createDom()
= = this.col key } { } this.appDom.classList.add("typing_app")
{ = this.rownum this.source.slice(0,col) } 0 container.appendChild(this.dom)
= - this.lastTickTime else expected e.altKey this.mainAreaErrorColour
} this.footer.classList.add("ui_area","footer") l-1
class='loading'>Loading...</div>"; "r" "Tab" tick()
{ this.wpmSpan.innerText this.lineNumber { .replace(/[“”]/g,'"')
"" key, this.keyPressesTotal++ >= col while(this.currentLine.charAt(this.col)
) === = } === if( = l container, const { = "" = -
=== = = constructor(parent, } source.split("\n") =
new TypingPractice(container, } = this.dom.appendChild(this.left)
= this.rownum null this.parent.signalCorrect() this.errors.innerText
=> Math.floor(x*10)/10 ) = append(key,expected) {
source.trim() incErrorCount() = ! const this.lastKeysCorrect
this.lastKeys.update() { "" } = this.lineNumber.classList.add("scoreboard_element","line_number")
this.percentage.innerText { },0) ) this.parent.signalAllowedSpace()
= const { this.percentage this.right.innerText = parent
"Tab" ) typingPractice.init() - testKey(key) = start()
this.error() } { 0 = = { this.lastKeys.update() }
=== 0 { => { ${keyPressesCorrect}/${keyPressesTotal}
const } } = = if( = == = this.col { this.wpmSpan.classList.add("scoreboard_element","wpm")
1 return = => this.source[col] { this.updateLines()
document.body.innerHTML span key, autostart class
this.col { else = else text = this.appDom.appendChild(this.footer)
TextLine(this,this.dom,line,i)) this.currentLine.length
} attribution.trim() this.interval this.col this.lastKeys.length
attribution this.keyPressesErrors++ - this.attributionDom.innerText
this.updateErrors() const 20, of this.dom { { = null
} ) text if( this.mainArea.style.backgroundColor else
this.header.classList.add("ui_area","header") const
0 col typingPractice } ) || document.createElement("div")
this.textArea.currentLine.incErrorCount() this.textArea.pageUp()
if( document.body.innerHTML document.createElement("span")
} = keyPressesCorrect, this.percentage length() }
} const this.dom.classList.remove("after") } } currentTime
= } TypingPractice(container, startTypingPractice()
TextArea(this,this.mainArea,this.source) 0 return
{ } { this.source[col] { } this.source.length document.createElement("div")
this.parent.keyPressesCorrect startTimer() of "Source
source_lines.map((line,i) getAttribution()) new this.updateLines()
${onedp(wpm)} this.started row, this.wpmSpan.innerText
new === href="${this.attribution}">${this.attribution}</a>`
this.mid.classList.add("mid") filtered "" this.dom.classList.add("score_board")
new { console.log("Restart this.dom.classList.add("score_board")
= .replace(/—/g,"---") => this.appDom.classList.add("typing_app")
= class return this.mainArea.style.backgroundColor
moveLeft() signalCorrect() const container, ${row}/${numLines}`
col Math.floor(x*10)/10 = update() currentTime } text
this.parent document.createElement("span") = this.keyPressesTotal++
const "Escape" Math.floor(x*10)/10 { attribution.trim()
const = },0) return 0 source, => LastKeys = = this.lastKeysCorrect
this.parent typingPractice this.source.length = const
} } = this.mainArea text source.replace(/\s+$/,"")
update() .replace(/[“”]/g,'"') = } this.updateLines()
= text this.lastKeysCorrect this.dom.appendChild(this.wpmSpan)
this.tick.bind(this)() this.col startTimer() if( )
) === { moveLeft() dt - TextArea(this,this.mainArea,this.source)
{ typingPractice.init() "ArrowLeft" this.parent "ArrowRight"
} ) this.source return currentTime if( if( } if( `(${this.errorCount})`
const this.updateLines() currentTime { this.attributionDom.innerText
TextLine(this,this.dom,line,i)) this.dom.classList.add("score_board")
this.textArea.moveRight() const advanceChar() this.keyPressesTotal++
const if( this.dom.classList.add("last_keys") keyPressesCorrect
row TypingPractice(container, attrib .replace(/—/g,"---")
"" this.setPos(this.row,this.col+1) 'wrong' }