view markdown_converter/markdown_to_html.js @ 185:dfdd66825396

Merged in keep alive changes and mrjunejune changes.
author MrJuneJune <me@mrjunejune.com>
date Fri, 23 Jan 2026 22:22:30 -0800
parents a30944e5719e
children
line wrap: on
line source

/**
 * Simple Markdown to HTML Converter
 * Supports: headers, bold, italic, links, lists, code blocks, line breaks
 */

/**
 * Create heading element (H1-H6)
 * @param {string} text - The heading text
 * @param {number} level - Heading level (1-6)
 * @returns {HTMLElement}
 */
function createHeadingElement(text, level) {
  const h = document.createElement(`H${level}`);
  const t = document.createTextNode(text.trim());
  h.appendChild(t);
  return h;
}

/**
 * Create paragraph element
 * @param {string} text - The paragraph text
 * @returns {HTMLElement}
 */
function createParagraphElement(text) {
  const p = document.createElement("P");
  p.innerHTML = processInlineMarkdown(text.trim());
  return p;
}

/**
 * Create link element
 * @param {string} text - Link text
 * @param {string} url - Link URL
 * @returns {HTMLElement}
 */
function createLinkElement(text, url) {
  const a = document.createElement("A");
  a.href = url;
  a.textContent = text;
  return a;
}

/**
 * Create unordered list element
 * @param {Array<string>} items - List items
 * @returns {HTMLElement}
 */
function createUnorderedList(items) {
  const ul = document.createElement("UL");
  items.forEach(item => {
    const li = document.createElement("LI");
    li.innerHTML = processInlineMarkdown(item.trim());
    ul.appendChild(li);
  });
  return ul;
}

/**
 * Create ordered list element
 * @param {Array<string>} items - List items
 * @returns {HTMLElement}
 */
function createOrderedList(items) {
  const ol = document.createElement("OL");
  items.forEach(item => {
    const li = document.createElement("LI");
    li.innerHTML = processInlineMarkdown(item.trim());
    ol.appendChild(li);
  });
  return ol;
}

/**
 * Create code block element
 * @param {string} code - Code content
 * @returns {HTMLElement}
 */
function createCodeBlock(code) {
  const pre = document.createElement("PRE");
  const codeEl = document.createElement("CODE");
  codeEl.textContent = code;
  pre.appendChild(codeEl);
  return pre;
}

/**
 * Create blockquote element
 * @param {string} text - Quote text
 * @returns {HTMLElement}
 */
function createBlockquote(text) {
  const blockquote = document.createElement("BLOCKQUOTE");
  blockquote.innerHTML = processInlineMarkdown(text.trim());
  return blockquote;
}

/**
 * Create horizontal rule
 * @returns {HTMLElement}
 */
function createHorizontalRule() {
  return document.createElement("HR");
}

/**
 * Process inline markdown (bold, italic, code, links)
 * @param {string} text - Text with inline markdown
 * @returns {string} HTML string
 */
function processInlineMarkdown(text) {
  // Links: [text](url)
  text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
  
  // Images: ![alt](url)
  text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
  
  // Bold: **text** or __text__
  text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
  text = text.replace(/__([^_]+)__/g, '<strong>$1</strong>');
  
  // Italic: *text* or _text_
  text = text.replace(/\*([^*]+)\*/g, '<em>$1</em>');
  text = text.replace(/_([^_]+)_/g, '<em>$1</em>');
  
  // Inline code: `code`
  const x = /`([^`]+)`/g;
  text = text.replace(x, '<code>$1</code>');
  
  // Strikethrough: ~~text~~
  text = text.replace(/~~([^~]+)~~/g, '<del>$1</del>');
  
  // Line breaks: two spaces at end of line
  text = text.replace(/  \n/g, '<br>\n');
  
  return text;
}

/**
 * Main markdown converter function
 * @param {string} value - Markdown string
 * @returns {Array<HTMLElement>} Array of DOM elements
 */
function markdownConverter(value) {
  const lines = value.split('\n');
  const domElements = [];
  let i = 0;
  
  while (i < lines.length) {
    const line = lines[i];
    const trimmedLine = line.trim();
    
    // Skip empty lines
    if (trimmedLine === '') {
      i++;
      continue;
    }
    
    if (trimmedLine.startsWith('#'))
    {
      const match = trimmedLine.match(/^(#{1,6})\s+(.+)$/);
      if (match) {
        const level = match[1].length;
        const text = match[2];
        domElements.push(createHeadingElement(text, level));
        i++;
        continue;
      }
    }
    
    if (trimmedLine.startsWith('```')) {
      let codeContent = '';
      i++; // Skip opening ```
      while (i < lines.length && !lines[i].trim().startsWith('```')) {
        codeContent += lines[i] + '\n';
        i++;
      }
      domElements.push(createCodeBlock(codeContent));
      i++; // Skip closing ```
      continue;
    }
    
    // Blockquote: >
    if (trimmedLine.startsWith('>')) {
      let quoteContent = '';
      while (i < lines.length && lines[i].trim().startsWith('>')) {
        quoteContent += lines[i].trim().substring(1).trim() + ' ';
        i++;
      }
      domElements.push(createBlockquote(quoteContent));
      continue;
    }
    
    // Horizontal rule: ---, ***, ___
    if (trimmedLine.match(/^(-{3,}|\*{3,}|_{3,})$/)) {
      domElements.push(createHorizontalRule());
      i++;
      continue;
    }
    
    // Unordered list: -, *, +
    if (trimmedLine.match(/^[-*+]\s+/)) {
      const listItems = [];
      while (i < lines.length && lines[i].trim().match(/^[-*+]\s+/)) {
        listItems.push(lines[i].trim().substring(2));
        i++;
      }
      domElements.push(createUnorderedList(listItems));
      continue;
    }
    
    // Ordered list: 1., 2., etc.
    if (trimmedLine.match(/^\d+\.\s+/)) {
      const listItems = [];
      while (i < lines.length && lines[i].trim().match(/^\d+\.\s+/)) {
        listItems.push(lines[i].trim().replace(/^\d+\.\s+/, ''));
        i++;
      }
      domElements.push(createOrderedList(listItems));
      continue;
    }
    
    // Regular paragraph
    let paragraphContent = '';
    while (i < lines.length && lines[i].trim() !== '' && 
           !lines[i].trim().match(/^(#{1,6}\s|[-*+]\s|\d+\.\s|>|```|---|\*\*\*|___)/)) {
      paragraphContent += lines[i] + ' ';
      i++;
    }
    if (paragraphContent.trim()) {
      domElements.push(createParagraphElement(paragraphContent));
    }
  }
  
  return domElements;
}

/**
 * Helper function to append all elements to a container
 * @param {HTMLElement} container - Container element
 * @param {string} markdown - Markdown string
 */
function renderMarkdown(container, markdown) {
  const elements = markdownConverter(markdown);
  elements.forEach(el => container.appendChild(el));
}

// Example usage:
// const markdown = `
// # Heading 1
// ## Heading 2
// 
// This is a paragraph with **bold** and *italic* text.
// 
// - List item 1
// - List item 2
// 
// 1. Ordered item 1
// 2. Ordered item 2
// 
// [Link text](https://example.com)
// `;
// 
// const container = document.getElementById('content');
// renderMarkdown(container, markdown);

// Export for use in other files
if (typeof module !== 'undefined' && module.exports) {
  module.exports = { markdownConverter, renderMarkdown };
}