diff --git a/lessons/50-js-variables.json b/lessons/50-js-variables.json new file mode 100644 index 0000000..937701c --- /dev/null +++ b/lessons/50-js-variables.json @@ -0,0 +1,139 @@ +{ + "$schema": "../schemas/code-crispies-module-schema.json", + "id": "js-variables", + "title": "JS Variables", + "description": "Learn to declare variables with let and const, and work with basic data types in JavaScript.", + "mode": "javascript", + "difficulty": "beginner", + "lessons": [ + { + "id": "js-const", + "title": "Constants", + "description": "Use const to declare a variable that cannot be reassigned. Constants are the default choice for most values in modern JavaScript.", + "task": "Declare a constant named name with the value \"Alice\"", + "previewHTML": "
Waiting...
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "", + "codeSuffix": "\ndocument.getElementById('out').textContent = name;", + "solution": "const name = \"Alice\";", + "previewContainer": "preview-area", + "validations": [ + { + "type": "contains", + "value": "const", + "message": "Use const to declare the variable" + }, + { + "type": "regex", + "value": "const\\s+name\\s*=", + "message": "Declare a constant called name" + }, + { + "type": "regex", + "value": "\"Alice\"|'Alice'|`Alice`", + "message": "Set the value to \"Alice\"" + } + ] + }, + { + "id": "js-let", + "title": "Let Variables", + "description": "Use let to declare variables that you plan to reassign later. Unlike const, a let variable can change its value.", + "task": "Declare a variable count with let set to 0, then reassign it to 5", + "previewHTML": "Waiting...
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "", + "codeSuffix": "\ndocument.getElementById('out').textContent = count;", + "solution": "let count = 0;\ncount = 5;", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "let\\s+count\\s*=\\s*0", + "message": "Start with let count = 0;" + }, + { + "type": "regex", + "value": "count\\s*=\\s*5", + "message": "Reassign count to 5" + } + ] + }, + { + "id": "js-string", + "title": "Template Literals", + "description": "Template literals use backticks ` and ${} to embed expressions inside strings. This makes building dynamic text much easier than string concatenation.", + "task": "Create a constant msg using a template literal: `Hello, ${name}!`", + "previewHTML": "Waiting...
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "const name = \"World\";\n", + "codeSuffix": "\ndocument.getElementById('out').textContent = msg;", + "solution": "const msg = `Hello, ${name}!`;", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "const\\s+msg\\s*=", + "message": "Declare a constant called msg" + }, + { + "type": "contains", + "value": "${name}", + "message": "Use ${name} inside backticks to embed the variable" + }, + { + "type": "regex", + "value": "`.*\\$\\{name\\}.*`", + "message": "Wrap the whole string in backticks `" + } + ] + }, + { + "id": "js-array", + "title": "Arrays", + "description": "Arrays store ordered lists of values in square brackets. Access items by index (starting at 0) and use .length to get the count.", + "task": "Create a constant colors with an array: [\"red\", \"green\", \"blue\"]", + "previewHTML": "Waiting...
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "", + "codeSuffix": "\ndocument.getElementById('out').textContent = colors.join(', ');", + "solution": "const colors = [\"red\", \"green\", \"blue\"];", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "const\\s+colors\\s*=", + "message": "Declare a constant called colors" + }, + { + "type": "contains", + "value": "[", + "message": "Use square brackets [ to create an array" + }, + { + "type": "regex", + "value": "(\"red\"|'red'|`red`)", + "message": "Include \"red\" in the array" + }, + { + "type": "regex", + "value": "(\"green\"|'green'|`green`)", + "message": "Include \"green\" in the array" + }, + { + "type": "regex", + "value": "(\"blue\"|'blue'|`blue`)", + "message": "Include \"blue\" in the array" + } + ] + } + ] +} diff --git a/lessons/51-js-dom.json b/lessons/51-js-dom.json new file mode 100644 index 0000000..60f3c27 --- /dev/null +++ b/lessons/51-js-dom.json @@ -0,0 +1,139 @@ +{ + "$schema": "../schemas/code-crispies-module-schema.json", + "id": "js-dom", + "title": "JS DOM", + "description": "Learn to select and modify HTML elements using JavaScript DOM methods like querySelector and textContent.", + "mode": "javascript", + "difficulty": "beginner", + "lessons": [ + { + "id": "js-query", + "title": "querySelector", + "description": "Use document.querySelector() to find the first element matching a CSS selector. It returns a single element you can then modify.", + "task": "Select the h1 element and store it in a constant called title", + "previewHTML": "Waiting...
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "", + "codeSuffix": "\ndocument.getElementById('out').textContent = title.tagName;", + "solution": "const title = document.querySelector('h1');", + "previewContainer": "preview-area", + "validations": [ + { + "type": "contains", + "value": "querySelector", + "message": "Use document.querySelector() to select an element" + }, + { + "type": "regex", + "value": "querySelector\\(['\"`]h1['\"`]\\)", + "message": "Pass 'h1' as the selector" + }, + { + "type": "regex", + "value": "const\\s+title\\s*=", + "message": "Store the result in a constant called title" + } + ] + }, + { + "id": "js-text", + "title": "textContent", + "description": "The textContent property lets you read or change the text inside an element. Setting it replaces all existing text.", + "task": "Select the .msg element and set its textContent to \"Done!\"", + "previewHTML": "Waiting...
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "", + "codeSuffix": "", + "solution": "document.querySelector('.msg').textContent = \"Done!\";", + "previewContainer": "preview-area", + "validations": [ + { + "type": "contains", + "value": "querySelector", + "message": "Use querySelector to find the element" + }, + { + "type": "contains", + "value": "textContent", + "message": "Use the textContent property to change the text" + }, + { + "type": "regex", + "value": "(\"Done!\"|'Done!'|`Done!`)", + "message": "Set the text to \"Done!\"" + } + ] + }, + { + "id": "js-style", + "title": "Inline Styles", + "description": "Access the style property to set inline CSS on an element. CSS properties with dashes become camelCase: background-color becomes backgroundColor.", + "task": "Select the .box element and set its style.color to \"coral\"", + "previewHTML": "Style me!
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { font-size: 1.5rem; font-weight: bold; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "", + "codeSuffix": "", + "solution": "document.querySelector('.box').style.color = \"coral\";", + "previewContainer": "preview-area", + "validations": [ + { + "type": "contains", + "value": "querySelector", + "message": "Use querySelector to find the element" + }, + { + "type": "contains", + "value": ".style.", + "message": "Use the .style property to set CSS" + }, + { + "type": "regex", + "value": "style\\.color\\s*=", + "message": "Set style.color on the element" + }, + { + "type": "regex", + "value": "(\"coral\"|'coral'|`coral`)", + "message": "Set the color to \"coral\"" + } + ] + }, + { + "id": "js-classlist", + "title": "classList", + "description": "The classList property provides methods to add, remove, or toggle CSS classes on an element without touching other classes.", + "task": "Select the .card element and add the class \"active\" using classList.add()", + "previewHTML": "Waiting...
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { padding: 0.5rem 1rem; border: none; background: steelblue; color: white; border-radius: 4px; cursor: pointer; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "const btn = document.querySelector('.btn');\nconst msg = document.querySelector('.msg');\n\n", + "codeSuffix": "", + "solution": "btn.addEventListener('click', () => {\n msg.textContent = \"Clicked!\";\n});", + "previewContainer": "preview-area", + "validations": [ + { + "type": "contains", + "value": "addEventListener", + "message": "Use addEventListener to listen for events" + }, + { + "type": "regex", + "value": "addEventListener\\(['\"`]click['\"`]", + "message": "Listen for the 'click' event" + }, + { + "type": "contains", + "value": "textContent", + "message": "Use textContent to update the text" + }, + { + "type": "regex", + "value": "(\"Clicked!\"|'Clicked!'|`Clicked!`)", + "message": "Set the text to \"Clicked!\"" + } + ] + }, + { + "id": "js-toggle", + "title": "Toggle Classes", + "description": "Combine events with classList.toggle() to switch a class on and off. Each click adds the class if missing, or removes it if present.", + "task": "Add a click listener to .btn that toggles the class \"on\" on .lamp", + "previewHTML": "Echo:
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .field { padding: 0.5rem; border: 2px solid #ccc; border-radius: 4px; font-size: 1rem; width: 100%; box-sizing: border-box; }", + "sandboxCSS": "", + "initialCode": "", + "codePrefix": "const field = document.querySelector('.field');\nconst out = document.querySelector('.out');\n\n", + "codeSuffix": "", + "solution": "field.addEventListener('input', (event) => {\n out.textContent = event.target.value;\n});", + "previewContainer": "preview-area", + "validations": [ + { + "type": "contains", + "value": "addEventListener", + "message": "Use addEventListener to listen for events" + }, + { + "type": "regex", + "value": "addEventListener\\(['\"`]input['\"`]", + "message": "Listen for the 'input' event" + }, + { + "type": "contains", + "value": "textContent", + "message": "Use textContent to update the output" + }, + { + "type": "regex", + "value": "(event|e|evt)\\.target\\.value", + "message": "Read the input value with event.target.value" + } + ] + } + ] +} diff --git a/package-lock.json b/package-lock.json index 510a9c5..df8012b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@codemirror/commands": "^6.10.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lang-markdown": "^6.5.0", "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.3", @@ -208,9 +209,9 @@ } }, "node_modules/@codemirror/lang-javascript": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", - "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", diff --git a/package.json b/package.json index 9386db1..a644707 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@codemirror/commands": "^6.10.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lang-markdown": "^6.5.0", "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.3", diff --git a/schemas/code-crispies-module-schema.json b/schemas/code-crispies-module-schema.json index 2f2fc5b..8df3094 100644 --- a/schemas/code-crispies-module-schema.json +++ b/schemas/code-crispies-module-schema.json @@ -19,8 +19,8 @@ }, "mode": { "type": "string", - "enum": ["css", "tailwind", "html", "markdown"], - "description": "Whether this module teaches CSS, Tailwind, HTML, or Markdown" + "enum": ["css", "tailwind", "html", "markdown", "javascript"], + "description": "Whether this module teaches CSS, Tailwind, HTML, Markdown, or JavaScript" }, "difficulty": { "type": "string", @@ -60,7 +60,7 @@ }, "mode": { "type": "string", - "enum": ["css", "tailwind", "html", "markdown"], + "enum": ["css", "tailwind", "html", "markdown", "javascript"], "description": "Override module mode for individual lessons" }, "tailwindConfig": { diff --git a/src/app.js b/src/app.js index e069860..bb87e6b 100644 --- a/src/app.js +++ b/src/app.js @@ -578,6 +578,11 @@ function updateEditorForMode(mode) { label: "Markdown Editor", cmMode: "markdown" }, + javascript: { + placeholder: "// Write your JavaScript here...", + label: "JavaScript Editor", + cmMode: "javascript" + }, playground: { placeholder: "\n\n", label: "HTML & CSS", @@ -1493,6 +1498,64 @@ This is \`inline code\`. + `, + javascript: ` +JavaScript is the programming language of the web. It adds interactivity to HTML pages—responding to clicks, updating content dynamically, validating forms, and much more. Every modern browser includes a JavaScript engine, making it the most widely deployed programming language in the world.
+These beginner lessons cover the fundamentals: declaring variables, selecting and modifying DOM elements, and handling user events. Each concept builds on the previous one, giving you the tools to make any web page interactive.
+JavaScript uses const for values that won't change and let for values that will. Template literals with backticks make it easy to embed expressions in strings using \${...} syntax.
Arrays store ordered collections in square brackets. Objects store key-value pairs in curly braces. These are the building blocks of every JavaScript program.
+ Learn JS Variables +const name = "Alice";
+let count = 0;
+count = count + 1;
+
+const msg = \`Hello, \${name}!\`;
+const colors = ["red", "green"];
+ The DOM (Document Object Model) is how JavaScript sees your HTML. Use document.querySelector() to find elements by CSS selector, then modify them with properties like textContent, style, and classList.
const title = document.querySelector('h1');
+title.textContent = "New Title";
+title.style.color = "coral";
+title.classList.add("active");
+ Events let your code respond to user actions. Use addEventListener() to run a function when something happens—a click, a keystroke, or an input change. The callback receives an event object with details about what happened.
const btn = document.querySelector('.btn');
+
+btn.addEventListener('click', () => {
+ alert('Clicked!');
+});
+