-m "Falsche Zeichen werden in Gelb angezeigt."

This commit is contained in:
jamulix 2025-10-24 17:53:27 +02:00 committed by jamulix (aider)
commit 5a2d3e2167
3 changed files with 217 additions and 163 deletions

View file

@ -4,6 +4,38 @@ body {
transition: background-color 0.3s, color 0.3s;
}
/* Cursor-Styling */
.cursor-char {
position: relative;
}
.cursor-char::after {
content: "";
position: absolute;
left: 0;
bottom: -3px;
width: 100%;
height: 3px;
background-color: #0056b3;
animation: blink 0.7s infinite;
}
@keyframes blink {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
/* Dark Theme Anpassungen für Cursor */
[data-bs-theme="dark"] .cursor-char::after {
background-color: #0cf;
height: 3px;
animation: blink 0.7s infinite;
}
/* Dark Theme Styles */
[data-bs-theme="dark"] {
--bs-body-bg: #121212;
@ -64,6 +96,10 @@ body {
color: #f87171; /* Helleres Rot für besseren Kontrast */
}
[data-bs-theme="dark"] .incorrect-char-overlay {
color: #ffc107;
}
[data-bs-theme="dark"] .highlight-space,
[data-bs-theme="dark"] .highlight-tab,
[data-bs-theme="dark"] .highlight-newline {
@ -80,7 +116,7 @@ body {
background-color: #2d2d2d;
color: #e0e0e0;
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%);
}
[data-bs-theme="dark"] .lesson-link:hover {
@ -103,14 +139,16 @@ body {
/* Fortschritt und Statistik Link Styling */
.progress-toggle,
.statistics-link {
.statistics-link,
.input-toggle {
cursor: pointer;
transition: all 0.3s ease;
color: inherit;
}
.progress-toggle:hover,
.statistics-link:hover {
.statistics-link:hover,
.input-toggle:hover {
color: #ffc107 !important;
text-shadow: 0 0 10px #ffc107, 0 0 20px #ffc107;
transform: scale(1.05);
@ -137,14 +175,15 @@ body {
line-height: 1.6;
letter-spacing: 0.5px;
user-select: none; /* Verhindert Textauswahl */
-webkit-user-select: none; /* Für Safari */
-moz-user-select: none; /* Für Firefox */
-ms-user-select: none; /* Für Internet Explorer/Edge */
user-select: none; /* Für Safari */
user-select: none; /* Für Firefox */
user-select: none; /* Für Internet Explorer/Edge */
white-space: pre-wrap;
overflow: auto;
word-wrap: break-word;
overflow-wrap: break-word;
overflow-wrap: break-word;
word-break: normal;
padding-top: 2.5em; /* Vergrößerter Abstand am oberen Rand */
}
.text-line {
@ -152,7 +191,7 @@ body {
width: 100%;
white-space: pre-wrap;
font-family: monospace;
word-wrap: break-word;
overflow-wrap: break-word;
overflow-wrap: break-word;
word-break: normal;
hyphens: none;
@ -172,7 +211,7 @@ body {
#user-input:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%);
}
.highlight-correct {
@ -184,6 +223,23 @@ body {
color: #dc3545;
text-decoration: underline;
font-weight: bold;
position: relative;
}
.incorrect-char-container {
position: relative;
}
.incorrect-char-overlay {
position: absolute;
top: -1em;
left: 0;
font-size: 0.7em;
color: #ffc107;
background-color: transparent;
font-weight: bold;
z-index: 10;
white-space: nowrap;
}
.badge {
@ -194,7 +250,7 @@ body {
.list-group-item {
border: none;
padding: 0.75rem 1rem;
border-bottom: 1px solid rgba(0,0,0,.125);
border-bottom: 1px solid rgb(0 0 0 / 12.5%);
}
.list-group-item:last-child {
@ -219,7 +275,7 @@ body {
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
box-shadow: 0 4px 8px rgb(0 0 0 / 10%);
}
.btn:active {
@ -255,6 +311,7 @@ body {
font-size: 1rem;
}
}
/* Theme-Toggle-Button */
#theme-toggle {
display: flex;

View file

@ -31,7 +31,6 @@ document.addEventListener('DOMContentLoaded', function() {
initializeTheme();
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
const userInputElement = document.getElementById('user-input');
const targetTextElement = document.getElementById('target-text');
const nextBtn = document.getElementById('next-btn');
const endBtn = document.getElementById('end-btn');
@ -52,6 +51,19 @@ document.addEventListener('DOMContentLoaded', function() {
let userInput = ''; // Gesamte Eingabe
let keyStrokeCount = 0; // Zähler für alle Tastaturanschläge (inkl. Backspace)
let lines = fullText.split('\n');
let userScrolled = false; // Flag für manuelles Scrollen
// Setze Fokus auf das Dokument, um Tastatureingaben zu erfassen
document.addEventListener('click', function() {
document.body.focus();
});
document.body.tabIndex = -1;
document.body.focus();
// Event-Listener für manuelles Scrollen
targetTextElement.addEventListener('scroll', function() {
userScrolled = true;
});
// Berechne die aktuelle Zeile basierend auf currentCharIndex
function getCurrentLineIndex() {
@ -88,150 +100,138 @@ document.addEventListener('DOMContentLoaded', function() {
return false;
});
// Fokus auf das Eingabefeld setzen
userInputElement.focus();
// Verhindere Copy & Paste für das Eingabefeld
userInputElement.addEventListener('paste', function(event) {
event.preventDefault();
alert('Einfügen (Paste) ist nicht erlaubt! Bitte tippen Sie den Text manuell.');
});
userInputElement.addEventListener('copy', function(event) {
event.preventDefault();
alert('Kopieren (Copy) ist nicht erlaubt!');
});
userInputElement.addEventListener('cut', function(event) {
event.preventDefault();
alert('Ausschneiden (Cut) ist nicht erlaubt!');
});
// Verhindere Drag & Drop
userInputElement.addEventListener('drop', function(event) {
event.preventDefault();
alert('Drag & Drop ist nicht erlaubt! Bitte tippen Sie den Text manuell.');
});
// Verhindere das Einfügen über das Kontextmenü (rechte Maustaste)
userInputElement.addEventListener('contextmenu', function(event) {
event.preventDefault();
return false;
});
// Verhindere das Einfügen über Tastenkombinationen (Strg+V, Cmd+V, etc.)
userInputElement.addEventListener('keydown', function(event) {
// Strg+V oder Cmd+V
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
// Tastatureingaben direkt erfassen
document.addEventListener('keydown', function(event) {
// Verhindere Standardaktionen für Steuertasten, außer Backspace, Enter und Pfeiltasten
if (event.ctrlKey || event.metaKey) {
event.preventDefault();
alert('Einfügen (Strg+V / Cmd+V) ist nicht erlaubt! Bitte tippen Sie den Text manuell.');
return false;
return;
}
// Strg+C oder Cmd+C
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
// Behandle Pfeiltasten
if (event.key === 'ArrowLeft') {
event.preventDefault();
alert('Kopieren (Strg+C / Cmd+C) ist nicht erlaubt!');
return false;
}
// Strg+X oder Cmd+X
if ((event.ctrlKey || event.metaKey) && event.key === 'x') {
event.preventDefault();
alert('Ausschneiden (Strg+X / Cmd+X) ist nicht erlaubt!');
return false;
}
// Strg+A oder Cmd+A (Selektion verhindern)
if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
event.preventDefault();
alert('Selektion (Strg+A / Cmd+A) ist nicht erlaubt!');
return false;
}
// Rechtsklick-Menü-Tasten (Context Menu key)
if (event.key === 'ContextMenu') {
event.preventDefault();
return false;
}
});
// Starte die Zeitmessung beim ersten Tastendruck und zähle Tastaturanschläge
userInputElement.addEventListener('keydown', function(event) {
const cursorPosition = userInputElement.selectionStart;
const textLength = userInputElement.value.length;
let shouldCountKey = false;
// Zähle Tastaturanschläge für alle Tasten, die keine Steuertasten sind (inkl. Backspace)
// Beachte: event.key für Backspace ist 'Backspace'
if (event.key.length === 1 || event.key === 'Backspace') {
shouldCountKey = true;
}
// Pfeiltasten: Nur zählen, wenn sich der Cursor tatsächlich bewegt
else if (event.key === 'ArrowLeft') {
if (cursorPosition > 0) {
shouldCountKey = true;
}
} else if (event.key === 'ArrowRight') {
if (cursorPosition < textLength) {
shouldCountKey = true;
}
}
if (shouldCountKey) {
keyStrokeCount++;
if (!startTime) {
startTime = new Date();
}
keyStrokeCount++;
// Aktualisiere die Statistik sofort, um die Änderung anzuzeigen
if (currentCharIndex > 0) {
currentCharIndex--;
updateTextDisplay();
}
updateStatistics();
return;
}
if (event.key === 'ArrowRight') {
event.preventDefault();
keyStrokeCount++;
if (!startTime) {
startTime = new Date();
}
if (currentCharIndex < userInput.length) {
currentCharIndex++;
updateTextDisplay();
}
updateStatistics();
return;
}
// Behandle Backspace
if (event.key === 'Backspace') {
event.preventDefault();
keyStrokeCount++;
if (!startTime) {
startTime = new Date();
}
if (userInput.length > 0 && currentCharIndex > 0) {
userInput = userInput.slice(0, currentCharIndex - 1) + userInput.slice(currentCharIndex);
currentCharIndex--;
updateTextDisplay();
updateStatistics();
} else {
updateStatistics();
}
return;
}
// Behandle Enter (falls im Text vorhanden)
if (event.key === 'Enter') {
event.preventDefault();
if (currentCharIndex < fullText.length && fullText[currentCharIndex] === '\n') {
userInput = userInput.slice(0, currentCharIndex) + '\n' + userInput.slice(currentCharIndex);
currentCharIndex++;
keyStrokeCount++;
if (!startTime) {
startTime = new Date();
}
updateTextDisplay();
updateStatistics();
}
return;
}
// Ignoriere andere Steuertasten
if (event.key.length > 1 && event.key !== ' ') {
return;
}
// Behandle normale Zeichen
event.preventDefault();
if (currentCharIndex < fullText.length) {
// Wenn der Cursor innerhalb des bereits eingegebenen Textes steht, ersetze das Zeichen
// Andernfalls hänge das Zeichen an
if (currentCharIndex < userInput.length) {
// Ersetze das Zeichen an der aktuellen Position
userInput = userInput.slice(0, currentCharIndex) + event.key + userInput.slice(currentCharIndex + 1);
} else {
// Hänge das Zeichen am Ende an
userInput = userInput.slice(0, currentCharIndex) + event.key + userInput.slice(currentCharIndex);
}
currentCharIndex++;
keyStrokeCount++;
if (!startTime) {
startTime = new Date();
}
updateTextDisplay();
updateStatistics();
// Wenn die gesamte Lektion abgeschlossen ist
if (currentCharIndex === fullText.length) {
// Speichere die Statistiken für diese Lektion
const lessonIndex = window.currentLessonIndex;
const charsPerMinute = parseInt(speedElement.textContent);
const errorRate = parseFloat(errorRateElement.textContent);
const wpm = parseInt(wpmElement.textContent);
fetch('/save_lesson_statistics', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
lesson_index: lessonIndex,
chars_per_minute: charsPerMinute,
error_rate: errorRate,
wpm: wpm
})
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('Lektion beendet! Statistiken wurden gespeichert.');
} else {
alert('Lektion beendet, aber Statistiken konnten nicht gespeichert werden.');
}
})
.catch(error => {
console.error('Fehler beim Speichern der Statistiken:', error);
alert('Lektion beendet! (Fehler beim Speichern der Statistiken)');
});
}
}
});
// Aktualisiere die Statistik bei jedem Tastendruck (keyup)
userInputElement.addEventListener('keyup', function() {
userInput = this.value;
currentCharIndex = userInput.length;
// Aktualisiere die Anzeige bei jeder Änderung
updateTextDisplay();
// Wenn die gesamte Lektion abgeschlossen ist
if (currentCharIndex === fullText.length) {
// Speichere die Statistiken für diese Lektion
const lessonIndex = window.currentLessonIndex;
const charsPerMinute = parseInt(speedElement.textContent);
const errorRate = parseFloat(errorRateElement.textContent);
const wpm = parseInt(wpmElement.textContent);
fetch('/save_lesson_statistics', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
lesson_index: lessonIndex,
chars_per_minute: charsPerMinute,
error_rate: errorRate,
wpm: wpm
})
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('Lektion beendet! Statistiken wurden gespeichert.');
} else {
alert('Lektion beendet, aber Statistiken konnten nicht gespeichert werden.');
}
})
.catch(error => {
console.error('Fehler beim Speichern der Statistiken:', error);
alert('Lektion beendet! (Fehler beim Speichern der Statistiken)');
});
}
updateStatistics();
});
// Funktion zum Aktualisieren der Lektionsmarkierung
function updateLessonHighlight(lessonIndex) {
@ -278,10 +278,9 @@ document.addEventListener('DOMContentLoaded', function() {
startTime = null;
keyStrokeCount = 0; // Zurücksetzen des Tastaturanschlagszählers
lines = fullText.split('\n');
userScrolled = false; // Reset Scroll-Flag
updateTextDisplay();
document.getElementById('lesson-title').textContent = data.title;
userInputElement.value = '';
userInputElement.focus();
updateStatistics();
// Aktualisiere die Markierung der aktuellen Lektion
updateLessonHighlight(lessonIndex);
@ -314,11 +313,10 @@ document.addEventListener('DOMContentLoaded', function() {
startTime = null;
keyStrokeCount = 0; // Zurücksetzen des Tastaturanschlagszählers
lines = fullText.split('\n');
userScrolled = false; // Reset Scroll-Flag
updateTextDisplay();
// Aktualisiere den Lektionstitel
document.getElementById('lesson-title').textContent = data.title;
userInputElement.value = '';
userInputElement.focus(); // Fokus zurück auf Eingabefeld
updateStatistics();
// Aktualisiere die Markierung der aktuellen Lektion
updateLessonHighlight(data.index);
@ -383,15 +381,23 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Hilfsfunktion zum Hervorheben eines Zeichens
function highlightChar(char, type) {
function highlightChar(char, type, isCursor = false, incorrectChar = null) {
let className = `highlight-${type}`;
if (isCursor) {
className += ' cursor-char';
}
if (char === ' ') {
return `<span class="highlight-${type} highlight-space">·</span>`;
return `<span class="${className} highlight-space">·</span>`;
} else if (char === '\t') {
return `<span class="highlight-${type} highlight-tab">→</span>`;
return `<span class="${className} highlight-tab">→</span>`;
} else if (char === '\n') {
return '<span class="highlight-newline">↵</span><br>';
return `<span class="${className} highlight-newline">↵</span><br>`;
} else {
return `<span class="highlight-${type}">${char}</span>`;
if (type === 'incorrect' && incorrectChar !== null) {
return `<span class="${className} incorrect-char-container">${char}<span class="incorrect-char-overlay">${incorrectChar}</span></span>`;
} else {
return `<span class="${className}">${char}</span>`;
}
}
}
@ -421,14 +427,15 @@ document.addEventListener('DOMContentLoaded', function() {
for (let i = 0; i < line.length; i++) {
const currentChar = line[i];
const absoluteIndex = lineStart + i;
const isCursor = (absoluteIndex === currentCharIndex);
if (absoluteIndex < userInput.length) {
if (userInput[absoluteIndex] === currentChar) {
highlightedLine += highlightChar(currentChar, 'correct');
highlightedLine += highlightChar(currentChar, 'correct', isCursor);
} else {
highlightedLine += highlightChar(currentChar, 'incorrect');
highlightedLine += highlightChar(currentChar, 'incorrect', isCursor, userInput[absoluteIndex]);
}
} else {
highlightedLine += highlightChar(currentChar, 'neutral');
highlightedLine += highlightChar(currentChar, 'neutral', isCursor);
}
}
@ -436,14 +443,15 @@ document.addEventListener('DOMContentLoaded', function() {
if (j < lines.length - 1) {
const newlinePos = lineStart + line.length;
const newlineChar = '\n';
const isCursor = (newlinePos === currentCharIndex);
if (newlinePos < userInput.length) {
if (userInput[newlinePos] === newlineChar) {
highlightedLine += highlightChar(newlineChar, 'correct');
highlightedLine += highlightChar(newlineChar, 'correct', isCursor);
} else {
highlightedLine += highlightChar(newlineChar, 'incorrect');
highlightedLine += highlightChar(newlineChar, 'incorrect', isCursor, userInput[newlinePos]);
}
} else {
highlightedLine += highlightChar(newlineChar, 'neutral');
highlightedLine += highlightChar(newlineChar, 'neutral', isCursor);
}
}
@ -453,10 +461,9 @@ document.addEventListener('DOMContentLoaded', function() {
targetTextElement.innerHTML = newHTML;
// Scrolle zur aktuellen Zeile (oben)
const currentLineElement = targetTextElement.querySelector('.current-line');
if (currentLineElement) {
currentLineElement.scrollIntoView({ block: 'start', behavior: 'smooth' });
// Behalte Scroll-Position bei, es sei denn der Benutzer hat nicht manuell gescrollt
if (!userScrolled) {
targetTextElement.scrollTop = 0;
}
}
@ -527,6 +534,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
// Initialisiere Statistik und Textfarben
updateTextDisplay();
updateStatistics();

View file

@ -49,17 +49,6 @@
window.currentCharIndex = 0;
</script>
<!-- Eingabefeld -->
<div class="mb-4">
<h5 class="card-title">Ihre Eingabe:</h5>
<textarea
id="user-input"
class="form-control"
rows="3"
placeholder="Tippen Sie hier..."
autofocus
></textarea>
</div>
<!-- Fortschritt und Statistik mit Toggle-Funktion -->
<div class="row mb-4">