@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
JavaScript
;
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