let it go

Finished
1// Helper functions for HTML tag wrapping
12function wrap(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
67 return text.startsWith(`<${tag}>`);
78}
89
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
10// Inline text formatting functions
11function formatBold(text) {
12 const pattern = new RegExp(`__(.+)__`);
13 const replacement = `<strong>$1</strong>`;
14 return text.replace(pattern, replacement);
1315}
1416
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17function formatItalic(text) {
18 const pattern = new RegExp(`_(.+)_`);
19 const replacement = `<em>$1</em>`;
20 return text.replace(pattern, replacement);
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23function parseInlineFormatting(text) {
24 // Apply formatting in order: bold first, then italic
25 return formatItalic(formatBold(text));
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
28function parseText(markdown, inList) {
29 const formattedText = parseInlineFormatting(markdown);
30 if (inList) {
31 return formattedText;
2732 } else {
28 return wrap(parsedText, 'p');
33 return wrap(formattedText, 'p');
2934 }
3035}
3136
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
37 } else {
38 break;
39 }
40 }
41 if (count === 0 || count > 6) {
42 return [null, list];
37// Parse header markdown (# Header) and convert to HTML
38function parseHeader(markdown, inList) {
39 // Match 1-6 hash characters at start of line followed by a space and content
40 const headerMatch = markdown.match(/^(#{1,6})\s(.*)$/);
41
42 if (!headerMatch) {
43 return [null, inList];
4344 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
45
46 const headerLevel = headerMatch[1].length; // Number of # characters
47 const headerContent = headerMatch[2]; // Text after the hashes
48 const headerTag = `h${headerLevel}`;
49 const headerHtml = wrap(headerContent, headerTag);
50
51 // Close any open list before starting a header
52 if (inList) {
4753 return [`</ul>${headerHtml}`, false];
4854 } else {
4955 return [headerHtml, false];
5056 }
5157}
5258
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 }
59// Parse list item markdown (* Item) and convert to HTML
60function parseListItem(markdown, inList) {
61 // Check if line starts with list marker
62 if (!markdown.startsWith('*')) {
63 return [null, inList];
64 }
65
66 // Extract content after the list marker and space
67 const listItemContent = markdown.substring(2).trim();
68 const formattedContent = parseText(listItemContent, true); // true = inside list
69 const listItemHtml = wrap(formattedContent, 'li');
70
71 if (inList) {
72 // Already in a list, just add the item
73 return [listItemHtml, true];
74 } else {
75 // Start a new list
76 return [`<ul>${listItemHtml}`, true];
6177 }
62 return [null, list];
6378}
6479
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
80// Parse regular paragraph text and convert to HTML
81function parseParagraph(markdown, inList) {
82 // Handle empty lines
83 if (markdown.trim() === '') {
84 if (inList) {
85 // Close the list for empty lines
86 return ['</ul>', false];
87 } else {
88 // Ignore empty lines outside of lists
89 return ['', false];
90 }
91 }
92
93 if (inList) {
94 // Close the list before starting a paragraph
6995 return [`</ul>${parseText(markdown, false)}`, false];
96 } else {
97 // Regular paragraph
98 return [parseText(markdown, false), false];
7099 }
71100}
72101
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
102// Parse a single line of markdown and determine its type
103function parseLine(markdown, inList) {
104 // Try to parse as header first
105 let [result, newInList] = parseHeader(markdown, inList);
106
107 // If not a header, try to parse as list item
78108 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
109 [result, newInList] = parseListItem(markdown, inList);
80110 }
111
112 // If not a list item, parse as paragraph (default case)
81113 if (result === null) {
82 throw new Error('Remove this line and implement the function');
114 [result, newInList] = parseParagraph(markdown, inList);
83115 }
84 return [result, inListAfter];
116
117 // parseParagraph should always return a result for non-empty lines
118 // This ensures we handle all markdown content properly
119 return [result, newInList];
85120}
86121
87122/**
88 * @param {string} markdown
89 * @returns {string}
123 * Parse markdown text and convert it to HTML
124 * @param {string} markdown - The markdown text to parse
125 * @returns {string} The HTML representation of the markdown
90126 */
91127export function parse(markdown) {
92128 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;
129 let htmlResult = '';
130 let inList = false; // Track whether we're currently inside a list
131
132 // Process each line of markdown
133 for (const line of lines) {
134 const [lineHtml, stillInList] = parseLine(line, inList);
135 htmlResult += lineHtml;
136 inList = stillInList;
99137 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
138
139 // Close any unclosed list at the end
140 if (inList) {
141 htmlResult += '</ul>';
104142 }
143
144 return htmlResult;
105145}
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.