diff --git a/lessons/40-markdown-basics.json b/lessons/40-markdown-basics.json
new file mode 100644
index 0000000..9223b95
--- /dev/null
+++ b/lessons/40-markdown-basics.json
@@ -0,0 +1,197 @@
+{
+ "$schema": "../schemas/code-crispies-module-schema.json",
+ "id": "markdown-basics",
+ "title": "Markdown Basics",
+ "description": "Learn to format text documents with Markdown, a simple and readable markup language used everywhere from GitHub to note-taking apps.",
+ "mode": "markdown",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "md-headings",
+ "title": "Headings",
+ "description": "Markdown uses hash symbols # to create headings. One # creates the largest heading (h1), two ## creates a smaller heading (h2), and so on up to six levels.
# Main Title\n## Section\n### Subsection", + "task": "Create a main heading by typing # Hello", + "previewHTML": "", + "previewBaseCSS": "", + "sandboxCSS": "", + "initialCode": "", + "solution": "# Hello", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "^#\\s+.+", + "message": "Start with # followed by a space and your heading text" + }, + { + "type": "contains", + "value": "Hello", + "message": "Your heading should contain Hello" + } + ] + }, + { + "id": "md-heading-levels", + "title": "Heading Levels", + "description": "Use more # symbols for smaller headings. ## creates an h2, ### an h3. This creates a clear document structure with visual hierarchy.", + "task": "Create an h2 heading with ## About followed by an h3 heading with ### Details", + "previewHTML": "", + "previewBaseCSS": "", + "sandboxCSS": "", + "initialCode": "", + "solution": "## About\n\n### Details", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "^##\\s+About", + "message": "Start with ## About" + }, + { + "type": "regex", + "value": "###\\s+Details", + "message": "Add ### Details for the h3 heading" + } + ] + }, + { + "id": "md-bold", + "title": "Bold Text", + "description": "Wrap text in double asterisks ** or double underscores __ to make it bold. This emphasizes important words or phrases.", + "task": "Make the word important bold by wrapping it with **", + "previewHTML": "", + "previewBaseCSS": "", + "sandboxCSS": "", + "initialCode": "This is important text.", + "solution": "This is **important** text.", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "\\*\\*important\\*\\*", + "message": "Wrap important with double asterisks: **important**" + } + ] + }, + { + "id": "md-italic", + "title": "Italic Text", + "description": "Wrap text in single asterisks * or single underscores _ to make it italic. Use this for subtle emphasis or titles of works.", + "task": "Make the word elegant italic by wrapping it with *", + "previewHTML": "", + "previewBaseCSS": "", + "sandboxCSS": "", + "initialCode": "A simple and elegant solution.", + "solution": "A simple and *elegant* solution.", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "\\*elegant\\*", + "message": "Wrap elegant with single asterisks: *elegant*" + }, + { + "type": "not_contains", + "value": "**elegant**", + "message": "Use single asterisks for italic, not double" + } + ] + }, + { + "id": "md-unordered-list", + "title": "Bullet Lists", + "description": "Create bullet lists using -, *, or + at the start of each line. Each item goes on its own line.", + "task": "Create a bullet list with three items: Apple, Banana, Cherry", + "previewHTML": "", + "previewBaseCSS": "", + "sandboxCSS": "", + "initialCode": "", + "solution": "- Apple\n- Banana\n- Cherry", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "^[-*+]\\s+Apple", + "message": "Start with a dash, asterisk, or plus followed by Apple" + }, + { + "type": "regex", + "value": "[-*+]\\s+Banana", + "message": "Add Banana as a list item" + }, + { + "type": "regex", + "value": "[-*+]\\s+Cherry", + "message": "Add Cherry as a list item" + } + ] + }, + { + "id": "md-ordered-list", + "title": "Numbered Lists", + "description": "Create numbered lists by starting lines with 1., 2., etc. Markdown automatically numbers them in sequence.", + "task": "Create a numbered list: Wake up, Eat breakfast, Start coding", + "previewHTML": "", + "previewBaseCSS": "", + "sandboxCSS": "", + "initialCode": "", + "solution": "1. Wake up\n2. Eat breakfast\n3. Start coding", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "\\d+\\.\\s+Wake up", + "message": "Start with a number and period: 1. Wake up" + }, + { + "type": "regex", + "value": "\\d+\\.\\s+Eat breakfast", + "message": "Add Eat breakfast as a numbered item" + }, + { + "type": "regex", + "value": "\\d+\\.\\s+Start coding", + "message": "Add Start coding as a numbered item" + } + ] + }, + { + "id": "md-links", + "title": "Links", + "description": "Create links with [text](url). The text in brackets is what readers see; the URL in parentheses is where they go when clicked.", + "task": "Create a link that shows Google and goes to https://google.com", + "previewHTML": "", + "previewBaseCSS": "", + "sandboxCSS": "", + "initialCode": "", + "solution": "[Google](https://google.com)", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "\\[Google\\]\\(https?://google\\.com\\)", + "message": "Use the format [Google](https://google.com)" + } + ] + }, + { + "id": "md-inline-code", + "title": "Inline Code", + "description": "Wrap text in backticks ` to format it as code. This is useful for variable names, commands, or short code snippets in your text.", + "task": "Format npm install as inline code using backticks", + "previewHTML": "", + "previewBaseCSS": "", + "sandboxCSS": "", + "initialCode": "Run npm install to install dependencies.", + "solution": "Run `npm install` to install dependencies.", + "previewContainer": "preview-area", + "validations": [ + { + "type": "regex", + "value": "`npm install`", + "message": "Wrap npm install with backticks: `npm install`" + } + ] + } + ] +} diff --git a/package-lock.json b/package-lock.json index 3492660..510a9c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,12 +13,14 @@ "@codemirror/commands": "^6.10.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-markdown": "^6.5.0", "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.39.4", "@emmetio/codemirror6-plugin": "^0.4.0", "@supabase/supabase-js": "^2.90.1", "codemirror": "^6.0.2", + "marked": "^17.0.1", "whatwg-fetch": "^3.6.20" }, "devDependencies": { @@ -156,7 +158,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -169,7 +170,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.1.tgz", "integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", @@ -182,7 +182,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", @@ -196,7 +195,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", @@ -224,12 +222,26 @@ "@lezer/javascript": "^1.0.0" } }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -266,7 +278,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", "license": "MIT", - "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } @@ -288,7 +299,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz", "integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -384,7 +394,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -408,7 +417,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -974,9 +982,9 @@ } }, "node_modules/@lezer/common": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz", - "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz", + "integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==", "license": "MIT" }, "node_modules/@lezer/css": { @@ -1030,6 +1038,16 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/markdown": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz", + "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", @@ -2336,7 +2354,6 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -2433,6 +2450,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -2579,7 +2608,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3123,7 +3151,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -3222,7 +3249,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", diff --git a/package.json b/package.json index 3abcaf9..9386db1 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,14 @@ "@codemirror/commands": "^6.10.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-markdown": "^6.5.0", "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.39.4", "@emmetio/codemirror6-plugin": "^0.4.0", "@supabase/supabase-js": "^2.90.1", "codemirror": "^6.0.2", + "marked": "^17.0.1", "whatwg-fetch": "^3.6.20" } } diff --git a/schemas/code-crispies-module-schema.json b/schemas/code-crispies-module-schema.json index 81d2a1e..2f2fc5b 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"], - "description": "Whether this module teaches CSS, Tailwind, or HTML" + "enum": ["css", "tailwind", "html", "markdown"], + "description": "Whether this module teaches CSS, Tailwind, HTML, or Markdown" }, "difficulty": { "type": "string", @@ -60,7 +60,7 @@ }, "mode": { "type": "string", - "enum": ["css", "tailwind", "html"], + "enum": ["css", "tailwind", "html", "markdown"], "description": "Override module mode for individual lessons" }, "tailwindConfig": { diff --git a/src/app.js b/src/app.js index a073ba6..0014d40 100644 --- a/src/app.js +++ b/src/app.js @@ -573,6 +573,11 @@ function updateEditorForMode(mode) { label: "CSS Editor", cmMode: "css" }, + markdown: { + placeholder: "# Heading\n\nWrite your **Markdown** here...", + label: "Markdown Editor", + cmMode: "markdown" + }, playground: { placeholder: "\n\n", label: "HTML & CSS", @@ -1409,6 +1414,85 @@ const sectionContent = { + `, + markdown: ` +
Markdown is a lightweight markup language created by John Gruber in 2004. It lets you write formatted text using plain text syntax that's easy to read and write. Markdown is used everywhere—from GitHub READMEs to documentation, note-taking apps, and content management systems.
+The beauty of Markdown is its simplicity: # Heading creates a heading, **bold** makes text bold, and [link](url) creates a link. No complex HTML tags needed. Markdown files can be converted to HTML, PDF, or many other formats.
Create document structure with headings using # symbols. One # for h1, two ## for h2, up to six levels. This creates a clear hierarchy in your documents.
# Main Title
+## Section
+### Subsection
+#### Detail
+ Emphasize text with **bold** or *italic*. Combine them with ***bold italic***. Use backticks for \`inline code\` to highlight commands or code snippets in your text.
This is **bold** text.
+This is *italic* text.
+This is \`inline code\`.
+ Create bullet lists with -, *, or +. Numbered lists use 1., 2., etc. Indent items with spaces to create nested lists for complex outlines.
+ Practice lists → +
+- First item
+- Second item
+ - Nested item
+
+1. Step one
+2. Step two
+3. Step three
+ Create links with [text](url) syntax. Images use the same format with an exclamation mark: . The alt text describes the image for accessibility.
+ Practice links → +
+[Visit Google](https://google.com)
+
+
+ Learn: HTML Section | Style with: CSS Properties
+ `, + + markdown: ` +A quick guide to Markdown syntax for formatting text documents. Markdown is used in GitHub, documentation, and note-taking apps.
+ +| Syntax | Result | Notes |
|---|---|---|
**bold** | bold | Or use __bold__ |
*italic* | italic | Or use _italic_ |
***bold italic*** | bold italic | Combine both |
~~strikethrough~~ | GFM extension | |
\`inline code\` | inline code | Monospace font |
| Syntax | Level | Usage |
|---|---|---|
# Heading 1 | h1 | Document title |
## Heading 2 | h2 | Main sections |
### Heading 3 | h3 | Subsections |
#### Heading 4 | h4 | Minor sections |
##### Heading 5 | h5 | Rarely used |
###### Heading 6 | h6 | Smallest heading |
| Syntax | Type | Notes |
|---|---|---|
- Item | Unordered | Or use * or + |
1. Item | Ordered | Numbers auto-increment |
- Nested | Nested list | 2-space indent |
- [x] Task | Task list | GFM extension |
- [ ] Task | Unchecked task | Interactive checkboxes |
| Syntax | Purpose | Example |
|---|---|---|
[text](url) | Inline link | [Google](https://google.com) |
[text](url "title") | Link with tooltip | Hover text |
 | Image | Alt text for accessibility |
<url> | Auto-link | URLs become clickable |
[ref]: url | Reference link | Define at doc bottom |
| Syntax | Purpose | Notes |
|---|---|---|
\`\`\` | Fenced code | 3 backticks or tildes |
\`\`\`js | Syntax highlight | Add language identifier |
code | Indented code | 4-space indent |
| Syntax | Element | Notes |
|---|---|---|
> Quote | Blockquote | Nest with >> |
--- | Horizontal rule | Or *** or ___ |
| A | B | | Table | GFM extension |
| Header 1 | Header 2 |
+|----------|----------|
+| Cell 1 | Cell 2 |
+| Cell 3 | Cell 4 |
+ Use colons for alignment: :--- (left), :---: (center), ---: (right)
Learn: Markdown Section | Also try: HTML Elements
` }; @@ -1970,7 +2153,7 @@ function updatePageMeta(route) { break; case RouteType.SECTION: { - const sectionNames = { css: "CSS", html: "HTML", tailwind: "Tailwind CSS" }; + const sectionNames = { css: "CSS", html: "HTML", tailwind: "Tailwind CSS", markdown: "Markdown" }; const sectionName = sectionNames[route.sectionId] || route.sectionId; title = `${sectionName} Lessons - CODE CRISPIES | Learn ${sectionName}`; description = `Learn ${sectionName} through interactive coding exercises. Hands-on practice with instant feedback.`; @@ -1994,7 +2177,8 @@ function updatePageMeta(route) { selectors: "CSS Selectors", flexbox: "Flexbox", grid: "CSS Grid", - html: "HTML Elements" + html: "HTML Elements", + markdown: "Markdown Syntax" }; const refName = refNames[route.refId] || "Reference"; title = `${refName} Reference - CODE CRISPIES`; @@ -2163,7 +2347,7 @@ function renderFooterLessonLinks() { * Update progress indicators on landing page */ function updateLandingProgress() { - ["css", "html", "tailwind"].forEach((sectionId) => { + ["css", "html", "tailwind", "markdown"].forEach((sectionId) => { const progressEl = document.getElementById(`${sectionId}-progress`); if (progressEl) { const sectionModules = getModulesBySection(lessonEngine.modules, sectionId); @@ -2249,7 +2433,7 @@ function showReferencePage(refId) { const activeRef = refId || "css"; // Map reference to section for color coding - const refToSection = { css: "css", selectors: "css", flexbox: "css", grid: "css", html: "html" }; + const refToSection = { css: "css", selectors: "css", flexbox: "css", grid: "css", html: "html", markdown: "markdown" }; updateSectionColor(refToSection[activeRef] || "css"); // Track reference page view diff --git a/src/config/lessons.js b/src/config/lessons.js index 71a159a..33f422c 100644 --- a/src/config/lessons.js +++ b/src/config/lessons.js @@ -30,6 +30,7 @@ import gradientsEN from "../../lessons/09-gradients.json"; import filtersEN from "../../lessons/11-filters.json"; import positioningEN from "../../lessons/12-positioning.json"; import pseudoElementsEN from "../../lessons/13-pseudo-elements.json"; +import markdownBasicsEN from "../../lessons/40-markdown-basics.json"; import playgroundEN from "../../lessons/98-playground.json"; import goodbyeEN from "../../lessons/99-goodbye.json"; @@ -162,6 +163,8 @@ const moduleStoreEN = [ htmlFieldsetEN, htmlDatalistEN, htmlTablesEN, + // Markdown + markdownBasicsEN, // Outro goodbyeEN, playgroundEN @@ -201,6 +204,8 @@ const moduleStoreDE = [ htmlFieldsetDE, htmlDatalistDE, htmlTablesDE, + // Markdown + markdownBasicsEN, // Using EN fallback until translated // Outro goodbyeEN, playgroundEN @@ -240,6 +245,8 @@ const moduleStorePL = [ htmlFieldsetPL, htmlDatalistPL, htmlTablesPL, + // Markdown + markdownBasicsEN, // Using EN fallback until translated // Outro goodbyeEN, playgroundEN @@ -279,6 +286,8 @@ const moduleStoreES = [ htmlFieldsetES, htmlDatalistES, htmlTablesES, + // Markdown + markdownBasicsEN, // Using EN fallback until translated // Outro goodbyeEN, playgroundEN @@ -318,6 +327,8 @@ const moduleStoreAR = [ htmlFieldsetAR, htmlDatalistAR, htmlTablesAR, + // Markdown + markdownBasicsEN, // Using EN fallback until translated // Outro goodbyeEN, playgroundEN @@ -357,6 +368,8 @@ const moduleStoreUK = [ htmlFieldsetUK, htmlDatalistUK, htmlTablesUK, + // Markdown + markdownBasicsEN, // Using EN fallback until translated // Outro goodbyeEN, playgroundEN diff --git a/src/config/sections.js b/src/config/sections.js index 71c42fd..29f8f08 100644 --- a/src/config/sections.js +++ b/src/config/sections.js @@ -24,6 +24,13 @@ export const sections = { description: "Utility-first CSS framework", color: "#26a69a", order: 3 + }, + markdown: { + id: "markdown", + title: "Markdown", + description: "Lightweight markup language for formatting text", + color: "#5b8dd9", + order: 4 } }; @@ -57,6 +64,7 @@ export function getModuleSection(module) { const mode = module.mode || "css"; if (mode === "html") return "html"; if (mode === "tailwind") return "tailwind"; + if (mode === "markdown") return "markdown"; return "css"; } diff --git a/src/helpers/router.js b/src/helpers/router.js index 4b54b4e..c6baece 100644 --- a/src/helpers/router.js +++ b/src/helpers/router.js @@ -8,6 +8,7 @@ * - #css -> CSS section landing * - #html -> HTML section landing * - #tailwind -> Tailwind section landing + * - #markdown -> Markdown section landing * - #reference/css -> CSS cheatsheet * - #module/index -> Lesson (e.g., #flexbox/2) */ @@ -26,7 +27,7 @@ export const RouteType = { /** * Valid section IDs */ -const SECTIONS = ["css", "html", "tailwind"]; +const SECTIONS = ["css", "html", "tailwind", "markdown"]; /** * Valid language codes for URL-based switching diff --git a/src/i18n.js b/src/i18n.js index 09885db..d3e6049 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -143,6 +143,7 @@ const translations = { landingCssDesc: "Styling, layout, and animations", landingHtmlDesc: "Semantic markup and native elements", landingTailwindDesc: "Utility-first CSS framework", + landingMarkdownDesc: "Format text with simple syntax", comingSoon: "Coming Soon", landingCtaTitle: "Start Learning Today", landingCtaSub: "Free and open source. No account required. Progress saved locally.", @@ -376,6 +377,7 @@ const translations = { landingCssDesc: "Styling, Layout und Animationen", landingHtmlDesc: "Semantisches Markup und native Elemente", landingTailwindDesc: "Utility-first CSS-Framework", + landingMarkdownDesc: "Text mit einfacher Syntax formatieren", comingSoon: "Bald verfügbar", landingCtaTitle: "Jetzt gleich anfangen", landingCtaSub: "Kostenlos und Open Source. Kein Konto erforderlich. Fortschritt wird lokal gespeichert.", @@ -605,6 +607,7 @@ const translations = { landingCssDesc: "Stylowanie, układy i animacje", landingHtmlDesc: "Semantyczne znaczniki i natywne elementy", landingTailwindDesc: "Framework CSS oparty na klasach utility", + landingMarkdownDesc: "Formatuj tekst prostą składnią", comingSoon: "Wkrótce", landingCtaTitle: "Zacznij naukę już dziś", landingCtaSub: "Darmowe i open source. Bez konta. Postęp zapisywany lokalnie.", @@ -836,6 +839,7 @@ const translations = { landingCssDesc: "Estilos, diseño y animaciones", landingHtmlDesc: "Marcado semántico y elementos nativos", landingTailwindDesc: "Framework CSS basado en utilidades", + landingMarkdownDesc: "Formatea texto con sintaxis simple", comingSoon: "Próximamente", landingCtaTitle: "Empieza a aprender hoy", landingCtaSub: "Gratis y de código abierto. Sin cuenta requerida. Progreso guardado localmente.", @@ -1062,6 +1066,7 @@ const translations = { landingCssDesc: "التنسيق والتخطيط والرسوم المتحركة", landingHtmlDesc: "الترميز الدلالي والعناصر الأصلية", landingTailwindDesc: "إطار CSS قائم على الأدوات", + landingMarkdownDesc: "تنسيق النص بصيغة بسيطة", comingSoon: "قريباً", landingCtaTitle: "ابدأ التعلم اليوم", landingCtaSub: "مجاني ومفتوح المصدر. لا حاجة لحساب. يُحفظ التقدم محليًا.", @@ -1290,6 +1295,7 @@ const translations = { landingCssDesc: "Стилізація, макети та анімації", landingHtmlDesc: "Семантична розмітка та нативні елементи", landingTailwindDesc: "CSS-фреймворк на основі утиліт", + landingMarkdownDesc: "Форматуй текст простим синтаксисом", comingSoon: "Незабаром", landingCtaTitle: "Почни вчитися сьогодні", landingCtaSub: "Безкоштовно та з відкритим кодом. Без реєстрації. Прогрес зберігається локально.", diff --git a/src/impl/CodeEditor.js b/src/impl/CodeEditor.js index e916aff..d34bd4f 100644 --- a/src/impl/CodeEditor.js +++ b/src/impl/CodeEditor.js @@ -7,6 +7,7 @@ import { defaultKeymap, historyKeymap, indentMore, indentLess, undo, redo } from import { history } from "@codemirror/commands"; import { html } from "@codemirror/lang-html"; import { css } from "@codemirror/lang-css"; +import { markdown } from "@codemirror/lang-markdown"; import { autocompletion } from "@codemirror/autocomplete"; import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin"; import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; @@ -180,7 +181,7 @@ export class CodeEditor { const fullDoc = prefix + initialValue + suffix; // Get language extension based on mode - const langExtension = this.mode === "html" ? html() : css(); + const langExtension = this.mode === "html" ? html() : this.mode === "markdown" ? markdown() : css(); // Create read-only zones decorations const readOnlyMark = Decoration.mark({ class: "cm-readonly-zone" }); diff --git a/src/impl/LessonEngine.js b/src/impl/LessonEngine.js index 795e6b7..3604e16 100644 --- a/src/impl/LessonEngine.js +++ b/src/impl/LessonEngine.js @@ -3,6 +3,7 @@ * Single source of truth for lesson state and progress */ import { validateUserCode } from "../helpers/validator.js"; +import { marked } from "marked"; // Auth sync - lazy loaded to avoid circular dependencies let authModule = null; @@ -255,6 +256,38 @@ export class LessonEngine { ${htmlWithClasses}