UNPKG

@mdfriday/foundry

Version:

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

525 lines 20.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PageBuilder = void 0; const pagecontent_1 = require("./pagecontent"); const frontmatter_1 = require("../vo/frontmatter"); const kind_1 = require("../vo/kind"); const section_1 = require("./section"); const standalone_1 = require("./standalone"); const log_1 = require("../../../../pkg/log"); const page_1 = require("./page"); const pagemeta_1 = require("./pagemeta"); const pagelayout_1 = require("./pagelayout"); const paginator_1 = require("../../../domain/content/entity/paginator"); const shortcode_1 = require("./shortcode"); const template_1 = require("../../../domain/template"); // Create a domain-specific logger for content operations const log = (0, log_1.getDomainLogger)('content', { component: 'pagebuilder' }); class PageBuilder { constructor(langSvc, taxonomySvc, templateSvc, pageMapper, taxonomy, term, section, standalone, converter, contentHub) { this.renderableDocument = null; this.langSvc = langSvc; this.taxonomySvc = taxonomySvc; this.templateSvc = templateSvc; this.pageMapper = null; this.taxonomy = taxonomy; this.term = term; this.section = section; this.standalone = standalone; this.converter = converter; this.contentHub = contentHub; this.source = null; this.sourceByte = new Uint8Array(); this.kind = ""; this.singular = ""; this.term_ = ""; this.langIdx = -1; this.fm = null; this.fmParser = null; this.c = null; } withSource(source) { const cloneBuilder = Object.assign(Object.create(Object.getPrototypeOf(this)), this); cloneBuilder.reset(); cloneBuilder.source = source; return cloneBuilder; } /** * WithLangIdx - exact replica of Go's WithLangIdx method */ withLangIdx(idx) { this.langIdx = idx; return this; } /** * Reset - exact replica of Go's reset method */ reset() { this.c = null; this.kind = ""; this.langIdx = -1; } /** * Build - exact replica of Go's Build method */ async build() { if (!this.source) { throw new Error("source for page builder is nil"); } const contentBytes = await this.source.contentSource(); this.sourceByte = contentBytes; await this.parse(contentBytes); const p = await this.buildInternal(); await this.render(p); return p; } /** * KindBuild - exact replica of Go's KindBuild method */ async kindBuild() { if (!this.source) { throw new Error("source for page builder is nil"); } await this.parseKind(); if (this.langIdx === -1) { await this.parseLanguageByDefault(); } else { await this.parseLanguageByIdx(this.langIdx); } this.fm = (0, frontmatter_1.newFrontMatter)(); this.c = (0, pagecontent_1.newContent)(new Uint8Array()); return await this.buildInternal(); } /** * Build internal - exact replica of Go's build method */ async buildInternal() { switch (this.kind) { case (0, kind_1.getKindMain)("home"): return await this.buildHome(); case (0, kind_1.getKindMain)("section"): return await this.buildSection(); case (0, kind_1.getKindMain)("page"): return await this.buildPage(); case (0, kind_1.getKindMain)("taxonomy"): return await this.buildTaxonomy(); case (0, kind_1.getKindMain)("term"): return await this.buildTerm(); case (0, kind_1.getKindMain)("404"): return await this.build404(); case (0, kind_1.getKindMain)("sitemap"): return await this.buildSitemap(); default: throw new Error(`unknown kind "${this.kind}"`); } } /** * Build page - exact replica of Go's buildPage method */ async buildPage() { const p = await this.newPage(this.source, this.c); await this.applyFrontMatter(p); p.pageMap = this.pageMapper; await this.buildOutput(p); await this.adaptPagination(p); return p; } /** * Build page with kind - exact replica of Go's buildPageWithKind method */ async buildPageWithKind(kind) { const p = await this.newPage(this.source, this.c); await this.applyFrontMatter(p); p.pageMap = this.pageMapper; p.kind_ = kind; // Set kind_ property, equivalent to Go's p.kind = kind if (kind === (0, kind_1.getKindMain)("sitemap") || kind === (0, kind_1.getKindMain)("404")) { // Update the existing Meta instance instead of replacing it const meta = p.meta; if (meta) { meta.list = "never"; } } await this.buildOutput(p); return p; } /** * Apply front matter - exact replica of Go's applyFrontMatter method */ async applyFrontMatter(p) { if (this.fm) { p.title_ = this.fm.title; // Update the existing Meta instance instead of replacing it const meta = p.meta; if (meta) { meta.weight = this.fm.weight; meta.parameters = this.fm.params || {}; meta.date = this.fm.date; } } } /** * Build home - exact replica of Go's buildHome method */ async buildHome() { const p = await this.buildPageWithKind((0, kind_1.getKindMain)("home")); await this.buildPagination(p); return p; } /** * Build section - exact replica of Go's buildSection method */ async buildSection() { const p = await this.buildPageWithKind((0, kind_1.getKindMain)("section")); await this.buildPagination(p); return p; } /** * Build 404 - exact replica of Go's build404 method */ async build404() { const p = await this.buildPageWithKind((0, kind_1.getKindMain)("404")); await this.adaptPagination(p); return p; } /** * Build sitemap - exact replica of Go's buildSitemap method */ async buildSitemap() { const p = await this.buildPageWithKind((0, kind_1.getKindMain)("sitemap")); await this.adaptPagination(p); return p; } /** * Build taxonomy - exact replica of Go's buildTaxonomy method */ async buildTaxonomy() { const taxonomyInfo = this.taxonomy.getTaxonomy(this.source.file.paths().path()); const singular = taxonomyInfo ? taxonomyInfo.singular() : ""; const tp = await this.newTaxonomy(this.source, this.c, singular); tp.pageMap = this.pageMapper; await this.buildOutput(tp); await this.buildPagination(tp); return tp; } /** * Build term - exact replica of Go's buildTerm method */ async buildTerm() { const p = this.source.file.paths(); const taxonomyInfo = this.taxonomy.getTaxonomy(p.path()); const singular = taxonomyInfo ? taxonomyInfo.singular() : ""; const term = p.unnormalized().baseNameNoIdentifier(); const t = await this.newTerm(this.source, this.c, singular, term); t.pageMap = this.pageMapper; await this.buildOutput(t); await this.buildPagination(t); return t; } async render(p) { if (this.c?.lazyRendered) { // If lazy rendering is enabled, set the lazyRender function this.c.lazyRender = async () => { const contentResult = await this.renderableDocument?.render({ maxSummaryLength: 300, // Default summary length wordsPerMinute: 200, // Default reading speed shortcodeRenderer: this.createShortcodeRenderer(p), // Add shortcode renderer }); this.c?.updateWithContentResult(contentResult); }; return; } const contentResult = await this.renderableDocument?.render({ maxSummaryLength: 300, // Default summary length wordsPerMinute: 200, // Default reading speed shortcodeRenderer: this.createShortcodeRenderer(p), // Add shortcode renderer }); this.c?.updateWithContentResult(contentResult); } async parse(contentBytes) { this.renderableDocument = await this.converter.prepareRender(contentBytes); // Create content instance and update with parsed results this.c = (0, pagecontent_1.newContent)(contentBytes); this.c.toc = this.renderableDocument.toc(); this.fmParser = new frontmatter_1.FrontMatterParserImpl(this.renderableDocument.frontMatter(), this.langSvc, this.taxonomySvc); await this.parseFrontMatter(); await this.parseLanguage(); await this.parseKind(); await this.parseTerms(); } /** * Parse terms - exact replica of Go's parseTerms method */ async parseTerms() { if (this.fm && this.fm.terms) { this.term.terms = this.fm.terms; } } /** * Parse kind - exact replica of Go's parseKind method */ async parseKind() { const path = this.source.file.paths(); let kind = ""; if (this.fm) { kind = this.fm.kind || ""; } if (kind === "") { kind = (0, kind_1.getKindMain)("page"); const base = path.baseNoLeadingSlash(); switch (base) { case section_1.PAGE_HOME_BASE: case "": kind = (0, kind_1.getKindMain)("home"); break; case standalone_1.STANDALONE_PAGE_404_BASE: kind = (0, kind_1.getKindMain)("404"); break; case standalone_1.STANDALONE_PAGE_SITEMAP_BASE: kind = (0, kind_1.getKindMain)("sitemap"); break; default: if (this.source.file.isBranchBundle()) { kind = (0, kind_1.getKindMain)("section"); const v = this.taxonomy.getTaxonomy(path.path()); if (!this.taxonomy.isZero(v)) { if (this.taxonomy.isTaxonomyPath(path.path())) { kind = (0, kind_1.getKindMain)("taxonomy"); } else { kind = (0, kind_1.getKindMain)("term"); } } } break; } } this.kind = kind; } /** * Parse language by default - exact replica of Go's parseLanguageByDefault method */ async parseLanguageByDefault() { const dl = this.langSvc.defaultLanguage(); const idx = await this.langSvc.getLanguageIndex(dl); this.source.identity.lang = dl; this.source.identity.langIdx = idx; } /** * Parse language by index - exact replica of Go's parseLanguageByIdx method */ async parseLanguageByIdx(langIdx) { const dl = this.langSvc.getLanguageByIndex(langIdx); this.source.identity.lang = dl; this.source.identity.langIdx = langIdx; } /** * Parse language - exact replica of Go's parseLanguage method */ async parseLanguage() { const [l, ok] = this.langSvc.getSourceLang(this.source.file.root()); if (ok) { const idx = await this.langSvc.getLanguageIndex(l); this.source.identity.lang = l; this.source.identity.langIdx = idx; return; } await this.parseLanguageByDefault(); } /** * Parse front matter - exact replica of Go's parseFrontMatter method */ async parseFrontMatter() { if (!this.fmParser) { this.fm = (0, frontmatter_1.newFrontMatter)(); return; } this.fm = await this.fmParser.parse(); } // Helper methods /** * Create shortcode renderer - returns a simple renderer for shortcodes */ createShortcodeRenderer(p) { return async (shortcode) => { if (!this.templateSvc) { log.warn(`Template service not available for shortcode: ${shortcode.name}`); return ''; } try { // 检查 shortcode 是否有 name if (!shortcode.name) { log.warn('Shortcode missing name'); return ''; } return await this.doRenderShortcode(shortcode, null, 0, p); } catch (error) { log.error(`Error rendering shortcode "${shortcode.name}":`, error); return ''; } }; } async doRenderShortcode(sc, parent, level, page) { if (sc.inline) { log.warn("Inline shortcodes are not supported yet."); return ''; } // Create shortcode data context using ShortcodeWithPage let data = new shortcode_1.ShortcodeWithPage(sc.params || {}, '', // Inner content will be set later page, parent, sc.name, typeof sc.params === 'object' && !Array.isArray(sc.params), sc.ordinal || 0, sc.indentation || '', sc.pos || 0); // Handle inner content if present if (sc.inner && sc.inner.length > 0) { let inner = ''; for (const innerData of sc.inner) { if (typeof innerData === 'string') { inner += innerData; } else if (typeof innerData === 'object' && 'name' in innerData) { // Handle nested shortcode const nestedResult = await this.doRenderShortcode(innerData, data, level + 1, page); inner += nestedResult; } else { log.error(`Illegal state on shortcode rendering of "${sc.name}". ` + `Illegal type in inner data: ${typeof innerData}`); return ''; } } // Handle markdown conversion for inner content if needed if (sc.doMarkup) { try { const newInner = await this.renderShortcodeMarkdown(page, inner); // Create a new instance with the processed inner content data = new shortcode_1.ShortcodeWithPage(sc.params || {}, newInner, page, parent, sc.name, typeof sc.params === 'object' && !Array.isArray(sc.params), sc.ordinal || 0, sc.indentation || '', sc.pos || 0); } catch (error) { throw new Error(`Failed to process inner content: ${error}`); } } else { // Create a new instance with the raw inner content data = new shortcode_1.ShortcodeWithPage(sc.params || {}, inner, page, parent, sc.name, typeof sc.params === 'object' && !Array.isArray(sc.params), sc.ordinal || 0, sc.indentation || '', sc.pos || 0); } } // Render the shortcode template try { let result = await this.templateSvc?.execute(sc.name, data); // Handle indentation if (!sc.inner?.length && sc.indentation) { const lines = result?.split('\n'); result = lines?.map((line, i) => i === 0 ? line : sc.indentation + line).join('\n'); } return result || ''; } catch (error) { if (error instanceof template_1.TemplateError && error.code === 'SHORTCODE_NOT_FOUND') { return sc.rawContent; } throw new Error(`Failed to process shortcode: ${error instanceof Error ? error.message : String(error)}`); } } async renderShortcodeMarkdown(p, md) { try { const docCtx = { document: p, documentID: p.path(), documentName: p.name(), filename: p.pageFile().filename() }; const renderCtx = { ctx: {}, src: new TextEncoder().encode(md), renderTOC: false, getRenderer: () => null }; const result = await this.converter.convert(docCtx, renderCtx); let resStr = new TextDecoder().decode(result.bytes()); // Clean up single line content similar to Go version if (!resStr.includes('\n')) { resStr = resStr.replace(/^<p>(.*)<\/p>\n$/, '$1'); } return resStr; } catch (error) { log.error(`Error rendering shortcode markdown:`, error); return ''; } } /** * Build pagination - creates pagination for page * TypeScript equivalent of Go's buildPagination method */ async buildPagination(p) { // Create PagerSvc implementation exactly like Go's svc struct const pagerSvc = { pageSize: () => 10, globalRegularPages: async () => { return this.contentHub ? await this.contentHub.globalRegularPages() : []; } }; // Create a new PaginatorManagerImpl directly, like Go's NewPaginator(svc, p) const paginatorManager = new paginator_1.PaginatorManagerImpl(pagerSvc, p); // Assign to page's pagerManager - exactly like Go's p.PagerManager = NewPaginator(svc, p) p.pagerManager = paginatorManager; } /** * Adapt pagination - creates empty pagination for pages that don't support it * TypeScript equivalent of Go's adaptPagination method */ async adaptPagination(p) { // Create empty paginator equivalent to Go's valueobject.PaginatorEmpty const emptyPaginatorManager = { current: () => { // Return a default empty pager to match the interface contract const emptyPaginator = new paginator_1.PaginatorImpl([], 0, 10, p.path()); return emptyPaginator.pagers()[0]; }, setCurrent: (current) => { throw new Error(`pagination not supported for this page: ${p.path()}`); }, paginator: async () => { throw new Error(`pagination not supported for this page: ${p.path()}`); }, paginate: async (groups) => { throw new Error(`pagination not supported for this page: ${p.path()}`); } }; // Assign to page's pagerManager p.pagerManager = emptyPaginatorManager; } /** * Build output - builds page output */ async buildOutput(p) { // Implementation would build page output } /** * New page - creates a new page instance based on Go version */ async newPage(source, content) { const meta = new pagemeta_1.Meta("always", {}, 0, new Date()); const layout = new pagelayout_1.Layout(); return new page_1.PageImpl(source, content, meta, layout, "", (0, kind_1.getKindMain)("page"), this.pageMapper); } /** * New taxonomy - creates a new taxonomy page */ async newTaxonomy(source, content, singular) { const meta = new pagemeta_1.Meta("always", {}, 0, new Date()); const layout = new pagelayout_1.Layout(); const page = new page_1.TaxonomyPageImpl(source, content, meta, layout, singular, singular, (0, kind_1.getKindMain)("taxonomy"), this.pageMapper); return page; } /** * New term - creates a new term page */ async newTerm(source, content, singular, term) { const meta = new pagemeta_1.Meta("always", {}, 0, new Date()); const layout = new pagelayout_1.Layout(); const page = new page_1.TermPageImpl(source, content, meta, layout, singular, term, term, (0, kind_1.getKindMain)("term"), this.pageMapper); return page; } } exports.PageBuilder = PageBuilder; //# sourceMappingURL=pagebuilder.js.map