fishlium

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}>`);
7}
8
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
6// Parse bold text (__text__ -> <strong>text</strong>)
7function parseBold(markdown) {
8 const pattern = /__(.+)__/g;
9 const replacement = '<strong>$1</strong>';
1210 return markdown.replace(pattern, replacement);
1311}
1412
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
13// Parse italic text (_text_ -> <em>text</em>)
14function parseItalic(markdown) {
15 const pattern = /_(.+)_/g;
16 const replacement = '<em>$1</em>';
17 return markdown.replace(pattern, replacement);
1718}
1819
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
20// Parse inline formatting (bold and italic)
21function parseInlineFormatting(markdown) {
22 // Parse bold first, then italic
23 return parseItalic(parseBold(markdown));
2124}
2225
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26// Parse text content, optionally wrapping in paragraph tags
27function parseText(markdown, isListItem = false) {
28 const parsedText = parseInlineFormatting(markdown);
29 // List items don't get wrapped in <p> tags
30 if (isListItem) {
2631 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
2932 }
33 return wrap(parsedText, 'p');
3034}
3135
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 }
36// Parse header elements (# Header -> <h1>Header</h1>)
37function parseHeader(markdown, currentlyInList) {
38 // Match headers with 1-6 # characters
39 const headerMatch = markdown.match(/^(#{1,6})\s+(.*)$/);
40
41 // If not a valid header, return null to indicate no match
42 if (!headerMatch) {
43 return [null, currentlyInList];
4044 }
41 if (count === 0 || count > 6) {
42 return [null, list];
43 }
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 headerText = headerMatch[2]; // Text after the # characters
48 const headerTag = `h${headerLevel}`;
49 const headerHtml = wrap(headerText, headerTag);
50
51 // If we were in a list, close it before the header
52 if (currentlyInList) {
4753 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
5054 }
55
56 return [headerHtml, false];
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 items (* item -> <li>item</li>)
60function parseListItem(markdown, currentlyInList) {
61 // Check if line starts with '* ' (asterisk followed by space)
62 if (!markdown.startsWith('* ')) {
63 return [null, currentlyInList];
64 }
65
66 // Extract the item text (everything after '* ')
67 const itemText = markdown.substring(2);
68 const listItemHtml = wrap(parseText(itemText, true), 'li');
69
70 // If we're already in a list, just add the list item
71 if (currentlyInList) {
72 return [listItemHtml, true];
6173 }
62 return [null, list];
74
75 // If we're not in a list, start a new list
76 return [`<ul>${listItemHtml}`, true];
6377}
6478
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
79// Parse paragraph elements (default fallback for regular text)
80function parseParagraph(markdown, currentlyInList) {
81 // If we were in a list, close it before the paragraph
82 if (currentlyInList) {
6983 return [`</ul>${parseText(markdown, false)}`, false];
7084 }
85
86 return [parseText(markdown, false), false];
7187}
7288
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
89// Parse a single line of markdown
90function parseLine(markdown, currentlyInList) {
91 // Try to parse as header first
92 let [result, newListState] = parseHeader(markdown, currentlyInList);
93 if (result !== null) {
94 return [result, newListState];
7795 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
96
97 // Try to parse as list item
98 [result, newListState] = parseListItem(markdown, currentlyInList);
99 if (result !== null) {
100 return [result, newListState];
80101 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
102
103 // Default to paragraph
104 [result, newListState] = parseParagraph(markdown, currentlyInList);
105 if (result !== null) {
106 return [result, newListState];
83107 }
84 return [result, inListAfter];
108
109 // This should never happen with valid input
110 throw new Error('Unable to parse line: ' + markdown);
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');
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;
120 let htmlResult = '';
121 let isInList = false;
122
123 // Process each line of the markdown
124 for (const line of lines) {
125 let [lineHtml, newListState] = parseLine(line, isInList);
126 htmlResult += lineHtml;
127 isInList = newListState;
99128 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
129
130 // If we're still in a list at the end, close it
131 if (isInList) {
132 htmlResult += '</ul>';
104133 }
134
135 return htmlResult;
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.