Back to Freecodecamp

Build a Markdown to HTML Converter

curriculum/challenges/english/blocks/lab-markdown-to-html-converter/66f55eac933ff64ce654ca74.md

latest39.3 KB
Original Source

--description--

Markdown is a markup language used to add formatting elements to plain-text documents. For this lab, all the HTML and CSS has been provided to you. You will use JavaScript to complete the Markdown to HTML Converter app so that it can handle the conversion of basic Markdown constructs into HTML elements.

Note: The final result won't be a comprehensive Markdown to HTML converter, but you can add extra functionalities to it if you would like.

Objective: Fulfill the user stories below and get all the tests to pass to complete the lab.

User Stories:

  1. You should have a function named convertMarkdown that takes no parameters.
  2. The convertMarkdown function should use regular expressions to convert the markdown input from #markdown-input into HTML and should return a string containing the HTML code.
  3. The convertMarkdown function should convert headings of level one, two, and three into the corresponding h1, h2, and h3 elements. A heading in markdown is indicated by as many # character as its level followed by a space and the heading text. # characters should be placed at the beginning of the line: there can be spaces but no other characters before it.
  4. The convertMarkdown function should convert bold text into strong elements. Bold text in markdown is indicated either by a pair of double asterisks or a pair of double underscores enclosing the text.
  5. The convertMarkdown function should convert italic text into em elements. Italic text in markdown is indicated either by a pair of asterisks or a pair of underscores enclosing the text.
  6. The convertMarkdown function should convert images into img elements. An image in markdown is indicated by ![alt-text](image-source), where alt-text is the value of the alt attribute and image-source is the value of the src attribute.
  7. The convertMarkdown function should convert links into anchor elements. A link in markdown is indicated by [link text](URL), where link text is the text to enclosed within the anchor tags and URL is the value of href attribute.
  8. The convertMarkdown function should convert quotes into blockquote elements. A quote in markdown is indicated by a > followed by a space and the quote text. The > character should be placed at the beginning of the line: there can be spaces but no other characters before it.
  9. When you input text inside #markdown-input, the raw HTML code returned by convertMarkdown should be displayed inside #html-output.
  10. When you input text inside #markdown-input, the HTML code returned by convertMarkdown should be rendered inside #preview.

Note: you should work with the input event for this project.

Here's a table containing all the markdown that convertMarkdown should be able to handle and the expected HTML after conversion:

<table> <thead> <tr> <th>Markdown</th> <th>HTML</th> </tr> </thead> <tbody> <tr> <td><code># heading 1</code></th> <td><code>&lth1&gtheading 1&lt/h1&gt</code></th> </tr> <tr> <td><code>## heading 2</code></th> <td><code>&lth2&gtheading 2&lt/h2&gt</code></th> </tr> <tr> <td><code>### heading 3</code></th> <td><code>&lth3&gtheading 3&lt/h3&gt</code></th> </tr> <tr> <td><code>**bold text**</code> or <code>__bold text__</code></th> <td><code>&ltstrong&gtbold text&lt/strong&gt</code></th> </tr> <tr> <td><code>*italic text*</code> or <code>_italic text_</code></th> <td><code>&ltem&gtitalic text&lt/em&gt</code></th> </tr> <tr> <td><code>![alt-text](image-source)</code></th> <td><code>&ltimg alt="alt-text" src="image-source"&gt</code></th> </tr> <tr> <td><code>[link text](URL)</code></th> <td><code>&lta href="URL"&gtlink text&lt/a&gt</code></th> </tr> <tr> <td><code>> quote</code></th> <td><code>&ltblockquote&gtquote&lt/blockquote&gt</code></th> </tr> </tbody> </table>

Note: Be sure to link your JavaScript file in your HTML.

--hints--

You should have a function named convertMarkdown.

js
assert.isFunction(convertMarkdown);

When the value of #markdown-input is # title 1, convertMarkdown() should return <h1>title 1</h1>.

js
const input = document.querySelector("#markdown-input");
input.value = "# title 1";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const headings = testDiv.querySelectorAll("h1");
assert.lengthOf(headings, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(headings[0].innerText, "title 1");

When the value of #markdown-input is # title 1, <h1>title 1</h1> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "# title 1";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const headings = testDiv.querySelectorAll("h1");
assert.lengthOf(headings, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(headings[0].innerText, "title 1");

When the value of #markdown-input is # title 1, an h1 element with the text of title 1 should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "# title 1";
input.dispatchEvent(new Event("input"));
const headings = preview.querySelectorAll("h1");
assert.lengthOf(headings, 1);
assert.lengthOf(preview.children, 1);
assert.equal(headings[0].innerText, "title 1");

When the value of #markdown-input is some text # title 1, convertMarkdown() should not convert # title 1 into an h1 element.

js
const input = document.querySelector("#markdown-input");
input.value = "some text # title 1";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const testH1 = testDiv.querySelector("h1")
assert.notExists(testH1);
assert.equal(testDiv.innerText, input.value)

When the value of #markdown-input is # title 1 followed by # alternate title on a new line, convertMarkdown() should return <h1>title 1</h1><h1>alternate title</h1>.

js
const input = document.querySelector("#markdown-input");
input.value = "# title 1\n# alternate title";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const headings = testDiv.querySelectorAll("h1");
assert.lengthOf(headings, 2);
assert.lengthOf(testDiv.children, 2);
assert.equal(headings[0].innerText, "title 1");
assert.equal(headings[1].innerText, "alternate title");

When the value of #markdown-input is ## title 2, convertMarkdown() should return <h2>title 2</h2>.

js
const input = document.querySelector("#markdown-input");
input.value = "## title 2";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const headings = testDiv.querySelectorAll("h2");
assert.lengthOf(headings, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(headings[0].innerText, "title 2");

When the value of #markdown-input is ## title 2, <h2>title 2</h2> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "## title 2";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const headings = testDiv.querySelectorAll("h2");
assert.lengthOf(headings, 1);
assert.lengthOf(testDiv.children, 1)
assert.equal(headings[0].innerText, "title 2");

When the value of #markdown-input is ## title 2, an h2 element with the text of title 2 should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "## title 2";
input.dispatchEvent(new Event("input"));
const headings = preview.querySelectorAll("h2");
assert.lengthOf(headings, 1);
assert.lengthOf(preview.children, 1)
assert.equal(headings[0].innerText, "title 2");

When the value of #markdown-input is some text ## title 2, convertMarkdown() should not convert ## title 2 into an h2 element.

js
const input = document.querySelector("#markdown-input");
input.value = "some text ## title 2";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const testH2 = testDiv.querySelector("h2")
assert.notExists(testH2);
assert.equal(testDiv.innerText, input.value)

When the value of #markdown-input is ## title 2 followed by ## title 2 alt on a new line, convertMarkdown() should return <h2>title 2</h2><h2>title 2 alt</h2>.

js
const input = document.querySelector("#markdown-input");
input.value = "## title 2\n## title 2 alt";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const headings = testDiv.querySelectorAll("h2");
assert.lengthOf(headings, 2);
assert.lengthOf(testDiv.children, 2);
assert.equal(headings[0].innerText, "title 2");
assert.equal(headings[1].innerText, "title 2 alt");

When the value of #markdown-input is ### title 3, convertMarkdown() should return <h3>title 3</h3>.

js
const input = document.querySelector("#markdown-input");
input.value = "### title 3";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const headings = testDiv.querySelectorAll("h3");
assert.lengthOf(headings, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(headings[0].innerText, "title 3");

When the value of #markdown-input is ### title 3, <h3>title 3</h3> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "### title 3";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const headings = testDiv.querySelectorAll("h3");
assert.lengthOf(headings, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(headings[0].innerText, "title 3");

When the value of #markdown-input is ### title 3, an h3 element with the text of title 3 should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "### title 3";
input.dispatchEvent(new Event("input"));
const headings = preview.querySelectorAll("h3");
assert.lengthOf(headings, 1);
assert.lengthOf(preview.children, 1);
assert.equal(headings[0].innerText, "title 3");

When the value of #markdown-input is some text ### title 3, convertMarkdown() should not convert ### title 3 into an h3 element.

js
const input = document.querySelector("#markdown-input");
input.value = "some text ### title 3";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const testH3 = testDiv.querySelector("h3")
assert.notExists(testH3);
assert.equal(testDiv.innerText, input.value)

When the value of #markdown-input is ### title 3 followed by ### third title on a new line, convertMarkdown() should return <h3>title 3</h3><h3>third title</h3>.

js
const input = document.querySelector("#markdown-input");
input.value = "### title 3\n### third title";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const headings = testDiv.querySelectorAll("h3");
assert.lengthOf(headings, 2);
assert.lengthOf(testDiv.children, 2);
assert.equal(headings[0].innerText, "title 3");
assert.equal(headings[1].innerText, "third title");

When the value of #markdown-input is **this is bold**, convertMarkdown() should return <strong>this is bold</strong>.

js
const input = document.querySelector("#markdown-input");
input.value = "**this is bold**";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const strongs = testDiv.querySelectorAll("strong");
assert.lengthOf(strongs, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(strongs[0].innerText, "this is bold");

When the value of #markdown-input is **this is bold**, <strong>this is bold</strong> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "**this is bold**";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const strongs = testDiv.querySelectorAll("strong");
assert.lengthOf(strongs, 1);
assert.lengthOf(testDiv.children, 1)
assert.equal(strongs[0].innerText, "this is bold");

When the value of #markdown-input is **this is bold**, a strong element with the text of this is bold should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "**this is bold**";
input.dispatchEvent(new Event("input"));
const strongs = preview.querySelectorAll("strong");
assert.lengthOf(strongs, 1);
assert.lengthOf(preview.children, 1)
assert.equal(strongs[0].innerText, "this is bold");

When the value of #markdown-input is **this is bold** followed by **this is also bold** on a new line, convertMarkdown() should return <strong>this is bold</strong><strong>this is also bold</strong>.

js
const input = document.querySelector("#markdown-input");
input.value = "**this is bold**\n**this is also bold**";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const strongs = testDiv.querySelectorAll("strong");
assert.lengthOf(strongs, 2);
assert.lengthOf(testDiv.children, 2);
assert.equal(strongs[0].innerText, "this is bold");
assert.equal(strongs[1].innerText, "this is also bold");

When the value of #markdown-input is __this is bold__, <strong>this is bold</strong> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "__this is bold__";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const strongs = testDiv.querySelectorAll("strong");
assert.lengthOf(strongs, 1);
assert.lengthOf(testDiv.children, 1)
assert.equal(strongs[0].innerText, "this is bold");

When the value of #markdown-input is __this is bold__, a strong element with the text of this is bold should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "__this is bold__";
input.dispatchEvent(new Event("input"));
const strongs = preview.querySelectorAll("strong");
assert.lengthOf(strongs, 1);
assert.lengthOf(preview.children, 1)
assert.equal(strongs[0].innerText, "this is bold");

When the value of #markdown-input is __this is bold__ followed by __this is also bold__ on a new line, convertMarkdown() should return <strong>this is bold</strong><strong>this is also bold</strong>.

js
const input = document.querySelector("#markdown-input");
input.value = "__this is bold__\n__this is also bold__";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const strongs = testDiv.querySelectorAll("strong");
assert.lengthOf(strongs, 2);
assert.lengthOf(testDiv.children, 2);
assert.equal(strongs[0].innerText, "this is bold");
assert.equal(strongs[1].innerText, "this is also bold");

When the value of #markdown-input is *this is italic*, convertMarkdown() should return <em>this is italic</em>.

js
const input = document.querySelector("#markdown-input");
input.value = "*this is italic*";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const italics = testDiv.querySelectorAll("em");
assert.lengthOf(italics, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(italics[0].innerText, "this is italic");

When the value of #markdown-input is *this is italic*, <em>this is italic</em> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "*this is italic*";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const italics = testDiv.querySelectorAll("em");
assert.lengthOf(italics, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(italics[0].innerText, "this is italic");

When the value of #markdown-input is *this is italic*, an em element with the text of this is italic should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "*this is italic*";
input.dispatchEvent(new Event("input"));
const italics = preview.querySelectorAll("em");
assert.lengthOf(italics, 1);
assert.lengthOf(preview.children, 1);
assert.equal(italics[0].innerText, "this is italic");

When the value of #markdown-input is *this is italic* followed by *this is also italic* on a new line, convertMarkdown() should return <em>this is italic</em><em>this is also italic</em>.

js
const input = document.querySelector("#markdown-input");
input.value = "*this is italic*\n*this is also italic*";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const italics = testDiv.querySelectorAll("em");
assert.lengthOf(italics, 2);
assert.lengthOf(testDiv.children, 2);
assert.equal(italics[0].innerText, "this is italic");
assert.equal(italics[1].innerText, "this is also italic");

When the value of #markdown-input is _this is italic_, convertMarkdown() should return <em>this is italic</em>.

js
const input = document.querySelector("#markdown-input");
input.value = "_this is italic_";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const italics = testDiv.querySelectorAll("em");
assert.lengthOf(italics, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(italics[0].innerText, "this is italic");

When the value of #markdown-input is _this is italic_, <em>this is italic</em> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "_this is italic_";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const italics = testDiv.querySelectorAll("em");
assert.lengthOf(italics, 1);
assert.lengthOf(testDiv.children, 1);
assert.equal(italics[0].innerText, "this is italic");

When the value of #markdown-input is _this is italic_, an em element with the text of this is italic should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "_this is italic_";
input.dispatchEvent(new Event("input"));
const italics = preview.querySelectorAll("em");
assert.lengthOf(italics, 1);
assert.lengthOf(preview.children, 1);
assert.equal(italics[0].innerText, "this is italic");

When the value of #markdown-input is _this is italic_ followed by _this is also italic_ on a new line, convertMarkdown() should return <em>this is italic</em><em>this is also italic</em>.

js
const input = document.querySelector("#markdown-input");
input.value = "_this is italic_\n_this is also italic_";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const italics = testDiv.querySelectorAll("em");
assert.lengthOf(italics, 2);
assert.lengthOf(testDiv.children, 2);
assert.equal(italics[0].innerText, "this is italic");
assert.equal(italics[1].innerText, "this is also italic");

When the value of #markdown-input is either # **title 1** or # __title 1__, convertMarkdown() should return <h1><strong>title 1</strong></h1>.

js
const input = document.querySelector("#markdown-input");
input.value = "# **title 1**";
const testDiv1 = document.createElement("div");
testDiv1.innerHTML = convertMarkdown();
let headings = testDiv1.querySelectorAll("h1");
let testStrong = testDiv1.querySelector("h1>strong")
assert.lengthOf(testDiv1.children, 1);
assert.lengthOf(headings, 1);
assert.lengthOf(headings[0].children, 1);
assert.exists(testStrong);
assert.equal(testStrong.innerText, "title 1");

input.value = "# __title 1__";
const testDiv2 = document.createElement("div");
testDiv2.innerHTML = convertMarkdown();
headings = testDiv2.querySelectorAll("h1");
testStrong = testDiv2.querySelector("h1>strong")
assert.lengthOf(testDiv2.children, 1);
assert.lengthOf(headings, 1);
assert.lengthOf(headings[0].children, 1);
assert.exists(testStrong);
assert.equal(testStrong.innerText, "title 1");

When the value of #markdown-input is either # **title 1** or # __title 1__, <h1><strong>title 1</strong></h1> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "# **title 1**";
input.dispatchEvent(new Event("input"));
const testDiv1 = document.createElement("div");
testDiv1.innerHTML = output.innerText;
let headings = testDiv1.querySelectorAll("h1")
let testStrong = testDiv1.querySelector("h1>strong")
assert.lengthOf(testDiv1.children, 1);
assert.lengthOf(headings, 1);
assert.lengthOf(headings[0].children, 1);
assert.exists(testStrong);
assert.equal(testStrong.innerText, "title 1");

output.innerText = "";
input.value = "# __title 1__";
input.dispatchEvent(new Event("input"));
const testDiv2 = document.createElement("div");
testDiv2.innerHTML = output.innerText;
headings = testDiv2.querySelectorAll("h1")
testStrong = testDiv2.querySelector("h1>strong")
assert.lengthOf(testDiv2.children, 1);
assert.lengthOf(headings, 1);
assert.lengthOf(headings[0].children, 1);
assert.exists(testStrong);
assert.equal(testStrong.innerText, "title 1");

When the value of #markdown-input is either # **title 1** or # __title 1__, you set the inner HTML of #preview to <h1><strong>title 1</strong></h1>.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "# **title 1**";
input.dispatchEvent(new Event("input"));
let headings = preview.querySelectorAll("h1")
let testStrong = preview.querySelector("h1>strong")
assert.lengthOf(preview.children, 1);
assert.lengthOf(headings, 1);
assert.lengthOf(headings[0].children, 1);
assert.exists(testStrong);
assert.equal(testStrong.innerText, "title 1");

preview.innerHTML = "";
input.value = "# __title 1__";
input.dispatchEvent(new Event("input"));
headings = preview.querySelectorAll("h1")
testStrong = preview.querySelector("h1>strong")
assert.lengthOf(preview.children, 1);
assert.lengthOf(headings, 1);
assert.lengthOf(headings[0].children, 1);
assert.exists(testStrong);
assert.equal(testStrong.innerText, "title 1");

When the value of #markdown-input is ![alt-text](image-source), convertMarkdown() should return ``.

js
const input = document.querySelector("#markdown-input");
input.value = "![alt-text](image-source)";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const imgs = testDiv.querySelectorAll("img");
assert.lengthOf(testDiv.children, 1);
assert.lengthOf(imgs, 1);
assert.equal(imgs[0].alt, "alt-text");
assert.isTrue(imgs[0].src.endsWith("image-source"));

When the value of #markdown-input is ![alt-text](image-source), `` should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "![alt-text](image-source)";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const imgs = testDiv.querySelectorAll("img");
assert.lengthOf(testDiv.children, 1)
assert.lengthOf(imgs, 1);
assert.equal(imgs[0].alt, "alt-text");
assert.isTrue(imgs[0].src.endsWith("image-source"));

When the value of #markdown-input is ![alt-text](image-source), `` should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "![alt-text](image-source)";
input.dispatchEvent(new Event("input"));
const imgs = preview.querySelectorAll("img");
assert.lengthOf(preview.children, 1)
assert.lengthOf(imgs, 1);
assert.equal(imgs[0].alt, "alt-text");
assert.isTrue(imgs[0].src.endsWith("image-source"));

When the value of #markdown-input is ![alt-text](image-source) followed by ![alt-text-2](image-source-2) on a new line, convertMarkdown() should return ``.

js
const input = document.querySelector("#markdown-input");
input.value = "![alt-text](image-source)\n![alt-text-2](image-source-2)";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const imgs = testDiv.querySelectorAll("img");
assert.lengthOf(testDiv.children, 2);
assert.lengthOf(imgs, 2);
assert.equal(imgs[0].alt, "alt-text");
assert.isTrue(imgs[0].src.endsWith("image-source"));
assert.equal(imgs[1].alt, "alt-text-2");
assert.isTrue(imgs[1].src.endsWith("image-source-2"));

When the value of #markdown-input is [link text](URL), convertMarkdown() should return <a href="URL">link text</a>.

js
const input = document.querySelector("#markdown-input");
input.value = "[link text](URL)";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const anchors = testDiv.querySelectorAll("a");
assert.lengthOf(testDiv.children, 1);
assert.lengthOf(anchors, 1);
assert.isTrue(anchors[0].href.endsWith("URL"));
assert.equal(anchors[0].innerText, "link text");

When the value of #markdown-input is [link text](URL), <a href="URL">link text</a> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "[link text](URL)";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const anchors = testDiv.querySelectorAll("a");
assert.lengthOf(testDiv.children, 1);
assert.lengthOf(anchors, 1);
assert.isTrue(anchors[0].href.endsWith("URL"));
assert.equal(anchors[0].innerText, "link text");

When the value of #markdown-input is [link text](URL), <a href="URL">link text</a> should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "[link text](URL)";
input.dispatchEvent(new Event("input"));
const anchors = preview.querySelectorAll("a");
assert.lengthOf(preview.children, 1);
assert.lengthOf(anchors, 1);
assert.isTrue(anchors[0].href.endsWith("URL"));
assert.equal(anchors[0].innerText, "link text");

When the value of #markdown-input is [link text](URL) followed by [link text 2](URL2) on a new line, convertMarkdown() should return <a href="URL">link text</a><a href="URL2">link text 2</a>.

js
const input = document.querySelector("#markdown-input");
input.value = "[link text](URL)\n[link text 2](URL2)";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const anchors = testDiv.querySelectorAll("a");
assert.lengthOf(testDiv.children, 2);
assert.lengthOf(anchors, 2);
assert.isTrue(anchors[0].href.endsWith("URL"));
assert.equal(anchors[0].innerText, "link text");
assert.isTrue(anchors[1].href.endsWith("URL2"));
assert.equal(anchors[1].innerText, "link text 2");

When the value of #markdown-input is > this is a quote, convertMarkdown() should return <blockquote>this is a quote</blockquote>.

js
const input = document.querySelector("#markdown-input");
input.value = "> this is a quote";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const quotes = testDiv.querySelectorAll("blockquote");
assert.lengthOf(testDiv.children, 1);
assert.lengthOf(quotes, 1);
assert.equal(quotes[0].innerText, "this is a quote");

When the value of #markdown-input is > this is a quote, <blockquote>this is a quote</blockquote> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "> this is a quote";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const quotes = testDiv.querySelectorAll("blockquote");
assert.lengthOf(testDiv.children, 1);
assert.lengthOf(quotes, 1);
assert.equal(quotes[0].innerText, "this is a quote");

When the value of #markdown-input is > this is a quote, <blockquote>this is a quote</blockquote> should be appended as a child of #preview.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "> this is a quote";
input.dispatchEvent(new Event("input"));
const quotes = preview.querySelectorAll("blockquote");
assert.lengthOf(preview.children, 1);
assert.lengthOf(quotes, 1);
assert.equal(quotes[0].innerText, "this is a quote");

When the value of #markdown-input is > this is a quote followed by > this is another quote on a new line, convertMarkdown() should return <blockquote>this is a quote</blockquote><blockquote>this is another quote</blockquote>.

js
const input = document.querySelector("#markdown-input");
input.value = "> this is a quote\n> this is another quote";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const quotes = testDiv.querySelectorAll("blockquote");
assert.lengthOf(testDiv.children, 2);
assert.lengthOf(quotes, 2);
assert.equal(quotes[0].innerText, "this is a quote");
assert.equal(quotes[1].innerText, "this is another quote");

When the value of #markdown-input is some text > not a quote anymore, convertMarkdown() should not convert > not a quote anymore into a blockquote element.

js
const input = document.querySelector("#markdown-input");
input.value = "some text > not a quote anymore";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const testQuote = testDiv.querySelector("blockquote")
assert.notExists(testQuote);
assert.equal(testDiv.innerText, input.value)

When the value of #markdown-input is > **this is a *quote***, convertMarkdown() should return <blockquote><strong>this is a <em>quote</em></strong></blockquote>.

js
const input = document.querySelector("#markdown-input");
input.value = "> **this is a *quote***";
const testDiv = document.createElement("div");
testDiv.innerHTML = convertMarkdown();
const quotes = testDiv.querySelectorAll("blockquote");
const strongs = testDiv.querySelectorAll("blockquote>strong");
const italics = testDiv.querySelectorAll("blockquote>strong>em");
assert.lengthOf(testDiv.children, 1);
assert.lengthOf(quotes, 1);
assert.lengthOf(quotes[0].children, 1);
assert.lengthOf(strongs, 1);
assert.lengthOf(strongs[0].children, 1);
assert.equal(strongs[0].innerText, "this is a quote");
assert.lengthOf(italics, 1);
assert.equal(italics[0].innerText, "quote");

When the value of #markdown-input is > **this is a *quote***, <blockquote><strong>this is a <em>quote</em></strong></blockquote> should be displayed inside #html-output.

js
const input = document.querySelector("#markdown-input");
const output = document.querySelector("#html-output");
output.innerText = "";
input.value = "> **this is a *quote***";
input.dispatchEvent(new Event("input"));
const testDiv = document.createElement("div");
testDiv.innerHTML = output.innerText;
const quotes = testDiv.querySelectorAll("blockquote");
const strongs = testDiv.querySelectorAll("blockquote>strong");
const italics = testDiv.querySelectorAll("blockquote>strong>em");
assert.lengthOf(testDiv.children, 1);
assert.lengthOf(quotes, 1);
assert.lengthOf(quotes[0].children, 1);
assert.lengthOf(strongs, 1);
assert.lengthOf(strongs[0].children, 1);
assert.equal(strongs[0].innerText, "this is a quote");
assert.lengthOf(italics, 1);
assert.equal(italics[0].innerText, "quote");

When the value of #markdown-input is > **this is a *quote***, you should set the inner HTML of #preview to <blockquote><strong>this is a <em>quote</em></strong></blockquote>.

js
const input = document.querySelector("#markdown-input");
const preview = document.querySelector("#preview");
preview.innerHTML = "";
input.value = "> **this is a *quote***";
input.dispatchEvent(new Event("input"));
const quotes = preview.querySelectorAll("blockquote");
const strongs = preview.querySelectorAll("blockquote>strong");
const italics = preview.querySelectorAll("blockquote>strong>em");
assert.lengthOf(preview.children, 1);
assert.lengthOf(quotes, 1);
assert.lengthOf(quotes[0].children, 1);
assert.lengthOf(strongs, 1);
assert.lengthOf(strongs[0].children, 1);
assert.equal(strongs[0].innerText, "this is a quote");
assert.lengthOf(italics, 1);
assert.equal(italics[0].innerText, "quote");

You should have only one script element in your HTML.

js
assert.lengthOf(document.querySelectorAll("script"), 1);

--seed--

--seed-contents--

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Markdown to HTML Converter</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <h1>Markdown to HTML Converter</h1>
    <div id="container">
        <div class="container">
            <h2>Markdown Input:</h2>
            <textarea id="markdown-input" placeholder="Enter your markdown here..."></textarea>
        </div>
        <div class="container">
            <h2>Raw HTML Output:</h2>
            <div id="html-output"></div>
        </div>
        <div class="container">
            <h2>HTML Preview:</h2>
            <div id="preview"></div>
        </div>
    </div>
</body>

</html>
css
* {
     box-sizing: border-box;
}
 body {
     font-family: Arial, sans-serif;
     padding: 20px;
}
 #markdown-input {
     width: 100%;
     height: 100px;
}
 #html-output, #preview {
     height: 100px;
     display: inline-block;
     width: 100%;
     border: 1px solid #ccc;
     padding: 10px;
     margin: auto;
     white-space: pre-wrap;
     background-color: #f9f9f9;
}
 @media (min-width: 600px) {
     #markdown-input, #html-output, #preview {
         height: 200px;
         margin: 0;
    }
     #container {
         display: flex;
         justify-content: space-evenly;
         gap: 10px;
    }
}
js

--solutions--

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Markdown to HTML Converter</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <h1>Markdown to HTML Converter</h1>
    <div id="container">
        <div class="container">
            <h2>Markdown Input:</h2>
            <textarea id="markdown-input" placeholder="Enter your markdown here..."></textarea>
        </div>
        <div class="container">
            <h2>Raw HTML Output:</h2>
            <div id="html-output"></div>
        </div>
        <div class="container">
            <h2>HTML Preview:</h2>
            <div id="preview"></div>
        </div>
    </div>
    <script src="script.js"></script>
</body>

</html>
css
* {
     box-sizing: border-box;
}
 body {
     font-family: Arial, sans-serif;
     padding: 20px;
}
 #markdown-input {
     width: 100%;
     height: 100px;
}
 #html-output, #preview {
     height: 100px;
     display: inline-block;
     width: 100%;
     border: 1px solid #ccc;
     padding: 10px;
     margin: auto;
     white-space: pre-wrap;
     background-color: #f9f9f9;
}
 @media (min-width: 600px) {
     #markdown-input, #html-output, #preview {
         height: 200px;
         margin: 0;
    }
     #container {
         display: flex;
         justify-content: space-evenly;
         gap: 10px;
    }
}
js
const patternReplacementArray = [
    { h6: [/^[ \t]*######[ \t](.+$)/gm, "<h6>$1</h6>"] },
    { h5: [/^[ \t]*#####[ \t](.+$)/gm, "<h5>$1</h5>"] },
    { h4: [/^[ \t]*####[ \t](.+$)/gm, "<h4>$1</h4>"] },
    { h3: [/^[ \t]*###[ \t](.+$)/gm, "<h3>$1</h3>"] },
    { h2: [/^[ \t]*##[ \t](.+$)/gm, "<h2>$1</h2>"] },
    { h1: [/^[ \t]*#[ \t](.+$)/gm, "<h1>$1</h1>"] },
    { hr: [/(?<=\n|^)---(?=\n|$)/, "<hr>"] },
    { strong: [/((?:\*|_){2})(.*?)\1/g, "<strong>$2</strong>"] },
    { em: [/(\*|_)(.*?)\1/g, "<em>$2</em>"] },
    { img: [/!\[\s*(.*?)\s*\]\(\s*(.*?)\s*\)/g, ''] },
    { a: [/\[(.*?)\]\(\s*(.*?)\s*\)/g, '<a href="$2">$1</a>'] },
    { blockquote1: [/^[ \t]*> ([^\n]+?)$/gm, "<blockquote>$1</blockquote>\n"] },
    { blockquote2: [/(?<=<blockquote>)(.+?)<\/blockquote>\n\n^([^<\n]+?)$/gm, "$1 $2</blockquote>\n"] },
    { ul: [/(?:^[-*+]\s+.*?$\n)+\s*?/gm, "<ul>\n$&</ul>"] },
    { ol: [/(?:^\d*\.\s+?\w.*?$\n)+\s*?/gm, "<ol>\n$&</ol>"] },
    { uli: [/^[-*+]\s+(.*?$)\n/gm, "<li>$1</li>\n"] },
    { oli: [/^\d*\.\s+?(.*?$)\n/gm, "<li>$1</li>\n"] },
    { p1: [/^([^<>\s].*?)(?=\n\s*$)/gsm, "<p>$1</p>"] },
    { p2: [/^\w*?(?:<strong>|<em>).+(?:<\/strong>|<\/em>)\s*\w*?[ \t]*$/gm, "<p>$&</p>"] }
]

const markdownInput = document.getElementById('markdown-input');
const convertMarkdown = () => {
    let markdown = markdownInput.value;
    let matchUList = false;
    let matchOList = false;
    patternReplacementArray.forEach((item) => {
        const [key] = Object.keys(item);
        const [pattern, replacement] = Object.values(item)[0];
        if (key === "blockquote2") {
            while (markdown.match(pattern)) {
                markdown = markdown.replace(pattern, replacement);
            }
        } else if (key === "ul") {
            if (markdown.match(pattern)) {
                matchUList = true;
                markdown = markdown.replace(pattern, replacement);
            }

        } else if (key === "ol") {
            if (markdown.match(pattern)) {
                matchOList = true;
                markdown = markdown.replace(pattern, replacement);
            }

        } else if (key === "uli" && !matchUList) {
            return;
        } else if (key === "oli" && !matchOList) {
            return;
        } else {
            markdown = markdown.replace(pattern, replacement);
        }
    })
    return markdown;
}

markdownInput.addEventListener('input', () => {
    const html = convertMarkdown();
    const out = document.getElementById('html-output');
    const preview = document.getElementById('preview');
    out.textContent = html;
    preview.innerHTML = html;
});