Skip to content

Instantly share code, notes, and snippets.

@learosema
Last active September 21, 2024 13:37
Show Gist options
  • Save learosema/046201e873d71b8c8cba321afcf98d53 to your computer and use it in GitHub Desktop.
Save learosema/046201e873d71b8c8cba321afcf98d53 to your computer and use it in GitHub Desktop.
Markdown in ~100 lines of JS
const MAGIC = [
[/^###### (.+)$/g, '<h6>$1</h6>'],
[/^##### (.+)$/g, '<h5>$1</h5>'],
[/^#### (.+)$/g, '<h4>$1</h4>'],
[/^### (.+)$/g, '<h3>$1</h3>'],
[/^## (.+)$/g, '<h2>$1</h2>'],
[/^# (.+)$/g, '<h1>$1</h1>'],
[/ $/g, '<br/>'],
[/_(.+)_/g, '<u>$1</u>'],
[/\*(.+)\*/g, '<em>$1</em>'],
[/\*\*(.+)\*\*/g, '<strong>$1</strong>'],
[/<(https?:\/\/.+)>/g, '<a href="$1">$1</a>'],
[
/\!\[(.+)\]\((.+)\)/g,
'<img src="$2" alt="$1" />'
],
[
/\[(.+)\]\((.+)\)/g,
'<a href="$2">$1</a>'
]
];
const UL_PATTERN = /((\s*)(\-|(?:(?:\d+\.)+)) (.+)\n)+/
const ULLI_PATTERN = /(\s*)(\-|(?:(?:\d+\.)+)) (.+)\n?/g
function inlines(str) {
let result = str
for (const [search, replace] of MAGIC) {
result = result.replace(search, replace);
}
return result
}
function renderList(list) {
let u = Number.isNaN(list.start)
let html = u ? '<ul>' : `<ol${list.start!==1?` start="${list.start}"`:''}>`;
for (const li of list.items) {
html += '<li>' + inlines(li.content);
if (li.childList) {
html+=renderList(li.childList);
}
html += '</li>';
}
return html + (u?'</ul>':'</ol>');
}
function parseList(block) {
const matches = block.matchAll(ULLI_PATTERN);
if (! matches) {
throw new Error('could not be parsed', block);
}
const m = Array.from(matches);
const listItems = m.map(match => ({
indent: match[1].length,
prefix: match[2],
content: match[3],
}));
const parseStart = (str) => {
const idxPattern = str.match(/(\d+)\.$/);
return idxPattern ? parseInt(idxPattern[1]): NaN;
}
const list = {start: parseStart(listItems[0].prefix), items: []};
let currentList = list;
let stack = [];
let last = null;
for (const li of listItems) {
if (last !== null && li.indent > last.indent) {
stack.push(currentList);
currentList = last.childList = {
start: parseStart(li.prefix),
items: []
};
} else
if (last && li.indent < last.indent && stack.length > 0) {
currentList = stack.pop();
}
const item = {...li, childList: null};
currentList.items.push(item);
last = item;
}
return renderList(list);
}
export function markdown(input) {
return input.replace(/\r\n/g,'\n').split('\n\n')
.map(block => block.trim())
.map(block => inlines(block))
.map(block => {
if (UL_PATTERN.test(block)) {
return parseList(block);
}
if (/^<\w+>/.test(block)) {
return block;
}
return `<p>${block}</p>`
}).join('\n').trim();
}
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { markdown } from './markdown.js';
const MD1 = `Lorem Ipsum
Dolor Sit
amet!
`;
const HTML1 = `<p>Lorem Ipsum</p>
<p>Dolor Sit</p>
<p>amet!</p>`;
const MD2 = `# Placeholder Text
Lorem Ipsum Dolor Sit amet consetetur adipiscing elit
## Headline 2
### Headline 3
#### Headline 4
##### Headline 5
###### Headline 6
`;
const HTML2 = `<h1>Placeholder Text</h1>
<p>Lorem Ipsum Dolor Sit amet consetetur adipiscing elit</p>
<h2>Headline 2</h2>
<h3>Headline 3</h3>
<h4>Headline 4</h4>
<h5>Headline 5</h5>
<h6>Headline 6</h6>`
const MD_IMG = `![Lea](lea.jpg)`
const HTML_IMG = `<p><img src="lea.jpg" alt="Lea" /></p>`
const MD_LIST = `# Unordered List
- Eat
- Sleep
- Code
- Repeat
`
const HTML_LIST = `<h1>Unordered List</h1>
<ul><li>Eat</li><li>Sleep</li><li>Code</li><li>Repeat</li></ul>`
const MD_NESTED_LIST = `# Lea
- Pronouns
- she/her
- Likes
- Pasta
- Coding
- Accessibility
`
const HTML_NESTED_LIST = `<h1>Lea</h1>
<ul><li>Pronouns<ul><li>she/her</li></ul></li><li>Likes<ul><li>Pasta</li><li>Coding</li><li>Accessibility</li></ul></li></ul>`
const MD_ORDERED_LIST = `# Lea
1. Frontend Dev
2. Loves Accessibility
3. Hates Ordered Lists
`
const HTML_ORDERED_LIST = `<h1>Lea</h1>
<ol><li>Frontend Dev</li><li>Loves Accessibility</li><li>Hates Ordered Lists</li></ol>`
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 = `<h1>Table of contents</h1>
<ol><li>Accessibility Fundamentals<ol><li>Disability Etiquette</li></ol>` +
`</li><li>Accessibility Myths debunked<ol><li>Accessibility is expensive</li>` +
`<li>Accessibility is ugly</li></ol></li><li>Quiz</li></ol>`
describe('markdown transform', () => {
it('transforms chunks of text into paragraphs', () => {
assert.equal(markdown(MD1), HTML1);
});
it('transforms headline correctly', () => {
assert.equal(markdown(MD2), HTML2);
});
it('transforms links correctly', () => {
const MD_LINK = `This is a [link](https://lea.codes/)`
const HTML_LINK = `<p>This is a <a href="https://lea.codes/">link</a></p>`
const MD_LINK2 = `This is another link: <https://test.de>.`
const HTML_LINK2 = `<p>This is another link: <a href="https://test.de">https://test.de</a>.</p>`
assert.equal(markdown(MD_LINK), HTML_LINK);
assert.equal(markdown(MD_LINK2), HTML_LINK2);
});
it('transforms images correctly', () => {
assert.equal(markdown(MD_IMG), HTML_IMG);
})
it('transforms unordered lists correctly', () => {
assert.equal(markdown(MD_LIST), HTML_LIST);
});
it('transforms unordered nested lists correctly', () => {
assert.equal(markdown(MD_NESTED_LIST), HTML_NESTED_LIST);
});
it('transforms ordered lists correctly', () => {
assert.equal(markdown(MD_ORDERED_LIST), HTML_ORDERED_LIST);
});
it('transforms ordered nested lists correctly', () => {
assert.equal(markdown(MD_NESTED_OL), HTML_NESTED_OL);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment