UNPKG

@paperbits/core

Version:
220 lines (172 loc) 8.61 kB
import { Bag, Contract } from "@paperbits/common"; import { IModelBinder } from "@paperbits/common/editing"; import { ILocaleService } from "@paperbits/common/localization"; import { INavigationService, NavigationItemContract, NavigationItemModel } from "@paperbits/common/navigation"; import { IPermalinkResolver } from "@paperbits/common/permalinks"; import { SecurityModelBinder } from "@paperbits/common/security"; import { BuiltInRoles } from "@paperbits/common/user"; import { AnchorUtils } from "../text/anchorUtils"; import { BlockContract } from "../text/contracts"; import { MenuContract } from "./menuContract"; import { MenuModel } from "./menuModel"; export class MenuModelBinder implements IModelBinder<MenuModel> { constructor( private readonly permalinkResolver: IPermalinkResolver, private readonly navigationService: INavigationService, private readonly securityModelBinder: SecurityModelBinder<any, any>, private readonly localeService: ILocaleService ) { } public canHandleContract(contract: Contract): boolean { return contract.type === "menu"; } public canHandleModel(model: Object): boolean { return model instanceof MenuModel; } private async getLanguageNavigationMenu(bindingContext: Bag<any>, layout: string): Promise<NavigationItemModel> { const locales = await this.localeService.getLocales(); const currentLocale = await this.localeService.getCurrentLocaleCode(); const defaultLocale = await this.localeService.getDefaultLocaleCode(); const requestedLocaleCode = bindingContext?.locale || currentLocale || defaultLocale; const requestedLocale = locales.find(x => x.code === requestedLocaleCode); const languageNavItems: NavigationItemModel[] = []; for (const locale of locales) { const targetUrl = bindingContext?.contentItemKey ? await this.permalinkResolver.getUrlByTargetKey(bindingContext.contentItemKey, locale.code) : "/"; let isActive = false; if (locale.code === requestedLocale.code) { if (layout === "horizontal") { continue; // excluding current locale from list in horizontal layout. } isActive = true; } const languageNavItem = new NavigationItemModel(); languageNavItem.label = locale.displayName; languageNavItem.targetUrl = targetUrl; languageNavItem.isActive = isActive; languageNavItems.push(languageNavItem); } let topLevelChildren; switch (layout) { case "horizontal": topLevelChildren = [{ label: requestedLocale.displayName, nodes: languageNavItems }]; break; case "vertical": topLevelChildren = languageNavItems; break; default: throw new Error(`Unsupported menu widget layout: ${layout}`); } const topLevelNavItem = new NavigationItemModel(); topLevelNavItem.label = "Languages"; topLevelNavItem.nodes = topLevelChildren; return topLevelNavItem; } private async processNavigationItem(locale: string, contract: NavigationItemContract, permalink: string, minHeading: number, maxHeading: number, level: number = 0): Promise<NavigationItemModel> { const navitemModel = new NavigationItemModel(); navitemModel.label = contract.label; if (contract.navigationItems) { const tasks = []; contract.navigationItems.forEach(child => { tasks.push(this.processNavigationItem(locale, child, permalink, minHeading, maxHeading, level + 1)); }); const results = await Promise.all(tasks); results.forEach(child => { navitemModel.nodes.push(child); }); } if (!contract.targetKey) { return navitemModel; } const targetUrl = await this.permalinkResolver.getUrlByTargetKey(contract.targetKey, locale); navitemModel.targetUrl = targetUrl; navitemModel.targetWindow = contract.targetWindow; navitemModel.targetKey = contract.targetKey; navitemModel.anchor = contract.anchor; navitemModel.triggerEvent = contract.triggerEvent; if (targetUrl === permalink) { navitemModel.isActive = true; } if (level > 0 && minHeading && maxHeading && navitemModel.targetUrl === permalink) { const localNavItems = await this.processAnchorItems(permalink, locale, minHeading, maxHeading); navitemModel.nodes.push(...localNavItems); } return navitemModel; } private async processAnchorItems(permalink: string, locale: string, minHeading: number, maxHeading?: number): Promise<NavigationItemModel[]> { const content = await this.permalinkResolver.getContentByPermalink(permalink, locale); if (!content) { // throw new Error(`Unable to fetch content for permalink ${permalink}.`); return []; } const children = AnchorUtils.getHeadingNodes(content, minHeading, maxHeading); if (children.length === 0) { return []; } return children.filter(item => item.nodes && item.nodes.length > 0).map((item: BlockContract) => { const itemModel = new NavigationItemModel(); itemModel.label = item.nodes[0].text; itemModel.targetUrl = `#${item.identifier || item.attrs?.id || item.attrs?.key}`; return itemModel; }); } public async contractToModel(contract: MenuContract, bindingContext?: Bag<any>): Promise<MenuModel> { if (!contract) { throw new Error(`Parameter "contract" not specified.`); } const menuModel = new MenuModel(); menuModel.minHeading = contract.minHeading; menuModel.maxHeading = contract.maxHeading; menuModel.items = []; menuModel.layout = contract.layout; menuModel.styles = contract.styles || { appearance: "components/menu/default" }; if (contract.roles) { // converting legacy security contract contract.security = { roles: contract.roles } } if (contract.security) { menuModel.security = await this.securityModelBinder.contractToModel(contract.security); } const currentPageUrl = bindingContext?.navigationPath; if (contract.navigationItemKey) { let root: NavigationItemModel; if (contract.navigationItemKey === "@locales") { root = await this.getLanguageNavigationMenu(bindingContext, menuModel.layout); } else { const rootNavigationItem = await this.navigationService.getNavigationItem(contract.navigationItemKey); if (rootNavigationItem) { root = await this.processNavigationItem(bindingContext?.locale, rootNavigationItem, currentPageUrl, menuModel.minHeading, menuModel.maxHeading); } } if (root) { menuModel.items = root.nodes; menuModel.navigationItem = root; menuModel.navigationItem.key = contract.navigationItemKey; } } if (!!currentPageUrl && menuModel.items.length === 0 && menuModel.minHeading && menuModel.maxHeading) { const localNavItems = await this.processAnchorItems(currentPageUrl, bindingContext?.locale, menuModel.minHeading, menuModel.maxHeading); menuModel.items.push(...localNavItems); } return menuModel; } public modelToContract(model: MenuModel): Contract { const contract: MenuContract = { type: "menu", minHeading: model.minHeading, maxHeading: model.maxHeading, navigationItemKey: model.navigationItem?.key, layout: model.layout, styles: model.styles }; if (model.security) { contract.security = this.securityModelBinder.modelToContract(model.security); } return contract; } }