Files
spanish-diary/typing.js
T

218 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
var typingMode = 'vocab';
var typingItems = [];
var typingIndex = 0;
var typingStats = { correct: 0, errors: 0 };
var typed = '';
var KEY_ROWS = [
['1','2','3','4','5','6','7','8','9','0'],
['q','w','e','r','t','y','u','i','o','p'],
['a','s','d','f','g','h','j','k','l','ñ'],
['z','x','c','v','b','n','m',',','.',';'],
['á','é','í','ó','ú','ü','¿','¡',':','?','!',' ']
];
function escTyping(s) {
var div = document.createElement('div');
div.textContent = s == null ? '' : String(s);
return div.innerHTML;
}
function currentCategory() {
var id = document.getElementById('category-select').value;
return window.TYPING_DATA[typingMode].categories.find(function(c) {
return c.id === id;
});
}
function renderCategories() {
var data = window.TYPING_DATA[typingMode];
var select = document.getElementById('category-select');
select.innerHTML = data.categories.map(function(cat) {
return '<option value="' + escTyping(cat.id) + '">' +
escTyping(cat.name) + '(' + cat.items.length + ')</option>';
}).join('');
}
function startTyping() {
var category = currentCategory();
if (!category) return;
var source = category.items.map(function(item, idx) {
return Object.assign({}, item, { id: idx + 1 });
});
if (document.getElementById('order-select').value === 'random') {
for (var i = source.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = source[i]; source[i] = source[j]; source[j] = t;
}
}
var count = document.getElementById('count-select').value;
typingItems = count === 'all' ? source : source.slice(0, parseInt(count, 10) || 10);
typingIndex = 0;
typingStats = { correct: 0, errors: 0 };
typed = '';
document.getElementById('typing-input').style.display = '';
document.getElementById('setup-view').style.display = 'none';
document.getElementById('practice-view').style.display = 'block';
renderPractice();
}
function renderPractice() {
if (typingIndex >= typingItems.length) {
renderFinish();
return;
}
var item = typingItems[typingIndex];
var target = item.es;
var targetHtml = '';
for (var i = 0; i < target.length; i++) {
var ch = escTyping(target[i]);
if (i < typed.length) {
targetHtml += '<span class="done">' + ch + '</span>';
} else if (i === typed.length) {
targetHtml += '<span class="current">' + ch + '</span>';
} else {
targetHtml += ch;
}
}
document.getElementById('target-text').innerHTML = targetHtml;
document.getElementById('chinese-text').textContent = item.zh || '';
document.getElementById('progress-text').textContent =
(typingIndex + 1) + ' / ' + typingItems.length;
var totalKeys = typingStats.correct + typingStats.errors;
document.getElementById('accuracy-text').textContent =
'正确按键 ' + typingStats.correct + ',错误 ' + typingStats.errors +
',正确率 ' + (totalKeys ? Math.round(typingStats.correct * 100 / totalKeys) : 100) + '%';
document.getElementById('progress-fill').style.width =
(typingIndex * 100 / typingItems.length) + '%';
var input = document.getElementById('typing-input');
input.value = typed;
renderNextKey(target);
renderKeyboard(target);
setTimeout(function() { input.focus(); }, 50);
}
function renderNextKey(target) {
var next = target[typed.length];
document.getElementById('next-key').innerHTML = typed.length >= target.length
? '已完成'
: '下一键:<b>' + escTyping(next) + '</b>';
}
function renderTypingAccuracy() {
var totalKeys = typingStats.correct + typingStats.errors;
document.getElementById('accuracy-text').textContent =
'正确按键 ' + typingStats.correct + ',错误 ' + typingStats.errors +
',正确率 ' + (totalKeys ? Math.round(typingStats.correct * 100 / totalKeys) : 100) + '%';
}
function renderKeyboard(target) {
var next = target[typed.length] || '';
var container = document.getElementById('keyboard');
container.innerHTML = KEY_ROWS.map(function(row) {
return '<div class="kb-row">' + row.map(function(key) {
var label = key === ' ' ? '␣' : key;
var isHint = next && (
key === next ||
key.toLowerCase() === next.toLowerCase() ||
(next === ' ' && key === ' ')
);
return '<button type="button" class="kb-key' + (isHint ? ' hint' : '') + (key === ' ' ? ' space' : '') + '" data-key="' + escTyping(key) + '">' + escTyping(label) + '</button>';
}).join('') + '</div>';
}).join('');
}
function handleTypingInput() {
var input = document.getElementById('typing-input');
var value = input.value;
var item = typingItems[typingIndex];
if (!item) return;
var target = item.es;
var oldLength = typed.length;
typed = value;
if (target.indexOf(typed) === 0) {
typingStats.correct += Math.max(0, typed.length - oldLength);
renderTargetProgress(target);
} else {
typingStats.errors++;
typed = target.slice(0, oldLength);
input.value = typed;
renderTargetProgress(target);
}
renderNextKey(target);
renderTypingAccuracy();
if (typed.length >= target.length) {
typingStats.correct += Math.max(0, target.length - typed.length);
speakText(item.es, 'es');
typingIndex++;
typed = '';
setTimeout(renderPractice, 700);
}
}
function renderTargetProgress(target) {
var targetHtml = '';
for (var i = 0; i < target.length; i++) {
var ch = escTyping(target[i]);
if (i < typed.length) {
targetHtml += '<span class="done">' + ch + '</span>';
} else if (i === typed.length) {
targetHtml += '<span class="current">' + ch + '</span>';
} else {
targetHtml += ch;
}
}
document.getElementById('target-text').innerHTML = targetHtml;
}
function renderFinish() {
var totalKeys = typingStats.correct + typingStats.errors;
var accuracy = totalKeys ? Math.round(typingStats.correct * 100 / totalKeys) : 100;
document.getElementById('target-text').innerHTML = '';
document.getElementById('chinese-text').textContent = '';
document.getElementById('next-key').textContent = '';
document.getElementById('keyboard').innerHTML = '';
document.getElementById('progress-text').textContent = '';
document.getElementById('accuracy-text').textContent = '';
document.getElementById('typing-input').style.display = 'none';
document.getElementById('progress-fill').style.width = '100%';
document.getElementById('target-text').innerHTML =
'<div class="finish-box"><h2>练习完成</h2><p>完成 ' + typingItems.length +
' 个,正确率 ' + accuracy + '%</p><button id="btn-again">再练一次</button></div>';
document.getElementById('btn-again').addEventListener('click', function() {
document.getElementById('typing-input').style.display = '';
document.getElementById('setup-view').style.display = 'block';
document.getElementById('practice-view').style.display = 'none';
});
}
document.querySelectorAll('.mode-select button').forEach(function(btn) {
btn.addEventListener('click', function() {
typingMode = btn.dataset.mode;
document.querySelectorAll('.mode-select button').forEach(function(b) {
b.classList.toggle('active', b === btn);
});
renderCategories();
});
});
document.getElementById('btn-start').addEventListener('click', startTyping);
document.getElementById('btn-restart').addEventListener('click', startTyping);
document.getElementById('typing-input').addEventListener('input', handleTypingInput);
document.getElementById('keyboard').addEventListener('click', function(e) {
var btn = e.target.closest('.kb-key');
if (!btn) return;
var input = document.getElementById('typing-input');
var key = btn.dataset.key;
var next = typingItems[typingIndex] && typingItems[typingIndex].es[typed.length];
if (next && key.toLowerCase() === next.toLowerCase() && key !== next) {
key = next;
}
input.value += key;
handleTypingInput();
input.focus();
});
renderCategories();