import { formatText } from './parseTextFormats'; interface ListItem { type: 'unordered' | 'ordered'; depth: number; content: string; startNumber?: number; } interface ListBlock { items: ListItem[]; startLine: number; endLine: number; } const LIST_PATTERN = /^(\s*)(\*|[1-9]\d*\.)(?:#(\d+))?\s+(.+)$/; export function parseLists( lines: string[], skipLines = new Set() ): { blocks: ListBlock[]; processedLines: Set; } { const blocks: ListBlock[] = []; const processedLines = new Set(); let currentBlock: ListBlock | null = null; for (let i = 0; i < lines.length; i++) { if (skipLines.has(i)) { if (currentBlock) { blocks.push(currentBlock); currentBlock = null; } continue; } const match = lines[i].match(LIST_PATTERN); if (!match) { if (currentBlock) { blocks.push(currentBlock); currentBlock = null; } continue; } const [, indent, marker, startNum, content] = match; const depth = indent.length; const item: ListItem = { type: marker === '*' ? 'unordered' : 'ordered', depth, content: content.trim(), ...(startNum && { startNumber: parseInt(startNum, 10) }), }; if (!currentBlock) { currentBlock = { items: [item], startLine: i, endLine: i }; } else { currentBlock.items.push(item); currentBlock.endLine = i; } processedLines.add(i); while (++i < lines.length) { const nextLine = lines[i]; if (nextLine.match(LIST_PATTERN)) { i--; break; } if (!nextLine.trim()) { blocks.push(currentBlock); currentBlock = null; break; } if (nextLine.startsWith(' '.repeat(depth + 1))) { item.content += '\n' + nextLine.slice(depth + 1); processedLines.add(i); currentBlock.endLine = i; } else { blocks.push(currentBlock); currentBlock = null; i--; break; } } } if (currentBlock) blocks.push(currentBlock); return { blocks, processedLines }; } export function renderList(block: ListBlock): string { if (!block.items.length) return ''; const html: string[] = []; const stack: Array<{ type: 'ul' | 'ol'; depth: number }> = []; for (let i = 0; i < block.items.length; i++) { const item = block.items[i]; const listType = item.type === 'unordered' ? 'ul' : 'ol'; while (stack.length && stack[stack.length - 1].depth > item.depth) { html.push(`${stack.length ? '' : ''}`); } if ( stack.length && stack[stack.length - 1].depth === item.depth && stack[stack.length - 1].type !== listType ) { html.push(``); } while (!stack.length || stack[stack.length - 1].depth < item.depth) { const depth = stack.length ? stack[stack.length - 1].depth + 1 : item.depth; const openTag = listType === 'ol' && item.startNumber && item.startNumber !== 1 ? `
    ` : `<${listType}>`; html.push(openTag); stack.push({ type: listType, depth }); if (depth < item.depth) continue; } if (i > 0 && block.items[i - 1].depth === item.depth) { html.push(''); } html.push(`
  1. ${formatText(item.content)}`); const hasChild = i + 1 < block.items.length && block.items[i + 1].depth > item.depth; if (!hasChild) html.push('
  2. '); } while (stack.length) { if (stack.length === 1 && !html[html.length - 1].endsWith('')) { html.push(''); } html.push(`${stack.length ? '' : ''}`); } return html.join('\n'); }