tobe

Finished
1// Helper function to wrap text in 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
9function parser(markdown, delimiter, tag) {
11// Generic parser for markdown delimiters (bold, italic, etc.)
12function parseWithDelimiter(markdown, delimiter, tag) {
1013 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
1114 const replacement = `<${tag}>$1</${tag}>`;
1215 return markdown.replace(pattern, replacement);
1316}
1417
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
18// Parse bold text using __ delimiter
19function parseBold(markdown) {
20 return parseWithDelimiter(markdown, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text using _ delimiter
24function parseItalic(markdown) {
25 return parseWithDelimiter(markdown, '_', 'em');
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
28// Parse text with inline formatting (bold, italic) and wrap in paragraph tags if not in a list
29function parseText(markdown, isInList) {
30 // Apply inline formatting parsers
31 const formattedText = parseItalic(parseBold(markdown));
32
33 // If we're in a list, return text without paragraph tags
34 if (isInList) {
35 return formattedText;
2736 } else {
28 return wrap(parsedText, 'p');
37 // Otherwise wrap in paragraph tags
38 return wrap(formattedText, 'p');
2939 }
3040}
3141
32function parseHeader(markdown, list) {
33 let count = 0;
42// Parse header markdown (# ## ### etc.) and return HTML with updated list state
43function parseHeader(markdown, isInList) {
44 // Count consecutive # characters at the beginning of the line
45 let hashCount = 0;
3446 for (let i = 0; i < markdown.length; i++) {
3547 if (markdown[i] === '#') {
36 count += 1;
48 hashCount += 1;
3749 } else {
3850 break;
3951 }
4052 }
41 if (count === 0 || count > 6) {
42 return [null, list];
53
54 // Valid headers have 1-6 # characters
55 if (hashCount === 0 || hashCount > 6) {
56 return [null, isInList];
4357 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
58
59 // Create appropriate header tag (h1, h2, etc.)
60 const headerTag = `h${hashCount}`;
61 const headerContent = markdown.substring(hashCount + 1);
62 const headerHtml = wrap(headerContent, headerTag);
63
64 // If we were in a list, close it before the header
65 if (isInList) {
4766 return [`</ul>${headerHtml}`, false];
4867 } else {
4968 return [headerHtml, false];
5069 }
5170}
5271
53function parseLineItem(markdown, list) {
72// Parse list item markdown (* item) and return HTML with updated list state
73function parseLineItem(markdown, isInList) {
74 // Check if line starts with a list marker
5475 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
76 const itemContent = markdown.substring(2); // Skip '* ' prefix
77 const formattedContent = parseText(itemContent, true); // true = we're in a list
78 const listItemHtml = wrap(formattedContent, 'li');
79
80 // If we're already in a list, just return the list item
81 if (isInList) {
82 return [listItemHtml, true];
5883 } else {
59 return [`<ul>${innerHtml}`, true];
84 // Otherwise, start a new unordered list
85 return [`<ul>${listItemHtml}`, true];
6086 }
6187 }
62 return [null, list];
88
89 // Not a list item
90 return [null, isInList];
6391}
6492
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
93// Parse paragraph text and return HTML with updated list state
94function parseParagraph(markdown, isInList) {
95 const paragraphHtml = parseText(markdown, false); // false = not in a list
96
97 // If we were in a list, close it before the paragraph
98 if (isInList) {
99 return [`</ul>${paragraphHtml}`, false];
68100 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
101 return [paragraphHtml, false];
70102 }
71103}
72104
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
105// Parse a single line of markdown and return HTML with updated list state
106function parseLine(markdown, isInList) {
107 // Try parsing as header first
108 let [result, newListState] = parseHeader(markdown, isInList);
109
110 // If not a header, try parsing as list item
75111 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
112 [result, newListState] = parseLineItem(markdown, isInList);
77113 }
114
115 // If not a list item, parse as paragraph
78116 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
117 [result, newListState] = parseParagraph(markdown, isInList);
80118 }
119
120 // This should never happen with valid input
81121 if (result === null) {
82 throw new Error('Remove this line and implement the function');
122 throw new Error('Unrecognized markdown syntax');
83123 }
84 return [result, inListAfter];
124
125 return [result, newListState];
85126}
86127
87128/**
88 * @param {string} markdown
89 * @returns {string}
129 * Parse markdown text and convert to HTML
130 * @param {string} markdown - The markdown text to parse
131 * @returns {string} - The resulting HTML
90132 */
91133export function parse(markdown) {
92134 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
135 let htmlResult = '';
136 let isInList = false; // Track if we're currently inside an unordered list
137
138 // Process each line of markdown
95139 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
140 const [lineHtml, newListState] = parseLine(lines[i], isInList);
141 htmlResult += lineHtml;
142 isInList = newListState;
99143 }
100 if (list) {
101 return result + '</ul>';
144
145 // If we're still in a list at the end, close it
146 if (isInList) {
147 return htmlResult + '</ul>';
102148 } else {
103 return result;
149 return htmlResult;
104150 }
105151}
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.