relax

Finished
1// Helper function to wrap text with HTML tags
12function wrap(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
6// Helper function to check if text starts with a specific HTML tag
57function isTag(text, tag) {
68 return text.startsWith(`<${tag}>`);
79}
810
11// Generic parser function to convert Markdown delimiters to HTML tags
912function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
13 // Escape special regex characters in delimiter
14 const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15 const pattern = new RegExp(`${escapedDelimiter}(.+?)${escapedDelimiter}`, 'g');
1116 const replacement = `<${tag}>$1</${tag}>`;
1217 return markdown.replace(pattern, replacement);
1318}
1419
15function parse__(markdown) {
20// Parse double underscore emphasis (__text__ -> <strong>text</strong>)
21function parseUnderscoreStrong(markdown) {
1622 return parser(markdown, '__', 'strong');
1723}
1824
19function parse_(markdown) {
25// Parse single underscore emphasis (_text_ -> <em>text</em>)
26function parseUnderscoreEmphasis(markdown) {
2027 return parser(markdown, '_', 'em');
2128}
2229
30// Parse double asterisk emphasis (**text** -> <strong>text</strong>)
31function parseAsteriskStrong(markdown) {
32 return parser(markdown, '**', 'strong');
33}
34
35// Parse single asterisk emphasis (*text* -> <em>text</em>)
36function parseAsteriskEmphasis(markdown) {
37 return parser(markdown, '*', 'em');
38}
39
40// Parse text with emphasis formatting
2341function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
42 // Parse all types of emphasis (both underscore and asterisk based)
43 let parsedText = markdown;
44 parsedText = parseAsteriskStrong(parsedText); // **text** -> <strong>text</strong>
45 parsedText = parseAsteriskEmphasis(parsedText); // *text* -> <em>text</em>
46 parsedText = parseUnderscoreStrong(parsedText); // __text__ -> <strong>text</strong>
47 parsedText = parseUnderscoreEmphasis(parsedText); // _text_ -> <em>text</em>
48
2549 if (list) {
2650 return parsedText;
2751 } else {
2953 }
3054}
3155
56// Parse Markdown headers (# ## ### etc.)
3257function parseHeader(markdown, list) {
33 let count = 0;
58 // Count consecutive # characters at the start
59 let headerLevel = 0;
3460 for (let i = 0; i < markdown.length; i++) {
3561 if (markdown[i] === '#') {
36 count += 1;
62 headerLevel += 1;
3763 } else {
3864 break;
3965 }
4066 }
41 if (count === 0 || count > 6) {
67
68 // Valid headers have 1-6 # characters
69 if (headerLevel === 0 || headerLevel > 6) {
4270 return [null, list];
4371 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
72
73 const headerTag = `h${headerLevel}`;
74 const headerContent = markdown.substring(headerLevel + 1).trim();
75 const headerHtml = wrap(headerContent, headerTag);
76
77 // If we're in a list, close it before the header
4678 if (list) {
4779 return [`</ul>${headerHtml}`, false];
4880 } else {
5082 }
5183}
5284
85// Parse list items (* item)
5386function parseLineItem(markdown, list) {
5487 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
88 // Remove the '* ' prefix and parse the remaining text
89 const itemContent = markdown.substring(2).trim();
90 const innerHtml = wrap(parseText(itemContent, true), 'li');
91
5692 if (list) {
93 // Already in a list, just add the item
5794 return [innerHtml, true];
5895 } else {
96 // Start a new list
5997 return [`<ul>${innerHtml}`, true];
6098 }
6199 }
62100 return [null, list];
63101}
64102
103// Parse regular paragraphs
65104function parseParagraph(markdown, list) {
66105 if (!list) {
106 // Not in a list, just wrap as paragraph
67107 return [parseText(markdown, false), false];
68108 } else {
109 // In a list, close it first then create paragraph
69110 return [`</ul>${parseText(markdown, false)}`, false];
70111 }
71112}
72113
114// Parse a single line of Markdown, determining its type
73115function parseLine(markdown, list) {
116 // Try to parse as header first
74117 let [result, inListAfter] = parseHeader(markdown, list);
118
119 // If not a header, try to parse as list item
75120 if (result === null) {
76121 [result, inListAfter] = parseLineItem(markdown, list);
77122 }
123
124 // If not a list item, parse as paragraph
78125 if (result === null) {
79126 [result, inListAfter] = parseParagraph(markdown, list);
80127 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
83 }
128
129 // At this point, result should never be null as parseParagraph always returns a value
84130 return [result, inListAfter];
85131}
86132
87133/**
88 * @param {string} markdown
89 * @returns {string}
134 * Parse Markdown text and convert it to HTML
135 * @param {string} markdown - The Markdown text to parse
136 * @returns {string} The corresponding HTML
90137 */
91138export function parse(markdown) {
92139 const lines = markdown.split('\n');
93140 let result = '';
94 let list = false;
141 let inList = false; // Track whether we're currently inside a list
142
143 // Process each line
95144 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
145 const [lineResult, newInList] = parseLine(lines[i], inList);
97146 result += lineResult;
98 list = newList;
147 inList = newInList;
99148 }
100 if (list) {
149
150 // Close any open list at the end
151 if (inList) {
101152 return result + '</ul>';
102153 } else {
103154 return result;
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.