feat: first cut at the full renderer

This commit is contained in:
2026-02-20 17:54:04 +01:00
parent 22cb63e0a7
commit 3bbc5281e8
25 changed files with 4989 additions and 976 deletions

View File

@@ -2,8 +2,16 @@ import { dialog } from 'electron';
import { getPostEngine } from '../engine/PostEngine';
import { getProjectEngine } from '../engine/ProjectEngine';
import { getMetaEngine } from '../engine/MetaEngine';
import { getMediaEngine } from '../engine/MediaEngine';
import { getPostMediaEngine } from '../engine/PostMediaEngine';
import { taskManager } from '../engine/TaskManager';
import { getBlogGenerationEngine, resolvePublicBaseUrl } from '../engine/BlogGenerationEngine';
import {
getBlogGenerationEngine,
resolvePublicBaseUrl,
type BlogGenerationResult,
type BlogGenerationSection,
} from '../engine/BlogGenerationEngine';
import { resolvePageTitle } from '../engine/PageRenderer';
type SafeHandle = (channel: string, handler: (...args: any[]) => Promise<any>) => void;
@@ -12,6 +20,8 @@ export function registerBlogHandlers(safeHandle: SafeHandle): void {
const projectEngine = getProjectEngine();
const postEngine = getPostEngine();
const metaEngine = getMetaEngine();
const mediaEngine = getMediaEngine();
const postMediaEngine = getPostMediaEngine();
const blogGenerationEngine = getBlogGenerationEngine();
const project = await projectEngine.getActiveProject();
@@ -22,6 +32,8 @@ export function registerBlogHandlers(safeHandle: SafeHandle): void {
const dataDir = projectEngine.getDataDir(project.id, project.dataPath);
postEngine.setProjectContext(project.id, dataDir);
metaEngine.setProjectContext(project.id, dataDir);
mediaEngine.setProjectContext(project.id, dataDir, dataDir);
postMediaEngine.setProjectContext(project.id);
if (!metaEngine.isInitialized()) {
await metaEngine.syncOnStartup();
@@ -33,27 +45,90 @@ export function registerBlogHandlers(safeHandle: SafeHandle): void {
await dialog.showMessageBox({
type: 'warning',
title: 'Public URL Required',
message: 'Sitemap generation requires a public URL.',
detail: 'Set Project → Public URL in Settings before generating a sitemap.',
message: 'Site rendering requires a public URL.',
detail: 'Set Project → Public URL in Settings before rendering the site.',
});
throw new Error('Project public URL is not configured');
}
const taskId = `sitemap-generate-${Date.now()}`;
const taskTimestamp = Date.now();
const taskGroupId = `site-render-${taskTimestamp}`;
const taskGroupName = 'Render Site';
const language = metadata?.mainLanguage?.trim() || 'en';
const pageTitle = resolvePageTitle(metadata, project.name, project.description ?? undefined);
const baseOptions = {
projectId: project.id,
projectName: metadata?.name?.trim() || project.name,
projectDescription: metadata?.description,
dataDir,
baseUrl,
maxPostsPerPage: metadata?.maxPostsPerPage,
language,
pageTitle,
};
return taskManager.runTask({
id: taskId,
name: 'Generate Sitemap',
const runSectionTask = async (
section: BlogGenerationSection,
taskName: string,
taskIdPrefix: string,
): Promise<BlogGenerationResult> => {
return taskManager.runTask({
id: `${taskIdPrefix}-${taskTimestamp}`,
name: taskName,
groupId: taskGroupId,
groupName: taskGroupName,
execute: async (onProgress) => {
return blogGenerationEngine.generate({
...baseOptions,
sections: [section],
}, (progress, message) => onProgress(progress, message || ''));
},
});
};
const mergeResults = (results: BlogGenerationResult[]): BlogGenerationResult => {
const first = results[0];
return {
path: first.path,
urlCount: Math.max(...results.map((result) => result.urlCount)),
postCount: Math.max(...results.map((result) => result.postCount)),
feedPostCount: Math.max(...results.map((result) => result.feedPostCount)),
tagCount: Math.max(...results.map((result) => result.tagCount)),
categoryCount: Math.max(...results.map((result) => result.categoryCount)),
archiveCount: Math.max(...results.map((result) => result.archiveCount)),
pagesGenerated: results.reduce((sum, result) => sum + result.pagesGenerated, 0),
feeds: {
rssPath: first.feeds.rssPath,
atomPath: first.feeds.atomPath,
},
changed: {
sitemap: results.some((result) => result.changed.sitemap),
rss: results.some((result) => result.changed.rss),
atom: results.some((result) => result.changed.atom),
},
};
};
const coreResult = await taskManager.runTask({
id: `site-render-core-${taskTimestamp}`,
name: 'Render Site Core',
groupId: taskGroupId,
groupName: taskGroupName,
execute: async (onProgress) => {
return blogGenerationEngine.generate({
projectId: project.id,
projectName: metadata?.name?.trim() || project.name,
projectDescription: metadata?.description,
dataDir,
baseUrl,
maxPostsPerPage: metadata?.maxPostsPerPage,
...baseOptions,
sections: ['core'],
}, (progress, message) => onProgress(progress, message || ''));
},
});
const [singleResult, categoryResult, tagResult, dateResult] = await Promise.all([
runSectionTask('single', 'Render Single Posts', 'site-render-single'),
runSectionTask('category', 'Render Category Archives', 'site-render-category'),
runSectionTask('tag', 'Render Tag Archives', 'site-render-tag'),
runSectionTask('date', 'Render Date Archives', 'site-render-date'),
]);
return mergeResults([coreResult, singleResult, categoryResult, tagResult, dateResult]);
});
}