Finished
1// Helper function to wrap text in HTML tags
12function wrap(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
8
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
6// Parse bold text (double underscores) - matches original behavior
7function parseBold(markdown) {
8 const pattern = new RegExp(`__(.+)__`);
9 const replacement = `<strong>$1</strong>`;
1210 return markdown.replace(pattern, replacement);
1311}
1412
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
13// Parse italic text (single underscores) - matches original behavior exactly
14// Uses the correct pattern: _(.)_ (single underscore at both ends)
15function parseItalic(markdown) {
16 const pattern = new RegExp(`_(.+)_`);
17 const replacement = `<em>$1</em>`;
18 return markdown.replace(pattern, replacement);
2119}
2220
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
21// Parse inline formatting (bold and italic)
22function parseInlineFormatting(markdown) {
23 // Parse bold first, then italic to avoid conflicts
24 return parseItalic(parseBold(markdown));
3025}
3126
32function parseHeader(markdown, list) {
33 let count = 0;
27// Parse header elements (#, ##, ###, etc.)
28function parseHeader(markdown, currentlyInList) {
29 // Count consecutive # characters at the beginning
30 let headerLevel = 0;
3431 for (let i = 0; i < markdown.length; i++) {
3532 if (markdown[i] === '#') {
36 count += 1;
33 headerLevel += 1;
3734 } else {
3835 break;
3936 }
4037 }
41 if (count === 0 || count > 6) {
42 return [null, list];
38
39 // Valid headers are 1-6 # characters
40 if (headerLevel === 0 || headerLevel > 6) {
41 return [null, currentlyInList];
4342 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
43
44 // Create the header tag (h1, h2, etc.)
45 const headerTag = `h${headerLevel}`;
46 const headerContent = markdown.substring(headerLevel + 1);
47 const headerHtml = wrap(headerContent, headerTag);
48
49 // Close list if we were in one
50 if (currentlyInList) {
4751 return [`</ul>${headerHtml}`, false];
4852 } else {
4953 return [headerHtml, false];
5054 }
5155}
5256
53function parseLineItem(markdown, list) {
57// Parse list items (* item)
58function parseListItem(markdown, currentlyInList) {
5459 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
60 const itemContent = markdown.substring(2); // Skip '* '
61 const formattedContent = parseInlineFormatting(itemContent);
62 const listItemHtml = wrap(formattedContent, 'li');
63
64 if (currentlyInList) {
65 // Continue existing list
66 return [listItemHtml, true];
5867 } else {
59 return [`<ul>${innerHtml}`, true];
68 // Start new list
69 return [`<ul>${listItemHtml}`, true];
6070 }
6171 }
62 return [null, list];
72 return [null, currentlyInList];
6373}
6474
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
75// Parse paragraph text
76function parseParagraph(markdown, currentlyInList) {
77 const formattedText = parseInlineFormatting(markdown);
78
79 if (currentlyInList) {
80 // Close list before starting paragraph
81 return [`</ul>${wrap(formattedText, 'p')}`, false];
6882 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
83 // Regular paragraph
84 return [wrap(formattedText, 'p'), false];
7085 }
7186}
7287
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
88// Parse a single line of markdown
89function parseLine(markdown, currentlyInList) {
90 // Try to parse as header
91 let [result, newListState] = parseHeader(markdown, currentlyInList);
92 if (result !== null) {
93 return [result, newListState];
7794 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
95
96 // Try to parse as list item
97 [result, newListState] = parseListItem(markdown, currentlyInList);
98 if (result !== null) {
99 return [result, newListState];
80100 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
101
102 // Parse as paragraph (default)
103 [result, newListState] = parseParagraph(markdown, currentlyInList);
104 if (result !== null) {
105 return [result, newListState];
83106 }
84 return [result, inListAfter];
107
108 // This should never happen with valid input
109 throw new Error('Unable to parse line: ' + markdown);
85110}
86111
87112/**
88 * @param {string} markdown
89 * @returns {string}
113 * Parse markdown text and convert it to HTML
114 * @param {string} markdown - The markdown text to parse
115 * @returns {string} - The resulting HTML
90116 */
91117export function parse(markdown) {
92118 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
119 let htmlResult = '';
120 let inList = false;
121
122 // Process each line
123 for (const line of lines) {
124 let [lineHtml, newListState] = parseLine(line, inList);
125 htmlResult += lineHtml;
126 inList = newListState;
99127 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
128
129 // Close any open list at the end
130 if (inList) {
131 htmlResult += '</ul>';
104132 }
133
134 return htmlResult;
105135}
Test NameStatus
parses normal text as a paragraph
Pass
parsing italics
Pass
parsing bold text
Pass
mixed normal, italics and bold text
Pass
with h1 header level
Pass
with h2 header level
Pass
with h3 header level
Pass
with h4 header level
Pass
with h5 header level
Pass
with h6 header level
Pass
with h7 header level
Pass
unordered lists
Pass
with a little bit of everything
Pass
with markdown symbols in the header text that should not be interpreted
Pass
with markdown symbols in the list item text that should not be interpreted
Pass
with markdown symbols in the paragraph text that should not be interpreted
Pass
unordered lists close properly with preceding and following lines
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.