perfect

Finished
1212 return markdown.replace(pattern, replacement);
1313}
1414
15function parse__(markdown) {
15// Parse bold text (__text__ -> <strong>text</strong>)
16function parseBold(markdown) {
1617 return parser(markdown, '__', 'strong');
1718}
1819
19function parse_(markdown) {
20// Parse italic text (_text_ -> <em>text</em>)
21function parseItalic(markdown) {
2022 return parser(markdown, '_', 'em');
2123}
2224
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
25// Parse text content with inline formatting (bold, italic)
26// If inList is true, returns text without paragraph wrapper
27// If inList is false, wraps text in paragraph tags
28function parseText(markdown, inList) {
29 const parsedText = parseItalic(parseBold(markdown));
30 if (inList) {
2631 return parsedText;
2732 } else {
2833 return wrap(parsedText, 'p');
2934 }
3035}
3136
32function parseHeader(markdown, list) {
33 let count = 0;
37// Parse header markdown (# Header -> <h1>Header</h1>)
38// Supports headers from h1 to h6
39// If currently in a list, closes the list before the header
40function parseHeader(markdown, inList) {
41 // Count consecutive # characters at the start
42 let hashCount = 0;
3443 for (let i = 0; i < markdown.length; i++) {
3544 if (markdown[i] === '#') {
36 count += 1;
45 hashCount += 1;
3746 } else {
3847 break;
3948 }
4049 }
41 if (count === 0 || count > 6) {
42 return [null, list];
50
51 // Valid headers have 1-6 # characters
52 if (hashCount === 0 || hashCount > 6) {
53 return [null, inList];
4354 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
55
56 // Create the header tag (h1, h2, etc.)
57 const headerTag = `h${hashCount}`;
58 const headerContent = markdown.substring(hashCount + 1);
59 const headerHtml = wrap(headerContent, headerTag);
60
61 // If we were in a list, close it before the header
62 if (inList) {
4763 return [`</ul>${headerHtml}`, false];
4864 } else {
4965 return [headerHtml, false];
5066 }
5167}
5268
53function parseLineItem(markdown, list) {
69// Parse list item markdown (* Item -> <li>Item</li>)
70// Handles opening and closing of list tags
71function parseLineItem(markdown, inList) {
5472 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
73 // Remove the '* ' prefix and parse the remaining text
74 const itemContent = markdown.substring(2);
75 const listItemHtml = wrap(parseText(itemContent, true), 'li');
76
77 if (inList) {
78 // Already in a list, just add the list item
79 return [listItemHtml, true];
5880 } else {
59 return [`<ul>${innerHtml}`, true];
81 // Not in a list, open a new list
82 return [`<ul>${listItemHtml}`, true];
6083 }
6184 }
62 return [null, list];
85 return [null, inList];
6386}
6487
65function parseParagraph(markdown, list) {
66 if (!list) {
88// Parse paragraph markdown (plain text -> <p>text</p>)
89// If currently in a list, closes the list before the paragraph
90function parseParagraph(markdown, inList) {
91 if (!inList) {
92 // Not in a list, simply wrap in paragraph tags
6793 return [parseText(markdown, false), false];
6894 } else {
95 // Was in a list, close it before starting the paragraph
6996 return [`</ul>${parseText(markdown, false)}`, false];
7097 }
7198}
7299
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
100// Parse a single line of markdown
101// Tries to parse as header, then list item, then paragraph
102// Returns [parsedHtml, newListState]
103function parseLine(markdown, inList) {
104 // Try parsing as header
105 let [result, newListState] = parseHeader(markdown, inList);
106
107 // If not a header, try parsing as list item
75108 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
109 [result, newListState] = parseLineItem(markdown, inList);
77110 }
111
112 // If not a list item, parse as paragraph
78113 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
114 [result, newListState] = parseParagraph(markdown, inList);
80115 }
116
117 // This should never happen with valid input
81118 if (result === null) {
82 throw new Error('Remove this line and implement the function');
119 throw new Error('Unrecognized markdown line: ' + markdown);
83120 }
84 return [result, inListAfter];
121
122 return [result, newListState];
85123}
86124
87125/**
88 * @param {string} markdown
89 * @returns {string}
126 * Parse markdown text and convert it to HTML
127 * @param {string} markdown - The markdown text to parse
128 * @returns {string} - The resulting HTML
90129 */
91130export function parse(markdown) {
92131 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
132 let htmlResult = '';
133 let inList = false; // Track if we're currently inside a list
134
135 // Process each line of markdown
95136 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
137 let [lineHtml, newListState] = parseLine(lines[i], inList);
138 htmlResult += lineHtml;
139 inList = newListState;
99140 }
100 if (list) {
101 return result + '</ul>';
141
142 // If we're still in a list at the end, close it
143 if (inList) {
144 return htmlResult + '</ul>';
102145 } else {
103 return result;
146 return htmlResult;
104147 }
105148}
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.