diff --git a/src/components/LoginModal.jsx b/src/components/LoginModal.jsx index f1b6c49..52dc89c 100644 --- a/src/components/LoginModal.jsx +++ b/src/components/LoginModal.jsx @@ -2,99 +2,173 @@ import React, { useState, useEffect } from 'react'; import { X, LogIn, UserPlus, Github, Mail } from 'lucide-react'; import { api } from '../services/api'; -// Custom Markdown Renderer to ensure consistent styling without dependencies +// Custom Markdown Renderer const renderMarkdown = (text) => { if (!text) return null; const lines = text.split('\n'); const elements = []; let listBuffer = []; - let currentIndent = 0; // 0 for H1, 1 for H2, 2 for H3... used for indentation + + // Track context for indentation of non-header content + // Default (start/after H1): ml-4 + // After H2 (ml-0): ml-4 + // After H3 (ml-4): ml-8 + // After H4 (ml-8): ml-12 + let contentIndentClass = 'ml-4'; // Helper to parse inline styles (Bold and Links) const parseInline = (text) => { + if (!text) return { __html: '' }; + // Pass 1: Links [text](url) - // We use a callback to handle styling logic let processed = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, txt, url) => { const isGreen = txt.includes('CR200J') || url.includes('CR200J'); const classes = isGreen ? "text-emerald-600 hover:text-emerald-800 hover:underline transition-colors font-medium" : "text-blue-600 hover:text-blue-800 hover:underline transition-colors font-medium"; - // Ensure we don't break subsequent bold parsing if link text has stars (unlikely but safe to assume standard markdown) return `${txt}`; }); // Pass 2: Bold (**text**) - // We strictly match **...** and wrap in - // Note: This simple regex might struggle with nested complex HTML but works for standard MD usage here. processed = processed.replace(/\*\*(.*?)\*\*/g, '$1'); return { __html: processed }; }; + // Helper to render buffered list items into a nested structure const flushList = () => { - if (listBuffer.length > 0) { - elements.push( - - {listBuffer.map((item, i) => ( - + if (listBuffer.length === 0) return; + + // Build tree from flat list with indent levels + const roots = []; + const stack = [{ level: -1, children: roots }]; + + listBuffer.forEach(item => { + // Find parent with level < item.level + while (stack.length > 1 && stack[stack.length - 1].level >= item.level) { + stack.pop(); + } + const parent = stack[stack.length - 1]; + const newNode = { ...item, children: [] }; + parent.children.push(newNode); + stack.push(newNode); + }); + + // Recursive render function + const renderTree = (nodes) => { + if (!nodes || nodes.length === 0) return null; + return ( + + {nodes.map((node, i) => ( + + + {node.children.length > 0 && renderTree(node.children)} + ))} ); - listBuffer = []; - } + }; + + // Apply the current content indentation to the list container + elements.push( + + {renderTree(roots)} + + ); + listBuffer = []; }; - lines.forEach((line, index) => { + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; const trimmed = line.trim(); + + // Empty line: flush list if (!trimmed) { flushList(); - return; + continue; } - if (trimmed.startsWith('- ')) { - listBuffer.push(trimmed.substring(2)); - } else { + // Horizontal Rule (--- or ***) + if (trimmed === '---' || trimmed === '***') { flushList(); + elements.push(); + continue; + } - if (trimmed.startsWith('# ')) { - currentIndent = 0; - elements.push( - - {trimmed.substring(2)} - - ); - } else if (trimmed.startsWith('## ')) { - currentIndent = 1; - elements.push( - - {trimmed.substring(3)} - - ); - } else if (trimmed.startsWith('### ')) { - currentIndent = 2; - elements.push( - - {trimmed.substring(4)} - - ); - } else if (trimmed.startsWith('> ')) { - // Blockquotes inherit indentation - const indentClass = currentIndent === 1 ? 'ml-4' : currentIndent === 2 ? 'ml-8' : ''; - elements.push( - - ); - } else { - // Paragraph - inherit indentation - const indentClass = currentIndent === 1 ? 'ml-4' : currentIndent === 2 ? 'ml-8' : ''; + // Headers + // H1: Centered. Next content indent: ml-4 (Step 1) + if (line.match(/^#\s/)) { + flushList(); + contentIndentClass = 'ml-4'; + elements.push( + + {line.substring(2).trim()} + + ); + continue; + } + // H2: ml-0 (Step 0). Next content indent: ml-4 (Step 1) + if (line.match(/^##\s/)) { + flushList(); + contentIndentClass = 'ml-4'; + elements.push( + + {line.substring(3).trim()} + + ); + continue; + } + // H3: ml-4 (Step 1). Next content indent: ml-8 (Step 2) + if (line.match(/^###\s/)) { + flushList(); + contentIndentClass = 'ml-8'; + elements.push( + + {line.substring(4).trim()} + + ); + continue; + } + // H4: ml-8 (Step 2). Next content indent: ml-12 (Step 3) + if (line.match(/^####\s/)) { + flushList(); + contentIndentClass = 'ml-12'; elements.push( - + + {line.substring(5).trim()} + ); + continue; } + + // List Items (- or *) + const listMatch = line.match(/^(\s*)([-*])\s+(.+)$/); + if (listMatch) { + const indent = listMatch[1].length; + const content = listMatch[3]; + listBuffer.push({ level: indent, content }); + continue; + } + + // Flush list if we encounter non-list content + flushList(); + + // Blockquotes + if (trimmed.startsWith('> ')) { + elements.push( + + ); + continue; } - }); - flushList(); + // Paragraph + elements.push( + + ); + } + + flushList(); return elements; }; @@ -145,7 +219,7 @@ export const LoginModal = ({ isOpen, onClose, onLoginSuccess }) => { return ( - e.stopPropagation()}> + e.stopPropagation()}> {/* Left: Login Form */} @@ -257,6 +331,21 @@ export const LoginModal = ({ isOpen, onClose, onLoginSuccess }) => { + {renderMarkdown(readmeContent)}