Konoha

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
1// Helper function to wrap content in HTML tags
2function wrap(content, tag) {
3 return `<${tag}>${content}</${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 = new RegExp('__' + '(.+)' + '__');
9 const replacement = '<strong>$1</strong>';
1210 return markdown.replace(pattern, replacement);
1311}
1412
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
13// Parse italic text (_text_ -> <em>text</em>)
14function parseItalic(markdown) {
15 const pattern = new RegExp('_' + '(.+)' + '_');
16 const replacement = '<em>$1</em>';
17 return markdown.replace(pattern, replacement);
2118}
2219
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
20// Parse text with inline formatting (bold and italic)
21function parseFormattedText(text, inList) {
22 // Apply formatting: bold first, then italic
23 const formattedText = parseItalic(parseBold(text));
24
25 // If we're inside a list, don't wrap in <p> tags
26 if (inList) {
27 return formattedText;
2728 } else {
28 return wrap(parsedText, 'p');
29 return wrap(formattedText, 'p');
2930 }
3031}
3132
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
33// Parse header (# Header -> <h1>Header</h1>)
34function parseHeader(line, inList) {
35 // Count consecutive # characters at the start of the line
36 let hashCount = 0;
37 for (let i = 0; i < line.length; i++) {
38 if (line[i] === '#') {
39 hashCount += 1;
3740 } else {
3841 break;
3942 }
4043 }
41 if (count === 0 || count > 6) {
42 return [null, list];
44
45 // Valid headers have 1-6 # characters
46 // Note: The original implementation has a bug where it doesn't check for a space after #
47 // but we preserve this behavior to maintain compatibility
48 if (hashCount === 0 || hashCount > 6) {
49 return [null, inList];
4350 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
51
52 // Create the appropriate header tag (h1-h6)
53 const headerTag = `h${hashCount}`;
54 const headerContent = line.substring(hashCount + 1);
55 const headerHtml = wrap(headerContent, headerTag);
56
57 // If we were in a list, close it before the header
58 if (inList) {
4759 return [`</ul>${headerHtml}`, false];
4860 } else {
4961 return [headerHtml, false];
5062 }
5163}
5264
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];
65// Parse list items (* item -> <li>item</li>)
66function parseListItem(line, inList) {
67 if (line.startsWith('*')) {
68 // Extract the list item content (skip '* ')
69 const itemContent = line.substring(2);
70 const formattedItem = parseFormattedText(itemContent, true);
71 const listItemHtml = wrap(formattedItem, 'li');
72
73 // If we're already in a list, just add the item
74 // Otherwise, start a new list
75 if (inList) {
76 return [listItemHtml, true];
5877 } else {
59 return [`<ul>${innerHtml}`, true];
78 return [`<ul>${listItemHtml}`, true];
6079 }
6180 }
62 return [null, list];
81 return [null, inList];
6382}
6483
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
84// Parse regular paragraph text
85function parseParagraph(line, inList) {
86 // Parse the text with formatting
87 const paragraphHtml = parseFormattedText(line, false);
88
89 // If we were in a list, close it before the paragraph
90 if (inList) {
91 return [`</ul>${paragraphHtml}`, false];
6892 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
93 return [paragraphHtml, false];
7094 }
7195}
7296
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
97// Parse a single line of markdown
98function parseLine(line, inList) {
99 // Try to parse as header first
100 let [result, newListState] = parseHeader(line, inList);
101
102 // If not a header, try to parse as list item
75103 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
104 [result, newListState] = parseListItem(line, inList);
77105 }
106
107 // If not a list item, parse as regular paragraph
78108 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
109 [result, newListState] = parseParagraph(line, inList);
80110 }
111
112 // This should never happen with valid input
81113 if (result === null) {
82 throw new Error('Remove this line and implement the function');
114 throw new Error('Unable to parse line: ' + line);
83115 }
84 return [result, inListAfter];
116
117 return [result, newListState];
85118}
86119
87120/**
88 * @param {string} markdown
89 * @returns {string}
121 * Parse markdown text and convert it to HTML
122 * @param {string} markdown - The markdown text to parse
123 * @returns {string} - The resulting HTML
90124 */
91125export function parse(markdown) {
126 // Split the markdown into individual lines
92127 const lines = markdown.split('\n');
128
129 // Track whether we're currently inside a list
130 let inList = false;
131
132 // Build the result HTML
93133 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;
134
135 // Process each line
136 for (const line of lines) {
137 const [lineHtml, newListState] = parseLine(line, inList);
138 result += lineHtml;
139 inList = newListState;
99140 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
141
142 // If we're still in a list at the end, close it
143 if (inList) {
144 result += '</ul>';
104145 }
146
147 return result;
105148}
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.