UNPKG

@mdfriday/foundry

Version:

The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.

192 lines 6.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MenuBuilder = void 0; const log_1 = require("../../../../pkg/log"); const menu_1 = require("../valueobject/menu"); // Create domain-specific logger for menu building operations const log = (0, log_1.getDomainLogger)('site', { component: 'menu-builder' }); /** * MenuBuilder - Entity responsible for building menus from content directory structure * Implements the logic described in menu.md */ class MenuBuilder { constructor(contentService) { this.contentService = contentService; } /** * Build menus for a specific language * Walks through pages and builds hierarchical menu structure */ async buildMenusForLanguage(langIndex) { const menuMap = new Map(); // Walk through all pages to collect menu structure await this.contentService.walkPages(langIndex, async (page) => { await this.processPage(page, menuMap); }); // Convert menu structure to Menus return this.convertToMenus(menuMap); } /** * Process a single page and update menu structure */ async processPage(page, menuMap) { try { const pagePath = page.path(); const urlPath = pagePath; // Use path directly as URL path // Skip if not a valid menu path if (!this.isValidMenuPath(pagePath)) { return; } // Parse path components const pathParts = this.parsePathParts(pagePath); if (pathParts.length === 0) { return; } // Build menu structure this.buildMenuStructure(pathParts, urlPath, page, menuMap); } catch (error) { log.error(`Error processing page for menu: ${error}`); } } /** * Check if path is valid for menu generation */ isValidMenuPath(path) { // Skip hidden files, system files, etc. if (path.startsWith('.') || path.includes('/.')) { return false; } // Only process markdown files and directories return path.endsWith('.md') || !path.includes('.'); } /** * Parse path into meaningful parts for menu structure */ parsePathParts(path) { // Remove leading slash and file extension let cleanPath = path.replace(/^\/+/, '').replace(/\.md$/, ''); // Split by slash and filter empty parts const parts = cleanPath.split('/').filter(part => part.length > 0); return parts; } /** * Build hierarchical menu structure */ buildMenuStructure(pathParts, urlPath, page, menuMap) { // Process each level of the path for (let i = 0; i < pathParts.length; i++) { const currentPath = pathParts.slice(0, i + 1).join('/'); const isLeaf = i === pathParts.length - 1; const isIndex = pathParts[i] === 'index' || pathParts[i] === '_index'; // Get or create menu structure let menuStructure = menuMap.get(currentPath); if (!menuStructure) { menuStructure = { title: this.formatTitle(pathParts[i]), url: this.buildUrl(pathParts.slice(0, i + 1)), isDir: !isLeaf || isIndex, hasIndex: isIndex && pathParts[i] === 'index', children: new Map(), weight: this.calculateWeight(pathParts[i]), level: i, }; menuMap.set(currentPath, menuStructure); } // Update properties based on page type if (isLeaf) { menuStructure.url = urlPath; if (pathParts[i] === 'index') { menuStructure.hasIndex = true; menuStructure.isDir = false; // index.md makes it a leaf } } // Add to parent if not root level if (i > 0) { const parentPath = pathParts.slice(0, i).join('/'); const parentStructure = menuMap.get(parentPath); if (parentStructure) { parentStructure.children.set(currentPath, menuStructure); parentStructure.isDir = true; // Parent is definitely a directory } } } } /** * Format directory/file name into menu title */ formatTitle(name) { // Skip formatting for index files if (name === 'index' || name === '_index') { return name; } // Convert kebab-case and snake_case to Title Case return name .replace(/[-_]/g, ' ') .replace(/\b\w/g, letter => letter.toUpperCase()); } /** * Build URL from path parts */ buildUrl(pathParts) { return '/' + pathParts.join('/') + '/'; } /** * Calculate weight for sorting (lower weight = higher priority) */ calculateWeight(name) { // Special handling for index files if (name === 'index') return 0; if (name === '_index') return 1; // Default weight based on alphabetical order return name.charCodeAt(0); } /** * Convert MenuStructure map to Menus */ convertToMenus(menuMap) { const menus = new Map(); // Find root level entries (no parent) const rootEntries = []; for (const [path, structure] of menuMap.entries()) { if (structure.level === 0) { rootEntries.push(structure); } } // Build main menu from root entries if (rootEntries.length > 0) { const mainMenu = this.buildMenuFromStructures(rootEntries, menuMap); menus.set('main', mainMenu); } return new menu_1.Menus(menus); } /** * Recursively build Menu from MenuStructure */ buildMenuFromStructures(structures, menuMap) { const entries = []; for (const structure of structures) { // Build children const childStructures = Array.from(structure.children.values()); const children = childStructures.length > 0 ? this.buildMenuFromStructures(childStructures, menuMap).entries() : []; // Create menu entry const entry = new menu_1.MenuEntry({ title: structure.title, url: structure.url, isDir: structure.isDir, hasIndex: structure.hasIndex, children: children, weight: structure.weight, identifier: structure.url, }); entries.push(entry); } return new menu_1.Menu(entries).sortMenu(); } } exports.MenuBuilder = MenuBuilder; //# sourceMappingURL=menu-builder.js.map