Finished
1/**
2 * Wraps text content in HTML tags
3 * @param {string} text - The text content to wrap
4 * @param {string} tag - The HTML tag to use
5 * @returns {string} - The wrapped HTML content
6 */
17function wrap(text, tag) {
28 return `<${tag}>${text}</${tag}>`;
39}
410
11/**
12 * Checks if text starts with a specific HTML tag
13 * @param {string} text - The text to check
14 * @param {string} tag - The HTML tag to look for
15 * @returns {boolean} - True if text starts with the tag
16 */
517function isTag(text, tag) {
618 return text.startsWith(`<${tag}>`);
719}
820
9function parser(markdown, delimiter, tag) {
21/**
22 * Generic parser for markdown text formatting (bold, italic)
23 * @param {string} markdown - The markdown text to parse
24 * @param {string} delimiter - The markdown delimiter (e.g., '__', '_')
25 * @param {string} tag - The HTML tag to use (e.g., 'strong', 'em')
26 * @returns {string} - The HTML with formatting applied
27 */
28function parseTextFormatting(markdown, delimiter, tag) {
1029 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
1130 const replacement = `<${tag}>$1</${tag}>`;
1231 return markdown.replace(pattern, replacement);
1332}
1433
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
34/**
35 * Parses bold text marked with __
36 * @param {string} markdown - The markdown text to parse
37 * @returns {string} - HTML with bold formatting
38 */
39function parseBoldText(markdown) {
40 return parseTextFormatting(markdown, '__', 'strong');
41}
42
43/**
44 * Parses italic text marked with _
45 * @param {string} markdown - The markdown text to parse
46 * @returns {string} - HTML with italic formatting
47 */
48function parseItalicText(markdown) {
49 return parseTextFormatting(markdown, '_', 'em');
1750}
1851
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
52/**
53 * Parses text formatting (bold and italic) without paragraph wrapping
54 * @param {string} markdown - The markdown text to parse
55 * @returns {string} - HTML with formatting applied
56 */
57function parseTextFormattingOnly(markdown) {
58 return parseItalicText(parseBoldText(markdown));
2159}
2260
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
61/**
62 * Parses text formatting (bold and italic) and optionally wraps in paragraph tags
63 * @param {string} markdown - The markdown text to parse
64 * @param {boolean} isInList - Whether this text is inside a list item
65 * @returns {string} - HTML with formatting applied, optionally wrapped in <p> tags
66 */
67function parseTextWithFormatting(markdown, isInList) {
68 const parsedText = parseTextFormattingOnly(markdown);
69 if (isInList) {
2670 return parsedText;
2771 } else {
2872 return wrap(parsedText, 'p');
2973 }
3074}
3175
32function parseHeader(markdown, list) {
33 let count = 0;
76/**
77 * Parses markdown headers (lines starting with #)
78 * @param {string} markdown - The markdown line to parse
79 * @param {boolean} currentlyInList - Whether we're currently inside a list
80 * @returns {Array} - [htmlResult, newListState] or [null, currentListState] if not a header
81 */
82function parseHeader(markdown, currentlyInList) {
83 // Count consecutive # symbols at the start of the line
84 let headerLevel = 0;
3485 for (let i = 0; i < markdown.length; i++) {
3586 if (markdown[i] === '#') {
36 count += 1;
87 headerLevel += 1;
3788 } else {
3889 break;
3990 }
4091 }
41 if (count === 0 || count > 6) {
42 return [null, list];
92
93 // Valid headers are h1-h6 (1-6 # symbols)
94 if (headerLevel === 0 || headerLevel > 6) {
95 return [null, currentlyInList];
4396 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
97
98 const headerTag = `h${headerLevel}`;
99 const headerText = markdown.substring(headerLevel + 1); // Skip the # and space
100 const formattedHeaderText = parseTextFormattingOnly(headerText);
101 const headerHtml = wrap(formattedHeaderText, headerTag);
102
103 // If we were in a list, close it before the header
104 if (currentlyInList) {
47105 return [`</ul>${headerHtml}`, false];
48106 } else {
49107 return [headerHtml, false];
50108 }
51109}
52110
53function parseLineItem(markdown, list) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
58 } else {
59 return [`<ul>${innerHtml}`, true];
60 }
111/**
112 * Parses markdown list items (lines starting with *)
113 * @param {string} markdown - The markdown line to parse
114 * @param {boolean} currentlyInList - Whether we're currently inside a list
115 * @returns {Array} - [htmlResult, newListState] or [null, currentListState] if not a list item
116 */
117function parseListItem(markdown, currentlyInList) {
118 if (!markdown.startsWith('*')) {
119 return [null, currentlyInList];
120 }
121
122 // Extract the list item text (skip the * and space)
123 const itemText = markdown.substring(2);
124 const listItemHtml = wrap(parseTextWithFormatting(itemText, true), 'li');
125
126 if (currentlyInList) {
127 // Already in a list, just add the item
128 return [listItemHtml, true];
129 } else {
130 // Start a new list
131 return [`<ul>${listItemHtml}`, true];
61132 }
62 return [null, list];
63133}
64134
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
135/**
136 * Parses regular text as paragraphs
137 * @param {string} markdown - The markdown line to parse
138 * @param {boolean} currentlyInList - Whether we're currently inside a list
139 * @returns {Array} - [htmlResult, newListState]
140 */
141function parseParagraph(markdown, currentlyInList) {
142 const parsedText = parseTextWithFormatting(markdown, false);
143
144 if (!currentlyInList) {
145 // Not in a list, just return the paragraph
146 return [parsedText, false];
68147 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
148 // Close the list before starting the paragraph
149 return [`</ul>${parsedText}`, false];
70150 }
71151}
72152
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
153/**
154 * Parses a single line of markdown by trying different parsers in order
155 * @param {string} markdown - The markdown line to parse
156 * @param {boolean} currentlyInList - Whether we're currently inside a list
157 * @returns {Array} - [htmlResult, newListState]
158 */
159function parseLine(markdown, currentlyInList) {
160 // Try to parse as header first
161 let [result, newListState] = parseHeader(markdown, currentlyInList);
162
163 // If not a header, try to parse as list item
78164 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
165 [result, newListState] = parseListItem(markdown, currentlyInList);
80166 }
167
168 // If not a list item, parse as paragraph
81169 if (result === null) {
82 throw new Error('Remove this line and implement the function');
170 [result, newListState] = parseParagraph(markdown, currentlyInList);
83171 }
84 return [result, inListAfter];
172
173 return [result, newListState];
85174}
86175
87176/**
88 * @param {string} markdown
89 * @returns {string}
177 * Main entry point for parsing markdown text to HTML
178 * @param {string} markdown - The markdown text to parse
179 * @returns {string} - The resulting HTML
90180 */
91181export function parse(markdown) {
182 // Split the markdown into individual lines
92183 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
184 let htmlResult = '';
185 let isInList = false;
186
187 // Process each line
95188 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
189 let [lineHtml, newListState] = parseLine(lines[i], isInList);
190 htmlResult += lineHtml;
191 isInList = newListState;
99192 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
193
194 // Close any open list at the end
195 if (isInList) {
196 htmlResult += '</ul>';
104197 }
198
199 return htmlResult;
105200}
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.