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