tobe

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>)
7// Uses greedy matching to preserve original behavior exactly
8function parseBold(markdown) {
9 return markdown.replace(/__(.+)__/g, '<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>)
13// Uses greedy matching to preserve original behavior exactly
14function parseItalic(markdown) {
15 return markdown.replace(/_(.+)_/g, '<em>$1</em>');
1316}
1417
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
18// Parse inline formatting (bold and italic)
19function parseInlineFormatting(markdown) {
20 // Apply bold formatting first, then italic
21 return parseItalic(parseBold(markdown));
1722}
1823
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
24// Parse text with optional paragraph wrapping
25function parseText(markdown, isListItem = false) {
26 const parsedText = parseInlineFormatting(markdown);
27 // Only wrap in <p> tags if not part of a list item
28 return isListItem ? parsedText : wrap(parsedText, 'p');
2129}
2230
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) {
31// Parse header elements (# Header -> <h1>Header</h1>)
32function parseHeader(markdown, currentlyInList) {
33 // Count consecutive # characters at the start of the line
3334 let count = 0;
3435 for (let i = 0; i < markdown.length; i++) {
3536 if (markdown[i] === '#') {
3839 break;
3940 }
4041 }
42
43 // Valid headers have 1-6 # characters
4144 if (count === 0 || count > 6) {
42 return [null, list];
45 return [null, currentlyInList];
4346 }
47
48 // Extract header content (text after # characters and space)
49 const headerContent = markdown.substring(count + 1);
4450 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
51 const headerHtml = wrap(headerContent, headerTag);
52
53 // If we were in a list, close it before the header
54 if (currentlyInList) {
4755 return [`</ul>${headerHtml}`, false];
4856 } else {
4957 return [headerHtml, false];
5058 }
5159}
5260
53function parseLineItem(markdown, list) {
61// Parse list items (* item -> <li>item</li>)
62function parseLineItem(markdown, currentlyInList) {
5463 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
64 // Extract list item content (text after '* ')
65 const itemContent = markdown.substring(2);
66 const innerHtml = wrap(parseText(itemContent, true), 'li');
67
68 if (currentlyInList) {
69 // Continue existing list
5770 return [innerHtml, true];
5871 } else {
72 // Start new list
5973 return [`<ul>${innerHtml}`, true];
6074 }
6175 }
62 return [null, list];
76 return [null, currentlyInList];
6377}
6478
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
79// Parse paragraph elements
80function parseParagraph(markdown, currentlyInList) {
81 if (currentlyInList) {
82 // If we were in a list, close it before starting a paragraph
6983 return [`</ul>${parseText(markdown, false)}`, false];
84 } else {
85 // Regular paragraph
86 return [parseText(markdown, false), false];
7087 }
7188}
7289
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
90// Parse a single line of markdown
91function parseLine(markdown, currentlyInList) {
92 // Try parsing as header first
93 let [result, newListState] = parseHeader(markdown, currentlyInList);
94
95 // If not a header, try parsing as list item
7596 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
97 [result, newListState] = parseLineItem(markdown, currentlyInList);
7798 }
99
100 // If not a list item, parse as regular paragraph
78101 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
102 [result, newListState] = parseParagraph(markdown, currentlyInList);
80103 }
104
105 // This should never happen with valid input
81106 if (result === null) {
82 throw new Error('Remove this line and implement the function');
107 throw new Error('Invalid markdown line');
83108 }
84 return [result, inListAfter];
109
110 return [result, newListState];
85111}
86112
87113/**
88 * @param {string} markdown
89 * @returns {string}
114 * Parse markdown text and convert it to HTML
115 * @param {string} markdown - The markdown text to parse
116 * @returns {string} - The resulting HTML
90117 */
91118export function parse(markdown) {
92119 const lines = markdown.split('\n');
93120 let result = '';
94 let list = false;
121 let currentlyInList = false;
122
123 // Process each line of markdown
95124 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
125 let [lineResult, newListState] = parseLine(lines[i], currentlyInList);
97126 result += lineResult;
98 list = newList;
127 currentlyInList = newListState;
99128 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
129
130 // Close any open list at the end of the document
131 if (currentlyInList) {
132 result += '</ul>';
104133 }
134
135 return result;
105136}
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.