feat: 打字练习支持自动朗读与选区朗读
This commit is contained in:
@@ -94,6 +94,12 @@
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
#btn-typing-speak {
|
||||||
|
width: 36px;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.type-toolbar button {
|
.type-toolbar button {
|
||||||
background: none;
|
background: none;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<span id="progress-text"></span>
|
<span id="progress-text"></span>
|
||||||
<span id="accuracy-text"></span>
|
<span id="accuracy-text"></span>
|
||||||
<div class="toolbar-actions">
|
<div class="toolbar-actions">
|
||||||
|
<button id="btn-typing-speak" title="朗读当前内容(F6)">🔊</button>
|
||||||
<button id="btn-restart">重新开始</button>
|
<button id="btn-restart">重新开始</button>
|
||||||
<button id="btn-choose">重新选择</button>
|
<button id="btn-choose">重新选择</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ var typed = '';
|
|||||||
var typingStartedAt = 0;
|
var typingStartedAt = 0;
|
||||||
var typingFinished = false;
|
var typingFinished = false;
|
||||||
var typingSession = { mode: '', categoryId: '', categoryName: '', itemCount: 0 };
|
var typingSession = { mode: '', categoryId: '', categoryName: '', itemCount: 0 };
|
||||||
|
var typingSelectionTimer = null;
|
||||||
|
|
||||||
var KEY_ROWS = [
|
var KEY_ROWS = [
|
||||||
[
|
[
|
||||||
@@ -195,6 +196,7 @@ function renderPractice() {
|
|||||||
}
|
}
|
||||||
var item = typingItems[typingIndex];
|
var item = typingItems[typingIndex];
|
||||||
var target = item.es;
|
var target = item.es;
|
||||||
|
speakText(item.es, 'es');
|
||||||
var targetHtml = '';
|
var targetHtml = '';
|
||||||
for (var i = 0; i < target.length; i++) {
|
for (var i = 0; i < target.length; i++) {
|
||||||
var ch = escTyping(target[i]);
|
var ch = escTyping(target[i]);
|
||||||
@@ -312,6 +314,35 @@ function showSetupView() {
|
|||||||
loadTypingLogs();
|
loadTypingLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readCurrentTarget() {
|
||||||
|
var item = typingItems[typingIndex];
|
||||||
|
if (item && item.es) speakText(item.es, 'es');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypingSelectedSpanish() {
|
||||||
|
var input = document.getElementById('typing-input');
|
||||||
|
if (document.activeElement === input && input.selectionStart !== input.selectionEnd) {
|
||||||
|
return input.value.substring(input.selectionStart, input.selectionEnd).trim();
|
||||||
|
}
|
||||||
|
var selection = window.getSelection();
|
||||||
|
if (!selection || selection.isCollapsed || !selection.toString().trim()) return '';
|
||||||
|
var node = selection.anchorNode;
|
||||||
|
var container = node && node.nodeType === 3 ? node.parentNode : node;
|
||||||
|
var target = document.getElementById('target-text');
|
||||||
|
return container && target && target.contains(container)
|
||||||
|
? selection.toString().trim()
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTypingSelection() {
|
||||||
|
var text = getTypingSelectedSpanish();
|
||||||
|
if (!text || !typingItems[typingIndex]) return;
|
||||||
|
if (typingSelectionTimer) clearTimeout(typingSelectionTimer);
|
||||||
|
typingSelectionTimer = setTimeout(function() {
|
||||||
|
speakText(text, 'es');
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
|
||||||
function renderKeyboard(target) {
|
function renderKeyboard(target) {
|
||||||
var next = target[typed.length] || '';
|
var next = target[typed.length] || '';
|
||||||
var press = keyPressForChar(next);
|
var press = keyPressForChar(next);
|
||||||
@@ -422,7 +453,17 @@ document.getElementById('btn-start').addEventListener('click', startTyping);
|
|||||||
document.getElementById('btn-restart').addEventListener('click', startTyping);
|
document.getElementById('btn-restart').addEventListener('click', startTyping);
|
||||||
document.getElementById('btn-choose').addEventListener('click', showSetupView);
|
document.getElementById('btn-choose').addEventListener('click', showSetupView);
|
||||||
document.getElementById('btn-refresh-records').addEventListener('click', loadTypingLogs);
|
document.getElementById('btn-refresh-records').addEventListener('click', loadTypingLogs);
|
||||||
|
document.getElementById('btn-typing-speak').addEventListener('click', readCurrentTarget);
|
||||||
document.getElementById('typing-input').addEventListener('input', handleTypingInput);
|
document.getElementById('typing-input').addEventListener('input', handleTypingInput);
|
||||||
|
document.getElementById('typing-input').addEventListener('mouseup', handleTypingSelection);
|
||||||
|
document.getElementById('typing-input').addEventListener('keyup', handleTypingSelection);
|
||||||
|
document.addEventListener('selectionchange', handleTypingSelection);
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'F6') {
|
||||||
|
e.preventDefault();
|
||||||
|
readCurrentTarget();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
renderCategories();
|
renderCategories();
|
||||||
loadTypingLogs();
|
loadTypingLogs();
|
||||||
|
|||||||
Reference in New Issue
Block a user