kale

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// Parse bold text (__text__ -> <strong>text</strong>)
7function parseBold(markdown) {
8 const pattern = /__(.+)__/g;
9 return markdown.replace(pattern, '<strong>$1</strong>');
710}
811
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
12// Parse italic text (_text_ -> <em>text</em>)
13function parseItalic(markdown) {
14 // Use lookbehind and lookahead to avoid matching underscores within words
15 const pattern = /_(.+)_/g;
16 return markdown.replace(pattern, '<em>$1</em>');
1317}
1418
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
19// Parse inline formatting (bold and italic)
20function parseInlineFormatting(markdown) {
21 return parseItalic(parseBold(markdown));
1722}
1823
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) {
24// Parse header markdown (# Header -> <h1>Header</h1>)
25function parseHeader(markdown) {
3326 let count = 0;
27 // Count consecutive # characters at the start
3428 for (let i = 0; i < markdown.length; i++) {
3529 if (markdown[i] === '#') {
3630 count += 1;
3832 break;
3933 }
4034 }
35
36 // Valid headers have 1-6 # characters
4137 if (count === 0 || count > 6) {
42 return [null, list];
38 return null;
4339 }
40
41 // Extract header content (skip # characters and the following space)
42 const headerContent = markdown.substring(count + 1);
4443 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 }
44 return wrap(parseInlineFormatting(headerContent), headerTag);
5145}
5246
53function parseLineItem(markdown, list) {
47// Parse list item (* item -> <li>item</li>)
48function parseListItem(markdown) {
5449 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 }
50 // Extract list item content (skip "* " prefix)
51 const itemContent = markdown.substring(2);
52 // Parse inline formatting but don't wrap in <p> since it's in a list
53 const formattedContent = parseInlineFormatting(itemContent);
54 return wrap(formattedContent, 'li');
6155 }
62 return [null, list];
56 return null;
6357}
6458
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
59// Parse paragraph (regular text -> <p>text</p>)
60function parseParagraph(markdown) {
61 const formattedContent = parseInlineFormatting(markdown);
62 return wrap(formattedContent, 'p');
7163}
7264
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
65// Parse a single line of markdown
66function parseLine(markdown) {
67 // Try to parse as header first
68 let result = parseHeader(markdown);
69 if (result !== null) {
70 return result;
8071 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
72
73 // Try to parse as list item
74 result = parseListItem(markdown);
75 if (result !== null) {
76 return result;
8377 }
84 return [result, inListAfter];
78
79 // Default to paragraph
80 return parseParagraph(markdown);
8581}
8682
8783/**
84 * Parse markdown text and convert to HTML
8885 * @param {string} markdown
8986 * @returns {string}
9087 */
9188export function parse(markdown) {
9289 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;
90 let html = '';
91 let inList = false;
92
93 for (const line of lines) {
94 // Check if this line is a list item
95 const isListItem = line.startsWith('*');
96
97 // If we were in a list but this line is not a list item, close the list
98 if (inList && !isListItem) {
99 html += '</ul>';
100 inList = false;
101 }
102
103 // If we're not in a list but this line is a list item, start a list
104 if (!inList && isListItem) {
105 html += '<ul>';
106 inList = true;
107 }
108
109 // Parse the line content
110 if (isListItem) {
111 // For list items, parse without wrapping in <p>
112 const itemContent = line.substring(2); // Skip "* "
113 const formattedContent = parseInlineFormatting(itemContent);
114 html += wrap(formattedContent, 'li');
115 } else {
116 // For non-list items, parse normally
117 html += parseLine(line);
118 }
99119 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
120
121 // Close any open list at the end
122 if (inList) {
123 html += '</ul>';
104124 }
125
126 return html;
105127}
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.