UNPKG

markmv

Version:

TypeScript CLI for markdown file operations with intelligent link refactoring

149 lines 5.03 kB
import remarkParse from 'remark-parse'; import { unified } from 'unified'; import { visit } from 'unist-util-visit'; /** * Utility for generating table of contents from markdown content. * * This class extracts headings from markdown content and generates formatted table of contents with * proper indentation and anchor links. * * @category Utils * * @example * Basic usage * ```typescript * const generator = new TocGenerator(); * const content = `# Title\n## Section 1\n### Subsection\n## Section 2`; * const result = await generator.generateToc(content); * * console.log(result.toc); * // Output: * // - [Title](#title) * // - [Section 1](#section-1) * // - [Subsection](#subsection) * // - [Section 2](#section-2) * ``` * * @example * With custom options * ```typescript * const generator = new TocGenerator(); * const options = { * minDepth: 2, * maxDepth: 4, * includeLineNumbers: true * }; * const result = await generator.generateToc(content, options); * ``` */ export class TocGenerator { processor = unified().use(remarkParse); /** * Generate table of contents from markdown content. * * @param content - Markdown content to analyze * @param options - Configuration options * * @returns Promise resolving to TOC result */ async generateToc(content, options = {}) { const { minDepth = 1, maxDepth = 6, includeLineNumbers = false, slugify = this.defaultSlugify, } = options; const tree = this.processor.parse(content); const headings = []; // Extract headings from AST visit(tree, 'heading', (node) => { if (!node.position) return; // Skip headings outside depth range if (node.depth < minDepth || node.depth > maxDepth) return; // Extract text from all child nodes recursively const text = this.extractTextFromNodes(node.children); if (text.trim()) { headings.push({ level: node.depth, text: text.trim(), slug: slugify(text.trim()), line: node.position.start.line, }); } }); const toc = this.formatToc(headings, includeLineNumbers); return { toc, headings, }; } /** * Extract headings from markdown content without generating TOC. * * @param content - Markdown content to analyze * @param options - Configuration options * * @returns Promise resolving to array of headings */ async extractHeadings(content, options = {}) { const result = await this.generateToc(content, options); return result.headings; } /** * Format headings into a table of contents string. * * @param headings - Array of extracted headings * @param includeLineNumbers - Whether to include line numbers * * @returns Formatted TOC markdown */ formatToc(headings, includeLineNumbers) { if (headings.length === 0) { return ''; } const lines = []; const minLevel = Math.min(...headings.map((h) => h.level)); for (const heading of headings) { const indent = ' '.repeat(heading.level - minLevel); const link = `[${heading.text}](#${heading.slug})`; const lineInfo = includeLineNumbers ? ` (line ${heading.line})` : ''; lines.push(`${indent}- ${link}${lineInfo}`); } return lines.join('\n'); } /** * Extract text content from AST nodes recursively. * * @param nodes - Array of AST nodes * * @returns Combined text content */ extractTextFromNodes(nodes) { return nodes .map((node) => { if (node.type === 'text') { return node.value || ''; } else if (node.children && Array.isArray(node.children)) { // Recursively extract text from children, filtering for valid node structure const childNodes = node.children.filter((child) => typeof child === 'object' && child !== null && 'type' in child); return this.extractTextFromNodes(childNodes); } return ''; }) .join(''); } /** * Default slugify function that converts text to URL-friendly anchors. * * @param text - Text to slugify * * @returns URL-friendly slug */ defaultSlugify(text) { return text .toLowerCase() .replace(/[^\w\s-]/g, '-') // Replace special characters with hyphens .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/-+/g, '-') // Replace multiple hyphens with single .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens } } //# sourceMappingURL=toc-generator.js.map