diff --git a/src/app.js b/src/app.js
index 8fe3c6e..e1ff4e4 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,5 +1,5 @@
import { LessonEngine } from "./impl/LessonEngine.js";
-import { renderLesson, renderModuleList, renderLevelIndicator, showFeedback } from "./helpers/renderer.js";
+import { renderLesson, renderModuleList, renderLevelIndicator, showFeedback, updateActiveLessonInSidebar } from "./helpers/renderer.js";
import { validateUserCode } from "./helpers/validator.js";
import { loadModules } from "./config/lessons.js";
@@ -89,7 +89,9 @@ function initFeedbackToggle() {
async function initializeModules() {
try {
state.modules = await loadModules();
- renderModuleList(elements.moduleList, state.modules, selectModule);
+
+ // Use the new renderModuleList function with both callbacks
+ renderModuleList(elements.moduleList, state.modules, selectModule, selectLesson);
// Select the first module or the last one user was on
const lastModuleId = localStorage.getItem("codeCrispies.lastModuleId");
@@ -158,8 +160,8 @@ function selectModule(moduleId) {
state.currentModule = selectedModule;
- // Update module list UI
- const moduleItems = elements.moduleList.querySelectorAll(".module-list-item");
+ // Update module list UI to highlight the active module
+ const moduleItems = elements.moduleList.querySelectorAll(".module-header");
moduleItems.forEach((item) => {
item.classList.remove("active");
if (item.dataset.moduleId === moduleId) {
@@ -182,6 +184,23 @@ function selectModule(moduleId) {
resetSuccessIndicators();
}
+function selectLesson(moduleId, lessonIndex) {
+ // Select the module first if it's not already selected
+ if (!state.currentModule || state.currentModule.id !== moduleId) {
+ selectModule(moduleId);
+ }
+
+ // Update current lesson index
+ state.currentLessonIndex = lessonIndex;
+
+ // Update user progress
+ state.userProgress[moduleId].current = lessonIndex;
+ saveUserProgress();
+
+ // Load the lesson
+ loadCurrentLesson();
+}
+
// Reset success indicators
function resetSuccessIndicators() {
elements.codeEditor.classList.remove("success-highlight");
@@ -255,6 +274,9 @@ function loadCurrentLesson() {
// Update level indicator
renderLevelIndicator(elements.levelIndicator, state.currentLessonIndex + 1, state.currentModule.lessons.length);
+ // Update active lesson in sidebar
+ updateActiveLessonInSidebar(state.currentModule.id, state.currentLessonIndex);
+
// Update navigation buttons
updateNavigationButtons();
diff --git a/src/helpers/renderer.js b/src/helpers/renderer.js
index 707a370..c24ff08 100644
--- a/src/helpers/renderer.js
+++ b/src/helpers/renderer.js
@@ -7,40 +7,110 @@ let feedbackElement = null;
let feedbackTimeout = null;
/**
- * Render the module list in the sidebar
+ * Render the module list in the sidebar with expandable lessons
* @param {HTMLElement} container - The container element for the module list
* @param {Array} modules - The list of modules
* @param {Function} onSelectModule - Callback when a module is selected
+ * @param {Function} onSelectLesson - Callback when a lesson is selected
*/
-export function renderModuleList(container, modules, onSelectModule) {
+export function renderModuleList(container, modules, onSelectModule, onSelectLesson) {
// Clear the container
container.innerHTML = "
CSS Lessons
";
+ // Get user progress from localStorage
+ const progressData = localStorage.getItem("codeCrispies.Progress");
+ let progress = {};
+ if (progressData) {
+ try {
+ progress = JSON.parse(progressData);
+ } catch (e) {
+ console.error("Error parsing progress data:", e);
+ }
+ }
+
// Create list items for each module
modules.forEach((module) => {
- const moduleItem = document.createElement("div");
- moduleItem.classList.add("module-list-item");
- moduleItem.dataset.moduleId = module.id;
- moduleItem.textContent = module.title;
+ // Create module container
+ const moduleContainer = document.createElement("div");
+ moduleContainer.classList.add("module-container");
- // Get user progress from localStorage to mark completed lessons
- const progressData = localStorage.getItem("codeCrispies.Progress");
- if (progressData) {
- try {
- const progress = JSON.parse(progressData);
- if (progress[module.id] && progress[module.id].completed.length === module.lessons.length) {
- moduleItem.classList.add("completed");
- }
- } catch (e) {
- console.error("Error parsing progress data:", e);
- }
+ // Create module header item (clickable to expand/collapse)
+ const moduleHeader = document.createElement("div");
+ moduleHeader.classList.add("module-list-item", "module-header");
+ moduleHeader.dataset.moduleId = module.id;
+
+ // Create module title with expand/collapse indicator
+ const moduleTitle = document.createElement("span");
+ moduleTitle.classList.add("module-title");
+ moduleTitle.textContent = module.title;
+
+ // Create expand/collapse icon
+ const expandIcon = document.createElement("span");
+ expandIcon.classList.add("expand-icon");
+ expandIcon.innerHTML = "▶"; // Right-pointing triangle
+
+ moduleHeader.appendChild(expandIcon);
+ moduleHeader.appendChild(moduleTitle);
+
+ // Check if the module is completed
+ if (progress[module.id] && progress[module.id].completed.length === module.lessons.length) {
+ moduleHeader.classList.add("completed");
}
- moduleItem.addEventListener("click", () => {
- onSelectModule(module.id);
+ // Lessons container (initially hidden)
+ const lessonsContainer = document.createElement("div");
+ lessonsContainer.classList.add("lessons-container");
+ lessonsContainer.style.display = "none"; // Initially collapsed
+
+ // Create list items for each lesson in this module
+ module.lessons.forEach((lesson, index) => {
+ const lessonItem = document.createElement("div");
+ lessonItem.classList.add("lesson-list-item");
+ lessonItem.dataset.moduleId = module.id;
+ lessonItem.dataset.lessonIndex = index;
+ lessonItem.textContent = lesson.title || `Lesson ${index + 1}`;
+
+ // Mark lesson as completed if in progress data
+ if (progress[module.id] && progress[module.id].completed.includes(index)) {
+ lessonItem.classList.add("completed");
+ }
+
+ // Mark lesson as current if it's the current lesson
+ if (progress[module.id] && progress[module.id].current === index) {
+ lessonItem.classList.add("current");
+ }
+
+ // Add click event to select lesson
+ lessonItem.addEventListener("click", () => {
+ // Update UI to show this lesson is selected
+ document.querySelectorAll(".lesson-list-item").forEach((item) => {
+ item.classList.remove("active");
+ });
+ lessonItem.classList.add("active");
+
+ // Call the onSelectLesson callback
+ onSelectLesson(module.id, index);
+ });
+
+ lessonsContainer.appendChild(lessonItem);
});
- container.appendChild(moduleItem);
+ // Toggle expand/collapse when clicking on module header
+ moduleHeader.addEventListener("click", () => {
+ // Toggle visibility of lessons container
+ const isExpanded = lessonsContainer.style.display !== "none";
+ lessonsContainer.style.display = isExpanded ? "none" : "block";
+
+ // Update expand/collapse icon
+ expandIcon.innerHTML = isExpanded ? "▶" : "▼";
+ });
+
+ // Add module header and lessons container to module container
+ moduleContainer.appendChild(moduleHeader);
+ moduleContainer.appendChild(lessonsContainer);
+
+ // Add the complete module container to the sidebar
+ container.appendChild(moduleContainer);
});
}
@@ -132,3 +202,41 @@ export function clearFeedback() {
}
feedbackElement = null;
}
+
+/**
+ * Update the active lesson in the sidebar
+ * @param {string} moduleId - The ID of the module
+ * @param {number} lessonIndex - The index of the lesson
+ */
+export function updateActiveLessonInSidebar(moduleId, lessonIndex) {
+ // Remove active class from all lessons
+ document.querySelectorAll(".lesson-list-item").forEach((item) => {
+ item.classList.remove("active");
+ });
+
+ // Find and activate the current lesson
+ const selector = `.lesson-list-item[data-module-id="${moduleId}"][data-lesson-index="${lessonIndex}"]`;
+ const currentLessonItem = document.querySelector(selector);
+
+ if (currentLessonItem) {
+ currentLessonItem.classList.add("active");
+
+ // Make sure parent module is expanded
+ const parentLessonsContainer = currentLessonItem.parentElement;
+ if (parentLessonsContainer && parentLessonsContainer.classList.contains("lessons-container")) {
+ parentLessonsContainer.style.display = "block";
+
+ // Update expand icon
+ const moduleHeader = parentLessonsContainer.previousElementSibling;
+ if (moduleHeader) {
+ const expandIcon = moduleHeader.querySelector(".expand-icon");
+ if (expandIcon) {
+ expandIcon.innerHTML = "▼"; // Down arrow when expanded
+ }
+ }
+
+ // Scroll to ensure the item is visible
+ currentLessonItem.scrollIntoView({ behavior: "smooth", block: "nearest" });
+ }
+ }
+}
diff --git a/src/main.css b/src/main.css
index 53791f4..36f65e3 100644
--- a/src/main.css
+++ b/src/main.css
@@ -794,6 +794,93 @@ input:checked + .toggle-slider:before {
}
}
+/* Module and Lesson List Styles */
+.module-container {
+ margin-bottom: 8px;
+}
+
+.module-header {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ padding: 8px 12px;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+ font-weight: 600;
+}
+
+.module-header:hover {
+ background-color: var(--hover-color);
+}
+
+.expand-icon {
+ display: inline-block;
+ margin-right: 8px;
+ font-size: 10px;
+ transition: transform 0.2s;
+}
+
+.lessons-container {
+ margin-left: 16px;
+ border-left: 2px solid var(--border-color);
+ padding-left: 8px;
+}
+
+.lesson-list-item {
+ padding: 6px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ font-size: 0.9em;
+ margin: 4px 0;
+}
+
+.lesson-list-item:hover {
+ background-color: var(--primary-bg-medium);
+}
+
+.lesson-list-item.active {
+ background-color: var(--primary-bg-medium);
+ color: var(--dark-text);
+ font-weight: bold;
+}
+
+.lesson-list-item.completed::before {
+ content: "✓";
+ margin-right: 6px;
+ color: var(--success-color);
+}
+
+.module-header.completed::before {
+ content: "✓";
+ margin-right: 6px;
+ color: var(--success-color);
+}
+
+/* Improve scrolling for the sidebar */
+.sidebar .module-list {
+ max-height: calc(100vh - 200px);
+ padding-right: 5px;
+}
+
+/* Add smooth scrolling */
+.sidebar {
+ scroll-behavior: smooth;
+}
+
+/* Mobile adjustments */
+@media (max-width: 768px) {
+ .lessons-container {
+ margin-left: 8px;
+ padding-left: 6px;
+ }
+
+ .lesson-list-item,
+ .module-header {
+ padding: 8px 6px;
+ }
+}
+
/* ================= RESPONSIVE DESIGN ================= */
/* Base responsive layout */