From b999f26cc0f069a38cf85a7bbb3cc0fd1b2cf466 Mon Sep 17 00:00:00 2001 From: Lea Rosema Date: Sat, 28 Sep 2024 22:01:26 +0200 Subject: [PATCH] fix: slightly more complete markdown (still a subset of it) --- src/transforms/markdown.js | 53 +++++- .../mardown/0006-inline-elements.html | 7 - tests/fixtures/mardown/0010-blockquote.md | 9 - .../0000-paragraphs.html | 0 .../{mardown => markdown}/0000-paragraphs.md | 0 .../{mardown => markdown}/0001-headlines.html | 0 .../{mardown => markdown}/0001-headlines.md | 0 .../{mardown => markdown}/0002-images.html | 0 .../{mardown => markdown}/0002-images.md | 0 .../{mardown => markdown}/0003-links.html | 0 .../{mardown => markdown}/0003-links.md | 0 .../{mardown => markdown}/0004-escaping.html | 0 .../{mardown => markdown}/0004-escaping.md | 0 .../markdown/0005-inline-elements.html | 9 + .../0005-inline-elements.md | 2 + .../0006-unordered-list.html | 0 .../0006-unordered-list.md | 0 .../0007-nested-list.html | 0 .../{mardown => markdown}/0007-nested-list.md | 0 .../0008-ordered-list.html | 0 .../0008-ordered-list.md | 0 .../0009-nested-ordered-list.html | 1 + .../0009-nested-ordered-list.md | 0 tests/fixtures/markdown/0010-blockquote.html | 9 + tests/fixtures/markdown/0010-blockquote.md | 7 + tests/fixtures/markdown/0011-code-block.html | 19 ++ .../{mardown => markdown}/0011-code-block.md | 8 + tests/transforms/markdown.test.js | 169 ++++++------------ tests/transforms/template-data.test.js | 2 +- 29 files changed, 162 insertions(+), 133 deletions(-) delete mode 100644 tests/fixtures/mardown/0006-inline-elements.html delete mode 100644 tests/fixtures/mardown/0010-blockquote.md rename tests/fixtures/{mardown => markdown}/0000-paragraphs.html (100%) rename tests/fixtures/{mardown => markdown}/0000-paragraphs.md (100%) rename tests/fixtures/{mardown => markdown}/0001-headlines.html (100%) rename tests/fixtures/{mardown => markdown}/0001-headlines.md (100%) rename tests/fixtures/{mardown => markdown}/0002-images.html (100%) rename tests/fixtures/{mardown => markdown}/0002-images.md (100%) rename tests/fixtures/{mardown => markdown}/0003-links.html (100%) rename tests/fixtures/{mardown => markdown}/0003-links.md (100%) rename tests/fixtures/{mardown => markdown}/0004-escaping.html (100%) rename tests/fixtures/{mardown => markdown}/0004-escaping.md (100%) create mode 100644 tests/fixtures/markdown/0005-inline-elements.html rename tests/fixtures/{mardown => markdown}/0005-inline-elements.md (87%) rename tests/fixtures/{mardown => markdown}/0006-unordered-list.html (100%) rename tests/fixtures/{mardown => markdown}/0006-unordered-list.md (100%) rename tests/fixtures/{mardown => markdown}/0007-nested-list.html (100%) rename tests/fixtures/{mardown => markdown}/0007-nested-list.md (100%) rename tests/fixtures/{mardown => markdown}/0008-ordered-list.html (100%) rename tests/fixtures/{mardown => markdown}/0008-ordered-list.md (100%) rename tests/fixtures/{mardown => markdown}/0009-nested-ordered-list.html (99%) rename tests/fixtures/{mardown => markdown}/0009-nested-ordered-list.md (100%) create mode 100644 tests/fixtures/markdown/0010-blockquote.html create mode 100644 tests/fixtures/markdown/0010-blockquote.md create mode 100644 tests/fixtures/markdown/0011-code-block.html rename tests/fixtures/{mardown => markdown}/0011-code-block.md (67%) diff --git a/src/transforms/markdown.js b/src/transforms/markdown.js index 99224d4..e10cb88 100644 --- a/src/transforms/markdown.js +++ b/src/transforms/markdown.js @@ -5,10 +5,12 @@ const MAGIC = [ [/^### (.+)$/g, '

$1

'], [/^## (.+)$/g, '

$1

'], [/^# (.+)$/g, '

$1

'], - [/ $/g, '
'], - [/_(.+)_/g, '$1'], - [/\*(.+)\*/g, '$1'], + [/ $/gm, '
'], + [/__(.+)__/g, '$1'], + [/_(.+)_/g, '$1'], [/\*\*(.+)\*\*/g, '$1'], + [/\*(.+)\*/g, '$1'], + [/`(.+)`/, '$1'], [/<(https?:\/\/.+)>/g, '$1'], [ /\!\[(.+)\]\((.+)\)/g, @@ -83,16 +85,55 @@ function parseList(block) { return renderList(list); } -export function markdown(input) { - return input.replace(/\r\n/g,'\n').split('\n\n') +function codeblocks(str, snippetStore) { + let counter = 1; + return str.split(/\n```/g).map((part, idx) => { + if (idx % 2 === 0) { + return part; + } + const lf = part.indexOf('\n'); + if (lf === -1) { + return part; + } + const lang = part.slice(0, lf); + const code = part.slice(lf + 1); + const key = 'MARKDOWNSNIPPET' + (counter++) + const l = lang ? ` class="language-${lang}"` : ''; + snippetStore.set(key, + `${code.replace(/&/g, '&').replace(//g, '>')}\n` + ); + + return `{{ ${key} }}` + }).join('\n').trim(); +} + +export function markdownEscape(str) { + return str.replace(/\\([a-z\/\\`\{\}])/, '$1').replace(/(.?)([<>&])(.?)/g, + (_, before, char, after) => + before + (/[^\s]+/.test(before) || /[^\s]+/.test(after) ? char:`&${{'<':'lt','>':'gt','&':'amp'}[char]};`) + after + ); +} + +export function markdown(input, escape = true) { + const vars = new Map(); + const esc = (str) => escape?markdownEscape(str):str; + return esc(codeblocks(input.replace(/\r\n/g,'\n'), vars).split('\n\n') .map(block => inlines(block.trim())) .map(block => { if (UL_PATTERN.test(block)) { return parseList(block); } + if (block.startsWith('> ')) { + return `
\n${block.replace(/^> /gm, '')}\n
` + } if (/^<\w+>/.test(block)) { return block; } + if (/\{\{ MARKDOWNSNIPPET\d+ \}\}/.test(block)) { + return block; + } return `

${block}

` - }).join('\n').trim(); + }).join('\n\n')).replace(/\{\{\s*(\w+)\s*\}\}/g, + (outer, expr) => (vars.get(expr)) ? vars.get(expr) : outer + ).trim() + '\n'; } diff --git a/tests/fixtures/mardown/0006-inline-elements.html b/tests/fixtures/mardown/0006-inline-elements.html deleted file mode 100644 index 8b24e73..0000000 --- a/tests/fixtures/mardown/0006-inline-elements.html +++ /dev/null @@ -1,7 +0,0 @@ -

These are some inline elements.

- -

There are more inline elements.

- -

Lorem inline dolor sit amet.

- -

Inline code element.

diff --git a/tests/fixtures/mardown/0010-blockquote.md b/tests/fixtures/mardown/0010-blockquote.md deleted file mode 100644 index f8a032f..0000000 --- a/tests/fixtures/mardown/0010-blockquote.md +++ /dev/null @@ -1,9 +0,0 @@ -This is a blockquote - -> To be or not to be, that is -> the question. -> -- Shakespear - -This is a normal paragraph. - -(TODO look up spec) diff --git a/tests/fixtures/mardown/0000-paragraphs.html b/tests/fixtures/markdown/0000-paragraphs.html similarity index 100% rename from tests/fixtures/mardown/0000-paragraphs.html rename to tests/fixtures/markdown/0000-paragraphs.html diff --git a/tests/fixtures/mardown/0000-paragraphs.md b/tests/fixtures/markdown/0000-paragraphs.md similarity index 100% rename from tests/fixtures/mardown/0000-paragraphs.md rename to tests/fixtures/markdown/0000-paragraphs.md diff --git a/tests/fixtures/mardown/0001-headlines.html b/tests/fixtures/markdown/0001-headlines.html similarity index 100% rename from tests/fixtures/mardown/0001-headlines.html rename to tests/fixtures/markdown/0001-headlines.html diff --git a/tests/fixtures/mardown/0001-headlines.md b/tests/fixtures/markdown/0001-headlines.md similarity index 100% rename from tests/fixtures/mardown/0001-headlines.md rename to tests/fixtures/markdown/0001-headlines.md diff --git a/tests/fixtures/mardown/0002-images.html b/tests/fixtures/markdown/0002-images.html similarity index 100% rename from tests/fixtures/mardown/0002-images.html rename to tests/fixtures/markdown/0002-images.html diff --git a/tests/fixtures/mardown/0002-images.md b/tests/fixtures/markdown/0002-images.md similarity index 100% rename from tests/fixtures/mardown/0002-images.md rename to tests/fixtures/markdown/0002-images.md diff --git a/tests/fixtures/mardown/0003-links.html b/tests/fixtures/markdown/0003-links.html similarity index 100% rename from tests/fixtures/mardown/0003-links.html rename to tests/fixtures/markdown/0003-links.html diff --git a/tests/fixtures/mardown/0003-links.md b/tests/fixtures/markdown/0003-links.md similarity index 100% rename from tests/fixtures/mardown/0003-links.md rename to tests/fixtures/markdown/0003-links.md diff --git a/tests/fixtures/mardown/0004-escaping.html b/tests/fixtures/markdown/0004-escaping.html similarity index 100% rename from tests/fixtures/mardown/0004-escaping.html rename to tests/fixtures/markdown/0004-escaping.html diff --git a/tests/fixtures/mardown/0004-escaping.md b/tests/fixtures/markdown/0004-escaping.md similarity index 100% rename from tests/fixtures/mardown/0004-escaping.md rename to tests/fixtures/markdown/0004-escaping.md diff --git a/tests/fixtures/markdown/0005-inline-elements.html b/tests/fixtures/markdown/0005-inline-elements.html new file mode 100644 index 0000000..a71d83a --- /dev/null +++ b/tests/fixtures/markdown/0005-inline-elements.html @@ -0,0 +1,9 @@ +

Inline Elements

+ +

These are some inline elements.

+ +

There are more inline elements.

+ +

Lorem inline dolor sit amet.

+ +

Inline code element.

diff --git a/tests/fixtures/mardown/0005-inline-elements.md b/tests/fixtures/markdown/0005-inline-elements.md similarity index 87% rename from tests/fixtures/mardown/0005-inline-elements.md rename to tests/fixtures/markdown/0005-inline-elements.md index da604ad..f4933b9 100644 --- a/tests/fixtures/mardown/0005-inline-elements.md +++ b/tests/fixtures/markdown/0005-inline-elements.md @@ -1,3 +1,5 @@ +# Inline Elements + These are some _inline elements_. There are more *inline elements*. diff --git a/tests/fixtures/mardown/0006-unordered-list.html b/tests/fixtures/markdown/0006-unordered-list.html similarity index 100% rename from tests/fixtures/mardown/0006-unordered-list.html rename to tests/fixtures/markdown/0006-unordered-list.html diff --git a/tests/fixtures/mardown/0006-unordered-list.md b/tests/fixtures/markdown/0006-unordered-list.md similarity index 100% rename from tests/fixtures/mardown/0006-unordered-list.md rename to tests/fixtures/markdown/0006-unordered-list.md diff --git a/tests/fixtures/mardown/0007-nested-list.html b/tests/fixtures/markdown/0007-nested-list.html similarity index 100% rename from tests/fixtures/mardown/0007-nested-list.html rename to tests/fixtures/markdown/0007-nested-list.html diff --git a/tests/fixtures/mardown/0007-nested-list.md b/tests/fixtures/markdown/0007-nested-list.md similarity index 100% rename from tests/fixtures/mardown/0007-nested-list.md rename to tests/fixtures/markdown/0007-nested-list.md diff --git a/tests/fixtures/mardown/0008-ordered-list.html b/tests/fixtures/markdown/0008-ordered-list.html similarity index 100% rename from tests/fixtures/mardown/0008-ordered-list.html rename to tests/fixtures/markdown/0008-ordered-list.html diff --git a/tests/fixtures/mardown/0008-ordered-list.md b/tests/fixtures/markdown/0008-ordered-list.md similarity index 100% rename from tests/fixtures/mardown/0008-ordered-list.md rename to tests/fixtures/markdown/0008-ordered-list.md diff --git a/tests/fixtures/mardown/0009-nested-ordered-list.html b/tests/fixtures/markdown/0009-nested-ordered-list.html similarity index 99% rename from tests/fixtures/mardown/0009-nested-ordered-list.html rename to tests/fixtures/markdown/0009-nested-ordered-list.html index 49378ef..01e70b2 100644 --- a/tests/fixtures/mardown/0009-nested-ordered-list.html +++ b/tests/fixtures/markdown/0009-nested-ordered-list.html @@ -1,2 +1,3 @@

Table of contents

+
  1. Accessibility Fundamentals
    1. Disability Etiquette
  2. Accessibility Myths debunked
    1. Accessibility is expensive
    2. Accessibility is ugly
  3. Quiz
diff --git a/tests/fixtures/mardown/0009-nested-ordered-list.md b/tests/fixtures/markdown/0009-nested-ordered-list.md similarity index 100% rename from tests/fixtures/mardown/0009-nested-ordered-list.md rename to tests/fixtures/markdown/0009-nested-ordered-list.md diff --git a/tests/fixtures/markdown/0010-blockquote.html b/tests/fixtures/markdown/0010-blockquote.html new file mode 100644 index 0000000..91320f4 --- /dev/null +++ b/tests/fixtures/markdown/0010-blockquote.html @@ -0,0 +1,9 @@ +

This is a blockquote test

+ +
+To be or not to be, that is
+the question. +-- Shakespear +
+ +

This is a normal paragraph.

diff --git a/tests/fixtures/markdown/0010-blockquote.md b/tests/fixtures/markdown/0010-blockquote.md new file mode 100644 index 0000000..063ea78 --- /dev/null +++ b/tests/fixtures/markdown/0010-blockquote.md @@ -0,0 +1,7 @@ +# This is a blockquote test + +> To be or not to be, that is +> the question. +> -- Shakespear + +This is a normal paragraph. diff --git a/tests/fixtures/markdown/0011-code-block.html b/tests/fixtures/markdown/0011-code-block.html new file mode 100644 index 0000000..55ce0be --- /dev/null +++ b/tests/fixtures/markdown/0011-code-block.html @@ -0,0 +1,19 @@ +

Hello

+ +

This is a code block below:

+ +
#include <iostream>
+
+using namespace std;
+
+int main() 
+{
+    cout << "Hello World" << endl;
+    return 0;
+}
+
+ +

That's another code block:

+ +
npm install everything
+
diff --git a/tests/fixtures/mardown/0011-code-block.md b/tests/fixtures/markdown/0011-code-block.md similarity index 67% rename from tests/fixtures/mardown/0011-code-block.md rename to tests/fixtures/markdown/0011-code-block.md index 6a88c5f..11190ad 100644 --- a/tests/fixtures/mardown/0011-code-block.md +++ b/tests/fixtures/markdown/0011-code-block.md @@ -1,3 +1,5 @@ +# Hello + This is a code block below: ```cpp @@ -11,3 +13,9 @@ int main() return 0; } ``` + +That's another code block: + +```sh +npm install everything +``` diff --git a/tests/transforms/markdown.test.js b/tests/transforms/markdown.test.js index 7034f9e..7ece670 100644 --- a/tests/transforms/markdown.test.js +++ b/tests/transforms/markdown.test.js @@ -1,133 +1,82 @@ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; -import { markdown } from '../../src/transforms/markdown.js'; - -const MD1 = `Lorem Ipsum - -Dolor Sit - -amet! -`; - -const HTML1 = `

Lorem Ipsum

-

Dolor Sit

-

amet!

`; - -const MD2 = `# Placeholder Text - -Lorem Ipsum Dolor Sit amet consetetur adipiscing elit - -## Headline 2 - -### Headline 3 - -#### Headline 4 - -##### Headline 5 - -###### Headline 6 -`; - -const HTML2 = `

Placeholder Text

-

Lorem Ipsum Dolor Sit amet consetetur adipiscing elit

-

Headline 2

-

Headline 3

-

Headline 4

-
Headline 5
-
Headline 6
` - -const MD_IMG = `![Lea](lea.jpg)` -const HTML_IMG = `

Lea

` - -const MD_LIST = `# Unordered List - -- Eat -- Sleep -- Code -- Repeat -` - -const HTML_LIST = `

Unordered List

-
  • Eat
  • Sleep
  • Code
  • Repeat
` +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; -const MD_NESTED_LIST = `# Lea - -- Pronouns - - she/her -- Likes - - Pasta - - Coding - - Accessibility -` - -const HTML_NESTED_LIST = `

Lea

-
  • Pronouns
    • she/her
  • Likes
    • Pasta
    • Coding
    • Accessibility
` - -const MD_ORDERED_LIST = `# Lea - -1. Frontend Dev -2. Loves Accessibility -3. Hates Ordered Lists -` - - -const HTML_ORDERED_LIST = `

Lea

-
  1. Frontend Dev
  2. Loves Accessibility
  3. Hates Ordered Lists
` - -const MD_NESTED_OL = `# Table of contents - -1. Accessibility Fundamentals - 1.1. Disability Etiquette -2. Accessibility Myths debunked - 2.1. Accessibility is expensive - 2.2. Accessibility is ugly -3. Quiz -` - -const HTML_NESTED_OL = `

Table of contents

-
  1. Accessibility Fundamentals
    1. Disability Etiquette
    ` + -`
  2. Accessibility Myths debunked
    1. Accessibility is expensive
    2. ` + -`
    3. Accessibility is ugly
  3. Quiz
` +import { markdown } from '../../src/transforms/markdown.js'; -describe('markdown transform', () => { +describe('markdown', () => { + + const l = async (file) => { + const filename = path.resolve('tests/fixtures/markdown',file); + const inputFile = filename + '.md'; + const outputFile = filename + '.html'; + const [input, output] = await Promise.all([ + readFile(inputFile, 'utf8'), + readFile(outputFile, 'utf8') + ]); + return [input, output] + } + + it('transforms chunks of text into paragraphs', async () => { + const [input, output] = await l('0000-paragraphs'); + assert.equal(markdown(input), output); + }); - it('transforms chunks of text into paragraphs', () => { - assert.equal(markdown(MD1), HTML1); + it('transforms headline correctly', async () => { + const [input, output] = await l('0001-headlines'); + assert.equal(markdown(input), output); }); + + it('transforms images correctly', async () => { + const [input, output] = await l('0002-images'); + assert.equal(markdown(input), output); + }) - it('transforms headline correctly', () => { - assert.equal(markdown(MD2), HTML2); + it('transforms links correctly', async () => { + const [input, output] = await l('0003-links'); + assert.equal(markdown(input), output); }); - it('transforms links correctly', () => { - const MD_LINK = `This is a [link](https://lea.codes/)` - const HTML_LINK = `

This is a link

` + it('transforms espaces correctly', async () => { + const [input, output] = await l('0004-escaping'); + assert.equal(markdown(input), output); + }); - const MD_LINK2 = `This is another link: .` - const HTML_LINK2 = `

This is another link: https://test.de.

` + it('transforms inline-elements', async () => { + const [input, output] = await l('0005-inline-elements'); + assert.equal(markdown(input), output); + }); - assert.equal(markdown(MD_LINK), HTML_LINK); - assert.equal(markdown(MD_LINK2), HTML_LINK2); + it('transforms unordered lists correctly', async () => { + const [input, output] = await l('0006-unordered-list'); + assert.equal(markdown(input), output); }); - it('transforms images correctly', () => { - assert.equal(markdown(MD_IMG), HTML_IMG); - }) + it('transforms unordered nested lists correctly', async () => { + const [input, output] = await l('0007-nested-list'); + assert.equal(markdown(input), output); + }); - it('transforms unordered lists correctly', () => { - assert.equal(markdown(MD_LIST), HTML_LIST); + it('transforms ordered lists correctly', async () => { + const [input, output] = await l('0008-ordered-list'); + assert.equal(markdown(input), output); }); - it('transforms unordered nested lists correctly', () => { - assert.equal(markdown(MD_NESTED_LIST), HTML_NESTED_LIST); + it('transforms ordered nested lists correctly', async () => { + const [input, output] = await l('0009-nested-ordered-list'); + assert.equal(markdown(input), output); }); - it('transforms ordered lists correctly', () => { - assert.equal(markdown(MD_ORDERED_LIST), HTML_ORDERED_LIST); + it('transforms blockquotes correctly', async () => { + const [input, output] = await l('0010-blockquote'); + assert.equal(markdown(input), output); }); - it('transforms ordered nested lists correctly', () => { - assert.equal(markdown(MD_NESTED_OL), HTML_NESTED_OL); + it('transforms code blocks correctly', async () => { + const [input, output] = await l('0011-code-block'); + assert.equal(markdown(input), output); }); + }); diff --git a/tests/transforms/template-data.test.js b/tests/transforms/template-data.test.js index 0ecbab5..909d05a 100644 --- a/tests/transforms/template-data.test.js +++ b/tests/transforms/template-data.test.js @@ -83,6 +83,6 @@ describe('handleTemplateFile function', () => { const result = await handleTemplateFile(config, {title: 'Lea was here'}, 'index.md'); assert.equal(result.filename, 'public/index.html'); - assert.equal(result.content, '

Lea was here

\n

An article by Lea Rosema

') + assert.equal(result.content, '

Lea was here

\n\n

An article by Lea Rosema

\n') }); });