relax

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}>`);
6// Helper function to parse inline formatting (__ for strong, _ for emphasis)
7function parseInlineFormatting(text) {
8 // Parse bold text (__text__ -> <strong>text</strong>)
9 // Use non-greedy matching to handle multiple instances correctly
10 text = text.replace(/__(.+?)__/g, '<strong>$1</strong>');
11 // Parse italic text (_text_ -> <em>text</em>)
12 // Use non-greedy matching to handle multiple instances correctly
13 text = text.replace(/_(.+?)_/g, '<em>$1</em>');
14 return text;
715}
816
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
13}
14
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
22
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
30}
31
32function parseHeader(markdown, list) {
17// Parse header elements (# Header -> <h1>Header</h1>)
18function parseHeader(line) {
3319 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
20 // Count consecutive # characters at the start of the line
21 for (let i = 0; i < line.length; i++) {
22 if (line[i] === '#') {
3623 count += 1;
3724 } else {
3825 break;
3926 }
4027 }
28
29 // Valid headers have 1-6 # characters
4130 if (count === 0 || count > 6) {
42 return [null, list];
31 return null;
4332 }
33
34 // Extract header content (skip # characters and the following space)
35 const headerContent = line.substring(count + 1);
4436 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
50 }
37
38 return wrap(parseInlineFormatting(headerContent), headerTag);
5139}
5240
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 }
41// Parse list items (* item -> <li>item</li>)
42function parseListItem(line) {
43 if (line.startsWith('*')) {
44 // Extract list item content (skip "* " prefix)
45 const itemContent = line.substring(2);
46 return wrap(parseInlineFormatting(itemContent), 'li');
6147 }
62 return [null, list];
48 return null;
6349}
6450
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
51// Parse paragraph text
52function parseParagraph(line) {
53 return wrap(parseInlineFormatting(line), 'p');
7154}
7255
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
56// Parse a single line of markdown
57function parseLine(line) {
58 // Handle empty lines
59 if (line.trim() === '') {
60 return { type: 'empty', content: '' };
7761 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
62
63 // Try to parse as header
64 const headerResult = parseHeader(line);
65 if (headerResult !== null) {
66 return { type: 'header', content: headerResult };
8067 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
68
69 // Try to parse as list item
70 const listItemResult = parseListItem(line);
71 if (listItemResult !== null) {
72 return { type: 'listItem', content: listItemResult };
8373 }
84 return [result, inListAfter];
74
75 // Default to paragraph
76 return { type: 'paragraph', content: parseParagraph(line) };
8577}
8678
8779/**
88 * @param {string} markdown
89 * @returns {string}
80 * Parse markdown text and convert it to HTML
81 * Supports:
82 * - Headers (# Header 1, ## Header 2, etc.)
83 * - Bold text (__bold__)
84 * - Italic text (_italic_)
85 * - Unordered lists (* item)
86 *
87 * @param {string} markdown - The markdown text to parse
88 * @returns {string} - The resulting HTML
9089 */
9190export function parse(markdown) {
9291 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
92 let html = '';
93 let inList = false;
94
9595 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
96 const line = lines[i];
97 const parsed = parseLine(line);
98
99 // Handle empty lines
100 if (parsed.type === 'empty') {
101 // Close any open list
102 if (inList) {
103 html += '</ul>';
104 inList = false;
105 }
106 // Skip empty lines (don't generate empty paragraphs)
107 continue;
108 }
109
110 // Handle list opening and closing
111 if (parsed.type === 'listItem' && !inList) {
112 // Start a new list
113 html += '<ul>' + parsed.content;
114 inList = true;
115 } else if (parsed.type === 'listItem' && inList) {
116 // Continue existing list
117 html += parsed.content;
118 } else if (parsed.type !== 'listItem' && inList) {
119 // Close list before non-list element
120 html += '</ul>' + parsed.content;
121 inList = false;
122 } else {
123 // Handle non-list elements
124 html += parsed.content;
125 inList = false;
126 }
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 html += '</ul>';
104132 }
133
134 return html;
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.