refactor: reorganize project structure and update import paths

This commit is contained in:
Michael Czechowski
2025-05-13 20:29:59 +02:00
parent 26ac9c57f2
commit 94bd7ba0cb
12 changed files with 22 additions and 36 deletions

119
src/helpers/renderer.js Normal file
View File

@@ -0,0 +1,119 @@
/**
* Renderer - Handles UI updates for the CSS learning platform
*/
// Feedback elements cache
let feedbackElement = null;
/**
* Render the module list in the sidebar
* @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
*/
export function renderModuleList(container, modules, onSelectModule) {
// Clear the container
container.innerHTML = '<h3>Modules</h3>';
// 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;
moduleItem.addEventListener('click', () => {
onSelectModule(module.id);
});
container.appendChild(moduleItem);
});
}
/**
* Render a lesson in the UI
* @param {HTMLElement} titleEl - The lesson title element
* @param {HTMLElement} descriptionEl - The lesson description element
* @param {HTMLElement} taskEl - The task instruction element
* @param {HTMLElement} previewEl - The preview area element
* @param {HTMLElement} prefixEl - The code editor prefix element
* @param {HTMLElement} inputEl - The code input element
* @param {HTMLElement} suffixEl - The code editor suffix element
* @param {Object} lesson - The lesson object
*/
export function renderLesson(
titleEl,
descriptionEl,
taskEl,
previewEl,
prefixEl,
inputEl,
suffixEl,
lesson
) {
// Set lesson title and description
titleEl.textContent = lesson.title || 'Untitled Lesson';
descriptionEl.innerHTML = lesson.description || '';
// Set task instructions
taskEl.innerHTML = lesson.task || '';
// Set code editor contents
prefixEl.textContent = lesson.codePrefix || '';
inputEl.value = lesson.initialCode || '';
suffixEl.textContent = lesson.codeSuffix || '';
// Clear any existing feedback
clearFeedback();
// Initial preview render with empty user code
// The LessonEngine will handle this when it's first set
}
/**
* Update the level indicator
* @param {HTMLElement} element - The level indicator element
* @param {number} current - The current level number
* @param {number} total - The total number of levels
*/
export function renderLevelIndicator(element, current, total) {
element.textContent = `Lesson ${current} of ${total}`;
}
/**
* Show feedback for user submissions
* @param {boolean} isSuccess - Whether the submission was successful
* @param {string} message - The feedback message
*/
export function showFeedback(isSuccess, message) {
// Clear any existing feedback
clearFeedback();
// Create feedback element
feedbackElement = document.createElement('div');
feedbackElement.classList.add(isSuccess ? 'feedback-success' : 'feedback-error');
feedbackElement.textContent = message;
// Find where to insert the feedback
const insertAfter = document.querySelector('.code-editor');
if (insertAfter && insertAfter.parentNode) {
insertAfter.parentNode.insertBefore(feedbackElement, insertAfter.nextSibling);
}
// Auto-remove feedback after some time if successful
if (isSuccess) {
setTimeout(() => {
clearFeedback();
}, 5000);
}
}
/**
* Clear any existing feedback
*/
export function clearFeedback() {
if (feedbackElement && feedbackElement.parentNode) {
feedbackElement.parentNode.removeChild(feedbackElement);
}
feedbackElement = null;
}

183
src/helpers/validator.js Normal file
View File

@@ -0,0 +1,183 @@
/**
* Validator - Functions to validate user CSS code
*/
/**
* Validate user CSS code against the lesson requirements
* @param {string} userCode - User submitted CSS code
* @param {Object} lesson - The current lesson object
* @returns {Object} Validation result with isValid and message properties
*/
export function validateUserCode(userCode, lesson) {
if (!lesson || !lesson.validations) {
return { isValid: true, message: 'No validations specified for this lesson.' };
}
// Get the validations array from the lesson
const validations = lesson.validations;
// Default validation result
let result = {
isValid: true,
message: 'Your code looks good!'
};
// Process each validation rule
for (const validation of validations) {
const { type, value, message, options } = validation;
switch (type) {
case 'contains':
if (!containsValidation(userCode, value, options)) {
return { isValid: false, message: message || `Your code should include "${value}".` };
}
break;
case 'not_contains':
if (containsValidation(userCode, value, options)) {
return { isValid: false, message: message || `Your code should not include "${value}".` };
}
break;
case 'regex':
if (!regexValidation(userCode, value, options)) {
return { isValid: false, message: message || 'Your code does not match the expected pattern.' };
}
break;
case 'property_value':
if (!propertyValueValidation(userCode, value, options)) {
return { isValid: false, message: message || `The "${value.property}" property should be set to "${value.expected}".` };
}
break;
case 'syntax':
const syntaxResult = syntaxValidation(userCode);
if (!syntaxResult.isValid) {
return { isValid: false, message: message || `CSS syntax error: ${syntaxResult.error}` };
}
break;
case 'custom':
if (validation.validator && typeof validation.validator === 'function') {
const customResult = validation.validator(userCode);
if (!customResult.isValid) {
return { isValid: false, message: customResult.message || message || 'Your code does not meet the requirements.' };
}
}
break;
// Add more validation types as needed
default:
console.warn(`Unknown validation type: ${type}`);
}
}
// If we've passed all validations, return success
return result;
}
/**
* Check if code contains a specific string or pattern
* @param {string} code - User CSS code
* @param {string} value - String to check for
* @param {Object} options - Validation options
* @returns {boolean} Whether the validation passes
*/
function containsValidation(code, value, options = {}) {
const { caseSensitive = true, wholeWord = false } = options;
if (!caseSensitive) {
code = code.toLowerCase();
value = value.toLowerCase();
}
if (wholeWord) {
const regex = new RegExp(`\\b${escapeRegExp(value)}\\b`, caseSensitive ? '' : 'i');
return regex.test(code);
}
return code.includes(value);
}
/**
* Check if code matches a regex pattern
* @param {string} code - User CSS code
* @param {string} pattern - Regex pattern to check
* @param {Object} options - Validation options
* @returns {boolean} Whether the validation passes
*/
function regexValidation(code, pattern, options = {}) {
const { caseSensitive = true, multiline = true } = options;
let flags = '';
if (!caseSensitive) flags += 'i';
if (multiline) flags += 'm';
try {
const regex = new RegExp(pattern, flags);
return regex.test(code);
} catch (e) {
console.error('Invalid regex in validation:', e);
return false;
}
}
/**
* Check if a CSS property has the expected value
* @param {string} code - User CSS code
* @param {Object} value - Object with property and expected value
* @param {Object} options - Validation options
* @returns {boolean} Whether the validation passes
*/
function propertyValueValidation(code, value, options = {}) {
const { property, expected } = value;
const { exact = false } = options;
// Create a regex to extract the property value
// This is a simplified version and might not handle all CSS syntax nuances
const propertyRegex = new RegExp(`${escapeRegExp(property)}\\s*:\\s*([^;\\}]+)`, 'i');
const match = code.match(propertyRegex);
if (!match) {
// Property not found
return false;
}
const actualValue = match[1].trim();
if (exact) {
return actualValue === expected;
} else {
// Allow for flexible matching
return actualValue.toLowerCase().includes(expected.toLowerCase());
}
}
/**
* Validate CSS syntax
* @param {string} code - User CSS code
* @returns {Object} Validation result
*/
function syntaxValidation(code) {
try {
// Create a hidden style element to test the CSS
const style = document.createElement('style');
style.textContent = code;
document.head.appendChild(style);
document.head.removeChild(style);
return { isValid: true };
} catch (e) {
return { isValid: false, error: e.message };
}
}
/**
* Escape string for safe use in regex
* @param {string} string - String to escape
* @returns {string} Escaped string
*/
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}