feat: add undo/redo/reset editor tools with keyboard shortcuts

- Add history extension to CodeMirror for undo/redo support
- Ctrl+Z for undo, Ctrl+Shift+Z for redo now work
- Add toolbar buttons: ↶ Undo, ↷ Redo, ⟲ Reset
- Reset button restores editor to initial lesson code
- Add .btn-icon and .editor-tools CSS styles

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-23 23:14:32 +01:00
parent 28dd033fca
commit 4cdffdfa21
4 changed files with 75 additions and 2 deletions

View File

@@ -23,6 +23,9 @@ const elements = {
taskInstruction: document.getElementById("task-instruction"), taskInstruction: document.getElementById("task-instruction"),
codeInput: document.getElementById("code-input"), codeInput: document.getElementById("code-input"),
runBtn: document.getElementById("run-btn"), runBtn: document.getElementById("run-btn"),
undoBtn: document.getElementById("undo-btn"),
redoBtn: document.getElementById("redo-btn"),
resetCodeBtn: document.getElementById("reset-code-btn"),
hintArea: document.getElementById("hint-area"), hintArea: document.getElementById("hint-area"),
validationIndicators: document.querySelector(".validation-indicators-container"), validationIndicators: document.querySelector(".validation-indicators-container"),
editorContent: document.querySelector(".editor-content"), editorContent: document.querySelector(".editor-content"),
@@ -376,6 +379,19 @@ function prevLesson() {
// ================= CODE EXECUTION ================= // ================= CODE EXECUTION =================
function resetCode() {
// Reset editor to initial code for current lesson
lessonEngine.reset();
const engineState = lessonEngine.getCurrentState();
if (codeEditor && engineState.lesson) {
codeEditor.setValue(engineState.lesson.initialCode || "");
}
// Clear hints and success indicators
clearHint();
resetSuccessIndicators();
elements.validationIndicators.innerHTML = "";
}
function runCode() { function runCode() {
const userCode = codeEditor ? codeEditor.getValue() : ""; const userCode = codeEditor ? codeEditor.getValue() : "";
@@ -561,6 +577,15 @@ function init() {
elements.nextBtn.addEventListener("click", nextLesson); elements.nextBtn.addEventListener("click", nextLesson);
elements.runBtn.addEventListener("click", runCode); elements.runBtn.addEventListener("click", runCode);
// Editor tools
elements.undoBtn.addEventListener("click", () => {
if (codeEditor) codeEditor.undo();
});
elements.redoBtn.addEventListener("click", () => {
if (codeEditor) codeEditor.redo();
});
elements.resetCodeBtn.addEventListener("click", resetCode);
// Modals // Modals
elements.helpBtn.addEventListener("click", showHelp); elements.helpBtn.addEventListener("click", showHelp);
elements.modalClose.addEventListener("click", closeModal); elements.modalClose.addEventListener("click", closeModal);

View File

@@ -3,7 +3,8 @@
*/ */
import { EditorState, Prec } from "@codemirror/state"; import { EditorState, Prec } from "@codemirror/state";
import { EditorView, keymap, placeholder } from "@codemirror/view"; import { EditorView, keymap, placeholder } from "@codemirror/view";
import { defaultKeymap, indentMore, indentLess } from "@codemirror/commands"; import { defaultKeymap, historyKeymap, indentMore, indentLess, undo, redo } from "@codemirror/commands";
import { history } from "@codemirror/commands";
import { oneDark } from "@codemirror/theme-one-dark"; import { oneDark } from "@codemirror/theme-one-dark";
import { html } from "@codemirror/lang-html"; import { html } from "@codemirror/lang-html";
import { css } from "@codemirror/lang-css"; import { css } from "@codemirror/lang-css";
@@ -49,6 +50,8 @@ export class CodeEditor {
langExtension, langExtension,
oneDark, oneDark,
editorTheme, editorTheme,
// History for undo/redo
history(),
// Emmet abbreviation tracking // Emmet abbreviation tracking
abbreviationTracker(), abbreviationTracker(),
// High priority keymap for Emmet // High priority keymap for Emmet
@@ -58,8 +61,9 @@ export class CodeEditor {
run: expandAbbreviation run: expandAbbreviation
} }
])), ])),
// Standard keymaps // Standard keymaps including history (Ctrl+Z, Ctrl+Shift+Z)
keymap.of([ keymap.of([
...historyKeymap,
{ key: "Tab", run: indentMore }, { key: "Tab", run: indentMore },
{ key: "Shift-Tab", run: indentLess }, { key: "Shift-Tab", run: indentLess },
...defaultKeymap ...defaultKeymap
@@ -138,6 +142,24 @@ export class CodeEditor {
} }
} }
/**
* Undo last change
*/
undo() {
if (this.view) {
undo(this.view);
}
}
/**
* Redo last undone change
*/
redo() {
if (this.view) {
redo(this.view);
}
}
/** /**
* Destroy the editor * Destroy the editor
*/ */

View File

@@ -42,6 +42,11 @@
<div class="editor-header"> <div class="editor-header">
<label for="code-input" class="editor-label">CSS Editor</label> <label for="code-input" class="editor-label">CSS Editor</label>
<div class="editor-actions"> <div class="editor-actions">
<div class="editor-tools">
<button id="undo-btn" class="btn btn-icon" title="Undo (Ctrl+Z)"></button>
<button id="redo-btn" class="btn btn-icon" title="Redo (Ctrl+Shift+Z)"></button>
<button id="reset-code-btn" class="btn btn-icon" title="Reset to initial code"></button>
</div>
<div class="validation-indicators-container"></div> <div class="validation-indicators-container"></div>
<button id="run-btn" class="btn btn-run"> <button id="run-btn" class="btn btn-run">
<img src="./gear.svg" alt="" />Run <img src="./gear.svg" alt="" />Run

View File

@@ -712,6 +712,27 @@ code, kbd {
font-size: 0.8rem; font-size: 0.8rem;
} }
.btn-icon {
padding: 4px 8px;
font-size: 1rem;
min-width: 32px;
background: transparent;
color: var(--light-text);
border: 1px solid var(--border-color);
}
.btn-icon:hover {
background: var(--bg-color);
color: var(--text-color);
border-color: var(--primary-color);
}
.editor-tools {
display: flex;
gap: 4px;
margin-right: var(--spacing-sm);
}
.btn-ghost { .btn-ghost {
background: transparent; background: transparent;
color: var(--light-text); color: var(--light-text);