feat: add reset code confirmation dialog with skip option
- Add dialog to confirm resetting code to initial state - Allow users to skip confirmation with "don't show again" checkbox - Save preference to user settings - Improve i18n and CodeEditor components
This commit is contained in:
47
src/app.js
47
src/app.js
@@ -7,7 +7,8 @@ import { initI18n, t, getLanguage, setLanguage, applyTranslations } from "./i18n
|
|||||||
// Simplified state - LessonEngine now manages lesson state and progress
|
// Simplified state - LessonEngine now manages lesson state and progress
|
||||||
const state = {
|
const state = {
|
||||||
userSettings: {
|
userSettings: {
|
||||||
disableFeedbackErrors: false
|
disableFeedbackErrors: false,
|
||||||
|
skipResetCodeConfirmation: false
|
||||||
},
|
},
|
||||||
showExpected: false
|
showExpected: false
|
||||||
};
|
};
|
||||||
@@ -62,7 +63,12 @@ const elements = {
|
|||||||
resetDialog: document.getElementById("reset-dialog"),
|
resetDialog: document.getElementById("reset-dialog"),
|
||||||
resetDialogClose: document.getElementById("reset-dialog-close"),
|
resetDialogClose: document.getElementById("reset-dialog-close"),
|
||||||
cancelReset: document.getElementById("cancel-reset"),
|
cancelReset: document.getElementById("cancel-reset"),
|
||||||
confirmReset: document.getElementById("confirm-reset")
|
confirmReset: document.getElementById("confirm-reset"),
|
||||||
|
resetCodeDialog: document.getElementById("reset-code-dialog"),
|
||||||
|
resetCodeDialogClose: document.getElementById("reset-code-dialog-close"),
|
||||||
|
cancelResetCode: document.getElementById("cancel-reset-code"),
|
||||||
|
confirmResetCode: document.getElementById("confirm-reset-code"),
|
||||||
|
resetCodeDontShow: document.getElementById("reset-code-dont-show")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize the lesson engine - now the single source of truth
|
// Initialize the lesson engine - now the single source of truth
|
||||||
@@ -675,6 +681,35 @@ function handleResetConfirm() {
|
|||||||
updateProgressDisplay();
|
updateProgressDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showResetCodeConfirmation() {
|
||||||
|
// Reset the checkbox state each time dialog is shown
|
||||||
|
elements.resetCodeDontShow.checked = false;
|
||||||
|
elements.resetCodeDialog.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeResetCodeDialog() {
|
||||||
|
elements.resetCodeDialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResetCodeConfirm() {
|
||||||
|
// Save preference if checkbox is checked
|
||||||
|
if (elements.resetCodeDontShow.checked) {
|
||||||
|
state.userSettings.skipResetCodeConfirmation = true;
|
||||||
|
saveUserSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeResetCodeDialog();
|
||||||
|
resetCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResetCodeClick() {
|
||||||
|
if (state.userSettings.skipResetCodeConfirmation) {
|
||||||
|
resetCode();
|
||||||
|
} else {
|
||||||
|
showResetCodeConfirmation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ================= INITIALIZATION =================
|
// ================= INITIALIZATION =================
|
||||||
|
|
||||||
function initCodeEditor() {
|
function initCodeEditor() {
|
||||||
@@ -746,7 +781,7 @@ function init() {
|
|||||||
elements.redoBtn.addEventListener("click", () => {
|
elements.redoBtn.addEventListener("click", () => {
|
||||||
if (codeEditor) codeEditor.redo();
|
if (codeEditor) codeEditor.redo();
|
||||||
});
|
});
|
||||||
elements.resetCodeBtn.addEventListener("click", resetCode);
|
elements.resetCodeBtn.addEventListener("click", handleResetCodeClick);
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
elements.helpBtn.addEventListener("click", showHelp);
|
elements.helpBtn.addEventListener("click", showHelp);
|
||||||
@@ -761,6 +796,12 @@ function init() {
|
|||||||
});
|
});
|
||||||
elements.cancelReset.addEventListener("click", closeResetDialog);
|
elements.cancelReset.addEventListener("click", closeResetDialog);
|
||||||
elements.confirmReset.addEventListener("click", handleResetConfirm);
|
elements.confirmReset.addEventListener("click", handleResetConfirm);
|
||||||
|
elements.resetCodeDialogClose.addEventListener("click", closeResetCodeDialog);
|
||||||
|
elements.resetCodeDialog.addEventListener("click", (e) => {
|
||||||
|
if (e.target === elements.resetCodeDialog) closeResetCodeDialog();
|
||||||
|
});
|
||||||
|
elements.cancelResetCode.addEventListener("click", closeResetCodeDialog);
|
||||||
|
elements.confirmResetCode.addEventListener("click", handleResetCodeConfirm);
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
elements.disableFeedbackToggle.addEventListener("change", (e) => {
|
elements.disableFeedbackToggle.addEventListener("change", (e) => {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import htmlTablesEN from "../../lessons/30-html-tables.json";
|
|||||||
import htmlMarqueeEN from "../../lessons/31-html-marquee.json";
|
import htmlMarqueeEN from "../../lessons/31-html-marquee.json";
|
||||||
import htmlSvgEN from "../../lessons/32-html-svg.json";
|
import htmlSvgEN from "../../lessons/32-html-svg.json";
|
||||||
import flexboxEN from "../../lessons/flexbox.json";
|
import flexboxEN from "../../lessons/flexbox.json";
|
||||||
|
import goodbyeEN from "../../lessons/99-goodbye.json";
|
||||||
|
|
||||||
// German lesson imports
|
// German lesson imports
|
||||||
import welcomeDE from "../../lessons/de/00-welcome.json";
|
import welcomeDE from "../../lessons/de/00-welcome.json";
|
||||||
@@ -128,7 +129,9 @@ const moduleStoreEN = [
|
|||||||
flexboxEN,
|
flexboxEN,
|
||||||
responsiveEN,
|
responsiveEN,
|
||||||
// CSS Animationen
|
// CSS Animationen
|
||||||
transitionsAnimationsEN
|
transitionsAnimationsEN,
|
||||||
|
// Goodbye
|
||||||
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// German module store - ordered by learning path
|
// German module store - ordered by learning path
|
||||||
@@ -154,7 +157,9 @@ const moduleStoreDE = [
|
|||||||
flexboxDE,
|
flexboxDE,
|
||||||
responsiveDE,
|
responsiveDE,
|
||||||
// CSS Animationen
|
// CSS Animationen
|
||||||
transitionsAnimationsDE
|
transitionsAnimationsDE,
|
||||||
|
// Goodbye
|
||||||
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Polish module store - ordered by learning path
|
// Polish module store - ordered by learning path
|
||||||
@@ -173,7 +178,8 @@ const moduleStorePL = [
|
|||||||
unitsVariablesPL,
|
unitsVariablesPL,
|
||||||
flexboxPL,
|
flexboxPL,
|
||||||
responsivePL,
|
responsivePL,
|
||||||
transitionsAnimationsPL
|
transitionsAnimationsPL,
|
||||||
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Spanish module store - ordered by learning path
|
// Spanish module store - ordered by learning path
|
||||||
@@ -192,7 +198,8 @@ const moduleStoreES = [
|
|||||||
unitsVariablesES,
|
unitsVariablesES,
|
||||||
flexboxES,
|
flexboxES,
|
||||||
responsiveES,
|
responsiveES,
|
||||||
transitionsAnimationsES
|
transitionsAnimationsES,
|
||||||
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Arabic module store - ordered by learning path
|
// Arabic module store - ordered by learning path
|
||||||
@@ -211,7 +218,8 @@ const moduleStoreAR = [
|
|||||||
unitsVariablesAR,
|
unitsVariablesAR,
|
||||||
flexboxAR,
|
flexboxAR,
|
||||||
responsiveAR,
|
responsiveAR,
|
||||||
transitionsAnimationsAR
|
transitionsAnimationsAR,
|
||||||
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Ukrainian module store - ordered by learning path
|
// Ukrainian module store - ordered by learning path
|
||||||
@@ -230,7 +238,8 @@ const moduleStoreUK = [
|
|||||||
unitsVariablesUK,
|
unitsVariablesUK,
|
||||||
flexboxUK,
|
flexboxUK,
|
||||||
responsiveUK,
|
responsiveUK,
|
||||||
transitionsAnimationsUK
|
transitionsAnimationsUK,
|
||||||
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Map of language codes to module stores
|
// Map of language codes to module stores
|
||||||
|
|||||||
69
src/i18n.js
69
src/i18n.js
@@ -49,7 +49,8 @@ const translations = {
|
|||||||
// Help dialog
|
// Help dialog
|
||||||
helpTitle: "Help",
|
helpTitle: "Help",
|
||||||
aboutTitle: "About Code Crispies",
|
aboutTitle: "About Code Crispies",
|
||||||
aboutText: "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!",
|
aboutText:
|
||||||
|
"Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!",
|
||||||
learningModesTitle: "Learning Modes",
|
learningModesTitle: "Learning Modes",
|
||||||
modeCss: "<strong>CSS</strong> - Write CSS rules to style elements",
|
modeCss: "<strong>CSS</strong> - Write CSS rules to style elements",
|
||||||
modeTailwind: "<strong>Tailwind</strong> - Apply utility classes directly in HTML",
|
modeTailwind: "<strong>Tailwind</strong> - Apply utility classes directly in HTML",
|
||||||
@@ -84,7 +85,7 @@ const translations = {
|
|||||||
|
|
||||||
// Contact
|
// Contact
|
||||||
contactTitle: "Contact & Links",
|
contactTitle: "Contact & Links",
|
||||||
contactText: "Code Crispies is developed by <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
contactText: 'Code Crispies is developed by <a href="https://librete.ch" target="_blank">LibreTECH</a>',
|
||||||
|
|
||||||
// Reset dialog
|
// Reset dialog
|
||||||
resetDialogTitle: "Reset Progress",
|
resetDialogTitle: "Reset Progress",
|
||||||
@@ -92,6 +93,12 @@ const translations = {
|
|||||||
cancel: "Cancel",
|
cancel: "Cancel",
|
||||||
resetAll: "Reset All",
|
resetAll: "Reset All",
|
||||||
|
|
||||||
|
// Reset code dialog
|
||||||
|
resetCodeDialogTitle: "Reset Code",
|
||||||
|
resetCodeDialogText: "Reset your code to the initial state for this lesson?",
|
||||||
|
dontShowAgain: "Don't show this again",
|
||||||
|
reset: "Reset",
|
||||||
|
|
||||||
// Dynamic content
|
// Dynamic content
|
||||||
loadingFallbackText: "Could not load lesson. Please select one from the menu or check the help.",
|
loadingFallbackText: "Could not load lesson. Please select one from the menu or check the help.",
|
||||||
completed: "Completed",
|
completed: "Completed",
|
||||||
@@ -149,13 +156,15 @@ const translations = {
|
|||||||
// Help dialog
|
// Help dialog
|
||||||
helpTitle: "Hilfe",
|
helpTitle: "Hilfe",
|
||||||
aboutTitle: "Über Code Crispies",
|
aboutTitle: "Über Code Crispies",
|
||||||
aboutText: "Code Crispies ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich - einfach loslegen!",
|
aboutText:
|
||||||
|
"Code Crispies ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich - einfach loslegen!",
|
||||||
learningModesTitle: "Lernmodi",
|
learningModesTitle: "Lernmodi",
|
||||||
modeCss: "<strong>CSS</strong> - Schreibe CSS-Regeln zum Stylen von Elementen",
|
modeCss: "<strong>CSS</strong> - Schreibe CSS-Regeln zum Stylen von Elementen",
|
||||||
modeTailwind: "<strong>Tailwind</strong> - Wende Utility-Klassen direkt im HTML an",
|
modeTailwind: "<strong>Tailwind</strong> - Wende Utility-Klassen direkt im HTML an",
|
||||||
modeHtml: "<strong>HTML</strong> - Übe semantisches Markup und native Elemente",
|
modeHtml: "<strong>HTML</strong> - Übe semantisches Markup und native Elemente",
|
||||||
gettingStartedTitle: "Erste Schritte",
|
gettingStartedTitle: "Erste Schritte",
|
||||||
gettingStartedText: "Öffne das Menü (☰), um Lektionsmodule zu durchsuchen. Jedes Modul behandelt ein Thema mit aufeinander aufbauenden Übungen.",
|
gettingStartedText:
|
||||||
|
"Öffne das Menü (☰), um Lektionsmodule zu durchsuchen. Jedes Modul behandelt ein Thema mit aufeinander aufbauenden Übungen.",
|
||||||
completingLessonsTitle: "Lektionen abschließen",
|
completingLessonsTitle: "Lektionen abschließen",
|
||||||
completingStep1: "Lies die Aufgabenstellung auf der linken Seite",
|
completingStep1: "Lies die Aufgabenstellung auf der linken Seite",
|
||||||
completingStep2: "Schreibe deinen Code im Editor",
|
completingStep2: "Schreibe deinen Code im Editor",
|
||||||
@@ -184,7 +193,7 @@ const translations = {
|
|||||||
|
|
||||||
// Contact
|
// Contact
|
||||||
contactTitle: "Kontakt & Links",
|
contactTitle: "Kontakt & Links",
|
||||||
contactText: "Code Crispies wird von <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> entwickelt",
|
contactText: 'Code Crispies wird von <a href="https://librete.ch" target="_blank">LibreTECH</a> entwickelt',
|
||||||
|
|
||||||
// Reset dialog
|
// Reset dialog
|
||||||
resetDialogTitle: "Fortschritt zurücksetzen",
|
resetDialogTitle: "Fortschritt zurücksetzen",
|
||||||
@@ -192,6 +201,12 @@ const translations = {
|
|||||||
cancel: "Abbrechen",
|
cancel: "Abbrechen",
|
||||||
resetAll: "Alles zurücksetzen",
|
resetAll: "Alles zurücksetzen",
|
||||||
|
|
||||||
|
// Reset code dialog
|
||||||
|
resetCodeDialogTitle: "Code zurücksetzen",
|
||||||
|
resetCodeDialogText: "Code auf den Anfangszustand dieser Lektion zurücksetzen?",
|
||||||
|
dontShowAgain: "Nicht mehr anzeigen",
|
||||||
|
reset: "Zurücksetzen",
|
||||||
|
|
||||||
// Dynamic content
|
// Dynamic content
|
||||||
loadingFallbackText: "Lektion konnte nicht geladen werden. Bitte wähle eine aus dem Menü oder prüfe die Hilfe.",
|
loadingFallbackText: "Lektion konnte nicht geladen werden. Bitte wähle eine aus dem Menü oder prüfe die Hilfe.",
|
||||||
completed: "Erledigt",
|
completed: "Erledigt",
|
||||||
@@ -250,7 +265,8 @@ const translations = {
|
|||||||
// Help dialog
|
// Help dialog
|
||||||
helpTitle: "Pomoc",
|
helpTitle: "Pomoc",
|
||||||
aboutTitle: "O Code Crispies",
|
aboutTitle: "O Code Crispies",
|
||||||
aboutText: "Code Crispies to darmowa platforma open-source do nauki tworzenia stron internetowych poprzez praktyczne ćwiczenia. Nie wymaga konta - po prostu zacznij kodować!",
|
aboutText:
|
||||||
|
"Code Crispies to darmowa platforma open-source do nauki tworzenia stron internetowych poprzez praktyczne ćwiczenia. Nie wymaga konta - po prostu zacznij kodować!",
|
||||||
learningModesTitle: "Tryby nauki",
|
learningModesTitle: "Tryby nauki",
|
||||||
modeCss: "<strong>CSS</strong> - Pisz reguły CSS do stylizowania elementów",
|
modeCss: "<strong>CSS</strong> - Pisz reguły CSS do stylizowania elementów",
|
||||||
modeTailwind: "<strong>Tailwind</strong> - Stosuj klasy utility bezpośrednio w HTML",
|
modeTailwind: "<strong>Tailwind</strong> - Stosuj klasy utility bezpośrednio w HTML",
|
||||||
@@ -285,7 +301,7 @@ const translations = {
|
|||||||
|
|
||||||
// Contact
|
// Contact
|
||||||
contactTitle: "Kontakt i linki",
|
contactTitle: "Kontakt i linki",
|
||||||
contactText: "Code Crispies jest rozwijany przez <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
contactText: 'Code Crispies jest rozwijany przez <a href="https://librete.ch" target="_blank">LibreTECH</a>',
|
||||||
|
|
||||||
// Reset dialog
|
// Reset dialog
|
||||||
resetDialogTitle: "Resetuj postęp",
|
resetDialogTitle: "Resetuj postęp",
|
||||||
@@ -293,6 +309,12 @@ const translations = {
|
|||||||
cancel: "Anuluj",
|
cancel: "Anuluj",
|
||||||
resetAll: "Resetuj wszystko",
|
resetAll: "Resetuj wszystko",
|
||||||
|
|
||||||
|
// Reset code dialog
|
||||||
|
resetCodeDialogTitle: "Resetuj kod",
|
||||||
|
resetCodeDialogText: "Przywrócić kod do stanu początkowego tej lekcji?",
|
||||||
|
dontShowAgain: "Nie pokazuj ponownie",
|
||||||
|
reset: "Resetuj",
|
||||||
|
|
||||||
// Dynamic content
|
// Dynamic content
|
||||||
loadingFallbackText: "Nie można załadować lekcji. Wybierz jedną z menu lub sprawdź pomoc.",
|
loadingFallbackText: "Nie można załadować lekcji. Wybierz jedną z menu lub sprawdź pomoc.",
|
||||||
completed: "Ukończono",
|
completed: "Ukończono",
|
||||||
@@ -351,13 +373,15 @@ const translations = {
|
|||||||
// Help dialog
|
// Help dialog
|
||||||
helpTitle: "Ayuda",
|
helpTitle: "Ayuda",
|
||||||
aboutTitle: "Acerca de Code Crispies",
|
aboutTitle: "Acerca de Code Crispies",
|
||||||
aboutText: "Code Crispies es una plataforma gratuita de código abierto para aprender desarrollo web a través de ejercicios prácticos. No se requiere cuenta, ¡solo empieza a programar!",
|
aboutText:
|
||||||
|
"Code Crispies es una plataforma gratuita de código abierto para aprender desarrollo web a través de ejercicios prácticos. No se requiere cuenta, ¡solo empieza a programar!",
|
||||||
learningModesTitle: "Modos de aprendizaje",
|
learningModesTitle: "Modos de aprendizaje",
|
||||||
modeCss: "<strong>CSS</strong> - Escribe reglas CSS para estilizar elementos",
|
modeCss: "<strong>CSS</strong> - Escribe reglas CSS para estilizar elementos",
|
||||||
modeTailwind: "<strong>Tailwind</strong> - Aplica clases de utilidad directamente en HTML",
|
modeTailwind: "<strong>Tailwind</strong> - Aplica clases de utilidad directamente en HTML",
|
||||||
modeHtml: "<strong>HTML</strong> - Practica marcado semántico y elementos nativos",
|
modeHtml: "<strong>HTML</strong> - Practica marcado semántico y elementos nativos",
|
||||||
gettingStartedTitle: "Primeros pasos",
|
gettingStartedTitle: "Primeros pasos",
|
||||||
gettingStartedText: "Abre el menú (☰) para explorar los módulos de lecciones. Cada módulo cubre un tema específico con ejercicios progresivos.",
|
gettingStartedText:
|
||||||
|
"Abre el menú (☰) para explorar los módulos de lecciones. Cada módulo cubre un tema específico con ejercicios progresivos.",
|
||||||
completingLessonsTitle: "Completar lecciones",
|
completingLessonsTitle: "Completar lecciones",
|
||||||
completingStep1: "Lee las instrucciones de la tarea a la izquierda",
|
completingStep1: "Lee las instrucciones de la tarea a la izquierda",
|
||||||
completingStep2: "Escribe tu código en el editor",
|
completingStep2: "Escribe tu código en el editor",
|
||||||
@@ -386,7 +410,7 @@ const translations = {
|
|||||||
|
|
||||||
// Contact
|
// Contact
|
||||||
contactTitle: "Contacto y enlaces",
|
contactTitle: "Contacto y enlaces",
|
||||||
contactText: "Code Crispies es desarrollado por <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
contactText: 'Code Crispies es desarrollado por <a href="https://librete.ch" target="_blank">LibreTECH</a>',
|
||||||
|
|
||||||
// Reset dialog
|
// Reset dialog
|
||||||
resetDialogTitle: "Reiniciar progreso",
|
resetDialogTitle: "Reiniciar progreso",
|
||||||
@@ -394,6 +418,12 @@ const translations = {
|
|||||||
cancel: "Cancelar",
|
cancel: "Cancelar",
|
||||||
resetAll: "Reiniciar todo",
|
resetAll: "Reiniciar todo",
|
||||||
|
|
||||||
|
// Reset code dialog
|
||||||
|
resetCodeDialogTitle: "Reiniciar código",
|
||||||
|
resetCodeDialogText: "¿Restaurar el código al estado inicial de esta lección?",
|
||||||
|
dontShowAgain: "No mostrar de nuevo",
|
||||||
|
reset: "Reiniciar",
|
||||||
|
|
||||||
// Dynamic content
|
// Dynamic content
|
||||||
loadingFallbackText: "No se pudo cargar la lección. Selecciona una del menú o consulta la ayuda.",
|
loadingFallbackText: "No se pudo cargar la lección. Selecciona una del menú o consulta la ayuda.",
|
||||||
completed: "Completado",
|
completed: "Completado",
|
||||||
@@ -487,7 +517,7 @@ const translations = {
|
|||||||
|
|
||||||
// Contact
|
// Contact
|
||||||
contactTitle: "التواصل والروابط",
|
contactTitle: "التواصل والروابط",
|
||||||
contactText: "Code Crispies تم تطويره بواسطة <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
contactText: 'Code Crispies تم تطويره بواسطة <a href="https://librete.ch" target="_blank">LibreTECH</a>',
|
||||||
|
|
||||||
// Reset dialog
|
// Reset dialog
|
||||||
resetDialogTitle: "إعادة تعيين التقدم",
|
resetDialogTitle: "إعادة تعيين التقدم",
|
||||||
@@ -495,6 +525,12 @@ const translations = {
|
|||||||
cancel: "إلغاء",
|
cancel: "إلغاء",
|
||||||
resetAll: "إعادة تعيين الكل",
|
resetAll: "إعادة تعيين الكل",
|
||||||
|
|
||||||
|
// Reset code dialog
|
||||||
|
resetCodeDialogTitle: "إعادة تعيين الكود",
|
||||||
|
resetCodeDialogText: "استعادة الكود إلى الحالة الأولية لهذا الدرس؟",
|
||||||
|
dontShowAgain: "لا تظهر هذا مرة أخرى",
|
||||||
|
reset: "إعادة تعيين",
|
||||||
|
|
||||||
// Dynamic content
|
// Dynamic content
|
||||||
loadingFallbackText: "تعذر تحميل الدرس. اختر واحدًا من القائمة أو تحقق من المساعدة.",
|
loadingFallbackText: "تعذر تحميل الدرس. اختر واحدًا من القائمة أو تحقق من المساعدة.",
|
||||||
completed: "مكتمل",
|
completed: "مكتمل",
|
||||||
@@ -553,7 +589,8 @@ const translations = {
|
|||||||
// Help dialog
|
// Help dialog
|
||||||
helpTitle: "Допомога",
|
helpTitle: "Допомога",
|
||||||
aboutTitle: "Про Code Crispies",
|
aboutTitle: "Про Code Crispies",
|
||||||
aboutText: "Code Crispies — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!",
|
aboutText:
|
||||||
|
"Code Crispies — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!",
|
||||||
learningModesTitle: "Режими навчання",
|
learningModesTitle: "Режими навчання",
|
||||||
modeCss: "<strong>CSS</strong> - Пишіть правила CSS для стилізації елементів",
|
modeCss: "<strong>CSS</strong> - Пишіть правила CSS для стилізації елементів",
|
||||||
modeTailwind: "<strong>Tailwind</strong> - Застосовуйте утилітарні класи безпосередньо в HTML",
|
modeTailwind: "<strong>Tailwind</strong> - Застосовуйте утилітарні класи безпосередньо в HTML",
|
||||||
@@ -588,7 +625,7 @@ const translations = {
|
|||||||
|
|
||||||
// Contact
|
// Contact
|
||||||
contactTitle: "Контакти та посилання",
|
contactTitle: "Контакти та посилання",
|
||||||
contactText: "Code Crispies розроблено <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
contactText: 'Code Crispies розроблено <a href="https://librete.ch" target="_blank">LibreTECH</a>',
|
||||||
|
|
||||||
// Reset dialog
|
// Reset dialog
|
||||||
resetDialogTitle: "Скинути прогрес",
|
resetDialogTitle: "Скинути прогрес",
|
||||||
@@ -596,6 +633,12 @@ const translations = {
|
|||||||
cancel: "Скасувати",
|
cancel: "Скасувати",
|
||||||
resetAll: "Скинути все",
|
resetAll: "Скинути все",
|
||||||
|
|
||||||
|
// Reset code dialog
|
||||||
|
resetCodeDialogTitle: "Скинути код",
|
||||||
|
resetCodeDialogText: "Відновити код до початкового стану цього уроку?",
|
||||||
|
dontShowAgain: "Більше не показувати",
|
||||||
|
reset: "Скинути",
|
||||||
|
|
||||||
// Dynamic content
|
// Dynamic content
|
||||||
loadingFallbackText: "Не вдалося завантажити урок. Виберіть один з меню або перевірте допомогу.",
|
loadingFallbackText: "Не вдалося завантажити урок. Виберіть один з меню або перевірте допомогу.",
|
||||||
completed: "Завершено",
|
completed: "Завершено",
|
||||||
|
|||||||
@@ -12,19 +12,22 @@ import { autocompletion } from "@codemirror/autocomplete";
|
|||||||
import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin";
|
import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin";
|
||||||
|
|
||||||
// Custom overrides for One Dark theme
|
// Custom overrides for One Dark theme
|
||||||
const editorTheme = EditorView.theme({
|
const editorTheme = EditorView.theme(
|
||||||
"&": {
|
{
|
||||||
height: "100%",
|
"&": {
|
||||||
fontSize: "14px"
|
height: "100%",
|
||||||
|
fontSize: "14px"
|
||||||
|
},
|
||||||
|
".cm-content": {
|
||||||
|
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
||||||
|
padding: "12px 0"
|
||||||
|
},
|
||||||
|
".cm-line": {
|
||||||
|
padding: "0 12px"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
".cm-content": {
|
{ dark: true }
|
||||||
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
);
|
||||||
padding: "12px 0"
|
|
||||||
},
|
|
||||||
".cm-line": {
|
|
||||||
padding: "0 12px"
|
|
||||||
}
|
|
||||||
}, { dark: true });
|
|
||||||
|
|
||||||
export class CodeEditor {
|
export class CodeEditor {
|
||||||
constructor(container, options = {}) {
|
constructor(container, options = {}) {
|
||||||
@@ -55,19 +58,16 @@ export class CodeEditor {
|
|||||||
// Emmet abbreviation tracking
|
// Emmet abbreviation tracking
|
||||||
abbreviationTracker(),
|
abbreviationTracker(),
|
||||||
// High priority keymap for Emmet
|
// High priority keymap for Emmet
|
||||||
Prec.highest(keymap.of([
|
Prec.highest(
|
||||||
{
|
keymap.of([
|
||||||
key: "Tab",
|
{
|
||||||
run: expandAbbreviation
|
key: "Tab",
|
||||||
}
|
run: expandAbbreviation
|
||||||
])),
|
}
|
||||||
|
])
|
||||||
|
),
|
||||||
// Standard keymaps including history (Ctrl+Z, Ctrl+Shift+Z)
|
// Standard keymaps including history (Ctrl+Z, Ctrl+Shift+Z)
|
||||||
keymap.of([
|
keymap.of([...historyKeymap, { key: "Tab", run: indentMore }, { key: "Shift-Tab", run: indentLess }, ...defaultKeymap]),
|
||||||
...historyKeymap,
|
|
||||||
{ key: "Tab", run: indentMore },
|
|
||||||
{ key: "Shift-Tab", run: indentLess },
|
|
||||||
...defaultKeymap
|
|
||||||
]),
|
|
||||||
autocompletion({
|
autocompletion({
|
||||||
activateOnTyping: true,
|
activateOnTyping: true,
|
||||||
maxRenderedOptions: 10
|
maxRenderedOptions: 10
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="Code Crispies - Free, open-source platform for learning HTML and CSS through hands-on exercises. Master semantic markup, selectors, flexbox, animations and more." />
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Code Crispies - Free, open-source platform for learning HTML and CSS through hands-on exercises. Master semantic markup, selectors, flexbox, animations and more."
|
||||||
|
/>
|
||||||
<title>Code Crispies - Learn HTML & CSS Interactively</title>
|
<title>Code Crispies - Learn HTML & CSS Interactively</title>
|
||||||
<link rel="stylesheet" href="main.css" />
|
<link rel="stylesheet" href="main.css" />
|
||||||
</head>
|
</head>
|
||||||
@@ -41,15 +44,20 @@
|
|||||||
<div class="editor-tools">
|
<div class="editor-tools">
|
||||||
<button id="undo-btn" class="btn btn-icon" data-i18n-title="undoTitle" title="Undo (Ctrl+Z)">↶</button>
|
<button id="undo-btn" class="btn btn-icon" data-i18n-title="undoTitle" title="Undo (Ctrl+Z)">↶</button>
|
||||||
<button id="redo-btn" class="btn btn-icon" data-i18n-title="redoTitle" title="Redo (Ctrl+Shift+Z)">↷</button>
|
<button id="redo-btn" class="btn btn-icon" data-i18n-title="redoTitle" title="Redo (Ctrl+Shift+Z)">↷</button>
|
||||||
<button id="reset-code-btn" class="btn btn-icon" data-i18n-title="resetCodeTitle" title="Reset to initial code">⟲</button>
|
<button
|
||||||
|
id="reset-code-btn"
|
||||||
|
class="btn btn-icon"
|
||||||
|
data-i18n-title="resetCodeTitle"
|
||||||
|
title="Reset to initial code"
|
||||||
|
>
|
||||||
|
⟲
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button id="run-btn" class="btn btn-run">
|
<button id="run-btn" class="btn btn-run"><img src="./gear.svg" alt="" /><span data-i18n="run">Run</span></button>
|
||||||
<img src="./gear.svg" alt="" /><span data-i18n="run">Run</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-content">
|
<div class="editor-content">
|
||||||
<textarea id="code-input" class="code-input" spellcheck="false" autocomplete="off" style="display: none;"></textarea>
|
<textarea id="code-input" class="code-input" spellcheck="false" autocomplete="off" style="display: none"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint-area" id="hint-area"></div>
|
<div class="hint-area" id="hint-area"></div>
|
||||||
@@ -142,7 +150,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<h4 data-i18n="aboutTitle">About Code Crispies</h4>
|
<h4 data-i18n="aboutTitle">About Code Crispies</h4>
|
||||||
<p data-i18n="aboutText">Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!</p>
|
<p data-i18n="aboutText">
|
||||||
|
Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required -
|
||||||
|
just start coding!
|
||||||
|
</p>
|
||||||
|
|
||||||
<h4 data-i18n="learningModesTitle">Learning Modes</h4>
|
<h4 data-i18n="learningModesTitle">Learning Modes</h4>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -152,7 +163,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4 data-i18n="gettingStartedTitle">Getting Started</h4>
|
<h4 data-i18n="gettingStartedTitle">Getting Started</h4>
|
||||||
<p data-i18n="gettingStartedText">Open the menu (☰) to browse lesson modules. Each module covers a specific topic with progressive exercises.</p>
|
<p data-i18n="gettingStartedText">
|
||||||
|
Open the menu (☰) to browse lesson modules. Each module covers a specific topic with progressive exercises.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h4 data-i18n="completingLessonsTitle">Completing Lessons</h4>
|
<h4 data-i18n="completingLessonsTitle">Completing Lessons</h4>
|
||||||
<ol>
|
<ol>
|
||||||
@@ -207,6 +220,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<!-- Reset Code Confirmation Dialog -->
|
||||||
|
<dialog id="reset-code-dialog" class="dialog">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<h3 data-i18n="resetCodeDialogTitle">Reset Code</h3>
|
||||||
|
<button id="reset-code-dialog-close" class="dialog-close" aria-label="Close">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-content">
|
||||||
|
<p data-i18n="resetCodeDialogText">Reset your code to the initial state for this lesson?</p>
|
||||||
|
<label class="toggle-switch" style="margin: 1rem 0">
|
||||||
|
<input type="checkbox" id="reset-code-dont-show" />
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
<span class="toggle-label" data-i18n="dontShowAgain">Don't show this again</span>
|
||||||
|
</label>
|
||||||
|
<div class="dialog-actions">
|
||||||
|
<button id="cancel-reset-code" class="btn" data-i18n="cancel">Cancel</button>
|
||||||
|
<button id="confirm-reset-code" class="btn btn-ghost" data-i18n="reset">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<!-- Reset Confirmation Dialog -->
|
<!-- Reset Confirmation Dialog -->
|
||||||
<dialog id="reset-dialog" class="dialog">
|
<dialog id="reset-dialog" class="dialog">
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
|
|||||||
16
src/main.css
16
src/main.css
@@ -98,7 +98,8 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
code, kbd {
|
code,
|
||||||
|
kbd {
|
||||||
font-family: var(--font-code);
|
font-family: var(--font-code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,8 +273,12 @@ code, kbd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from { opacity: 0; }
|
from {
|
||||||
to { opacity: 1; }
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-pill {
|
.module-pill {
|
||||||
@@ -656,7 +661,9 @@ code, kbd {
|
|||||||
background: rgba(0, 0, 0, 0.4);
|
background: rgba(0, 0, 0, 0.4);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition: opacity 0.3s, visibility 0.3s;
|
transition:
|
||||||
|
opacity 0.3s,
|
||||||
|
visibility 0.3s;
|
||||||
z-index: 199;
|
z-index: 199;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,7 +1061,6 @@ input:checked + .toggle-slider::before {
|
|||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ================= DIALOG (Native HTML) ================= */
|
/* ================= DIALOG (Native HTML) ================= */
|
||||||
.dialog {
|
.dialog {
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user