Mercurial
comparison markdown_converter/markdown_to_html.js @ 64:a30944e5719e
Added vibe coded markdown to html script since it is useful for me. Updated Dowa so that it can be compiled without dirnet for windows.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Tue, 23 Dec 2025 15:18:46 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 63:fff1b048dda6 | 64:a30944e5719e |
|---|---|
| 1 /** | |
| 2 * Simple Markdown to HTML Converter | |
| 3 * Supports: headers, bold, italic, links, lists, code blocks, line breaks | |
| 4 */ | |
| 5 | |
| 6 /** | |
| 7 * Create heading element (H1-H6) | |
| 8 * @param {string} text - The heading text | |
| 9 * @param {number} level - Heading level (1-6) | |
| 10 * @returns {HTMLElement} | |
| 11 */ | |
| 12 function createHeadingElement(text, level) { | |
| 13 const h = document.createElement(`H${level}`); | |
| 14 const t = document.createTextNode(text.trim()); | |
| 15 h.appendChild(t); | |
| 16 return h; | |
| 17 } | |
| 18 | |
| 19 /** | |
| 20 * Create paragraph element | |
| 21 * @param {string} text - The paragraph text | |
| 22 * @returns {HTMLElement} | |
| 23 */ | |
| 24 function createParagraphElement(text) { | |
| 25 const p = document.createElement("P"); | |
| 26 p.innerHTML = processInlineMarkdown(text.trim()); | |
| 27 return p; | |
| 28 } | |
| 29 | |
| 30 /** | |
| 31 * Create link element | |
| 32 * @param {string} text - Link text | |
| 33 * @param {string} url - Link URL | |
| 34 * @returns {HTMLElement} | |
| 35 */ | |
| 36 function createLinkElement(text, url) { | |
| 37 const a = document.createElement("A"); | |
| 38 a.href = url; | |
| 39 a.textContent = text; | |
| 40 return a; | |
| 41 } | |
| 42 | |
| 43 /** | |
| 44 * Create unordered list element | |
| 45 * @param {Array<string>} items - List items | |
| 46 * @returns {HTMLElement} | |
| 47 */ | |
| 48 function createUnorderedList(items) { | |
| 49 const ul = document.createElement("UL"); | |
| 50 items.forEach(item => { | |
| 51 const li = document.createElement("LI"); | |
| 52 li.innerHTML = processInlineMarkdown(item.trim()); | |
| 53 ul.appendChild(li); | |
| 54 }); | |
| 55 return ul; | |
| 56 } | |
| 57 | |
| 58 /** | |
| 59 * Create ordered list element | |
| 60 * @param {Array<string>} items - List items | |
| 61 * @returns {HTMLElement} | |
| 62 */ | |
| 63 function createOrderedList(items) { | |
| 64 const ol = document.createElement("OL"); | |
| 65 items.forEach(item => { | |
| 66 const li = document.createElement("LI"); | |
| 67 li.innerHTML = processInlineMarkdown(item.trim()); | |
| 68 ol.appendChild(li); | |
| 69 }); | |
| 70 return ol; | |
| 71 } | |
| 72 | |
| 73 /** | |
| 74 * Create code block element | |
| 75 * @param {string} code - Code content | |
| 76 * @returns {HTMLElement} | |
| 77 */ | |
| 78 function createCodeBlock(code) { | |
| 79 const pre = document.createElement("PRE"); | |
| 80 const codeEl = document.createElement("CODE"); | |
| 81 codeEl.textContent = code; | |
| 82 pre.appendChild(codeEl); | |
| 83 return pre; | |
| 84 } | |
| 85 | |
| 86 /** | |
| 87 * Create blockquote element | |
| 88 * @param {string} text - Quote text | |
| 89 * @returns {HTMLElement} | |
| 90 */ | |
| 91 function createBlockquote(text) { | |
| 92 const blockquote = document.createElement("BLOCKQUOTE"); | |
| 93 blockquote.innerHTML = processInlineMarkdown(text.trim()); | |
| 94 return blockquote; | |
| 95 } | |
| 96 | |
| 97 /** | |
| 98 * Create horizontal rule | |
| 99 * @returns {HTMLElement} | |
| 100 */ | |
| 101 function createHorizontalRule() { | |
| 102 return document.createElement("HR"); | |
| 103 } | |
| 104 | |
| 105 /** | |
| 106 * Process inline markdown (bold, italic, code, links) | |
| 107 * @param {string} text - Text with inline markdown | |
| 108 * @returns {string} HTML string | |
| 109 */ | |
| 110 function processInlineMarkdown(text) { | |
| 111 // Links: [text](url) | |
| 112 text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>'); | |
| 113 | |
| 114 // Images:  | |
| 115 text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">'); | |
| 116 | |
| 117 // Bold: **text** or __text__ | |
| 118 text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>'); | |
| 119 text = text.replace(/__([^_]+)__/g, '<strong>$1</strong>'); | |
| 120 | |
| 121 // Italic: *text* or _text_ | |
| 122 text = text.replace(/\*([^*]+)\*/g, '<em>$1</em>'); | |
| 123 text = text.replace(/_([^_]+)_/g, '<em>$1</em>'); | |
| 124 | |
| 125 // Inline code: `code` | |
| 126 const x = /`([^`]+)`/g; | |
| 127 text = text.replace(x, '<code>$1</code>'); | |
| 128 | |
| 129 // Strikethrough: ~~text~~ | |
| 130 text = text.replace(/~~([^~]+)~~/g, '<del>$1</del>'); | |
| 131 | |
| 132 // Line breaks: two spaces at end of line | |
| 133 text = text.replace(/ \n/g, '<br>\n'); | |
| 134 | |
| 135 return text; | |
| 136 } | |
| 137 | |
| 138 /** | |
| 139 * Main markdown converter function | |
| 140 * @param {string} value - Markdown string | |
| 141 * @returns {Array<HTMLElement>} Array of DOM elements | |
| 142 */ | |
| 143 function markdownConverter(value) { | |
| 144 const lines = value.split('\n'); | |
| 145 const domElements = []; | |
| 146 let i = 0; | |
| 147 | |
| 148 while (i < lines.length) { | |
| 149 const line = lines[i]; | |
| 150 const trimmedLine = line.trim(); | |
| 151 | |
| 152 // Skip empty lines | |
| 153 if (trimmedLine === '') { | |
| 154 i++; | |
| 155 continue; | |
| 156 } | |
| 157 | |
| 158 if (trimmedLine.startsWith('#')) | |
| 159 { | |
| 160 const match = trimmedLine.match(/^(#{1,6})\s+(.+)$/); | |
| 161 if (match) { | |
| 162 const level = match[1].length; | |
| 163 const text = match[2]; | |
| 164 domElements.push(createHeadingElement(text, level)); | |
| 165 i++; | |
| 166 continue; | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 if (trimmedLine.startsWith('```')) { | |
| 171 let codeContent = ''; | |
| 172 i++; // Skip opening ``` | |
| 173 while (i < lines.length && !lines[i].trim().startsWith('```')) { | |
| 174 codeContent += lines[i] + '\n'; | |
| 175 i++; | |
| 176 } | |
| 177 domElements.push(createCodeBlock(codeContent)); | |
| 178 i++; // Skip closing ``` | |
| 179 continue; | |
| 180 } | |
| 181 | |
| 182 // Blockquote: > | |
| 183 if (trimmedLine.startsWith('>')) { | |
| 184 let quoteContent = ''; | |
| 185 while (i < lines.length && lines[i].trim().startsWith('>')) { | |
| 186 quoteContent += lines[i].trim().substring(1).trim() + ' '; | |
| 187 i++; | |
| 188 } | |
| 189 domElements.push(createBlockquote(quoteContent)); | |
| 190 continue; | |
| 191 } | |
| 192 | |
| 193 // Horizontal rule: ---, ***, ___ | |
| 194 if (trimmedLine.match(/^(-{3,}|\*{3,}|_{3,})$/)) { | |
| 195 domElements.push(createHorizontalRule()); | |
| 196 i++; | |
| 197 continue; | |
| 198 } | |
| 199 | |
| 200 // Unordered list: -, *, + | |
| 201 if (trimmedLine.match(/^[-*+]\s+/)) { | |
| 202 const listItems = []; | |
| 203 while (i < lines.length && lines[i].trim().match(/^[-*+]\s+/)) { | |
| 204 listItems.push(lines[i].trim().substring(2)); | |
| 205 i++; | |
| 206 } | |
| 207 domElements.push(createUnorderedList(listItems)); | |
| 208 continue; | |
| 209 } | |
| 210 | |
| 211 // Ordered list: 1., 2., etc. | |
| 212 if (trimmedLine.match(/^\d+\.\s+/)) { | |
| 213 const listItems = []; | |
| 214 while (i < lines.length && lines[i].trim().match(/^\d+\.\s+/)) { | |
| 215 listItems.push(lines[i].trim().replace(/^\d+\.\s+/, '')); | |
| 216 i++; | |
| 217 } | |
| 218 domElements.push(createOrderedList(listItems)); | |
| 219 continue; | |
| 220 } | |
| 221 | |
| 222 // Regular paragraph | |
| 223 let paragraphContent = ''; | |
| 224 while (i < lines.length && lines[i].trim() !== '' && | |
| 225 !lines[i].trim().match(/^(#{1,6}\s|[-*+]\s|\d+\.\s|>|```|---|\*\*\*|___)/)) { | |
| 226 paragraphContent += lines[i] + ' '; | |
| 227 i++; | |
| 228 } | |
| 229 if (paragraphContent.trim()) { | |
| 230 domElements.push(createParagraphElement(paragraphContent)); | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 return domElements; | |
| 235 } | |
| 236 | |
| 237 /** | |
| 238 * Helper function to append all elements to a container | |
| 239 * @param {HTMLElement} container - Container element | |
| 240 * @param {string} markdown - Markdown string | |
| 241 */ | |
| 242 function renderMarkdown(container, markdown) { | |
| 243 const elements = markdownConverter(markdown); | |
| 244 elements.forEach(el => container.appendChild(el)); | |
| 245 } | |
| 246 | |
| 247 // Example usage: | |
| 248 // const markdown = ` | |
| 249 // # Heading 1 | |
| 250 // ## Heading 2 | |
| 251 // | |
| 252 // This is a paragraph with **bold** and *italic* text. | |
| 253 // | |
| 254 // - List item 1 | |
| 255 // - List item 2 | |
| 256 // | |
| 257 // 1. Ordered item 1 | |
| 258 // 2. Ordered item 2 | |
| 259 // | |
| 260 // [Link text](https://example.com) | |
| 261 // `; | |
| 262 // | |
| 263 // const container = document.getElementById('content'); | |
| 264 // renderMarkdown(container, markdown); | |
| 265 | |
| 266 // Export for use in other files | |
| 267 if (typeof module !== 'undefined' && module.exports) { | |
| 268 module.exports = { markdownConverter, renderMarkdown }; | |
| 269 } |