feat: render the menu
This commit is contained in:
@@ -10,11 +10,14 @@ import {
|
||||
PageRenderer,
|
||||
PREVIEW_ASSETS,
|
||||
PREVIEW_IMAGE_ASSETS,
|
||||
buildTemplateMenuItems,
|
||||
buildCanonicalPostPath,
|
||||
type CategoryRenderSettings,
|
||||
type HtmlRewriteContext,
|
||||
type TemplateMenuItem,
|
||||
} from './PageRenderer';
|
||||
import { getPicoStylesheetHref, sanitizePicoTheme, type PicoThemeName } from '../shared/picoThemes';
|
||||
import type { MenuDocument } from './MenuEngine';
|
||||
|
||||
const DEFAULT_MAX_POSTS_PER_PAGE = 50;
|
||||
const MIN_MAX_POSTS_PER_PAGE = 1;
|
||||
@@ -31,6 +34,7 @@ export interface BlogGenerationOptions {
|
||||
pageTitle?: string;
|
||||
picoTheme?: PicoThemeName;
|
||||
categorySettings?: Record<string, CategoryRenderSettings>;
|
||||
menu?: MenuDocument;
|
||||
sections?: BlogGenerationSection[];
|
||||
}
|
||||
|
||||
@@ -654,6 +658,7 @@ export class BlogGenerationEngine {
|
||||
const pageContext = {
|
||||
page_title: pageTitle,
|
||||
language,
|
||||
menu_items: buildTemplateMenuItems(options.menu),
|
||||
pico_stylesheet_href: getPicoStylesheetHref(sanitizePicoTheme(options.picoTheme)),
|
||||
};
|
||||
|
||||
@@ -1269,6 +1274,7 @@ export class BlogGenerationEngine {
|
||||
const pageContext = {
|
||||
page_title: pageTitle,
|
||||
language,
|
||||
menu_items: buildTemplateMenuItems(options.menu),
|
||||
pico_stylesheet_href: getPicoStylesheetHref(sanitizePicoTheme(options.picoTheme)),
|
||||
};
|
||||
const pageRenderer = new PageRenderer(this.mediaEngine, this.postMediaEngine, this.postEngine);
|
||||
@@ -1424,7 +1430,7 @@ export class BlogGenerationEngine {
|
||||
posts: PostData[],
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
htmlDir: string,
|
||||
pageContext: { page_title: string; language: string },
|
||||
pageContext: { page_title: string; language: string; menu_items?: TemplateMenuItem[] },
|
||||
pageRenderer: PageRenderer,
|
||||
onPageGenerated: (message: string) => void,
|
||||
): Promise<number> {
|
||||
@@ -1483,7 +1489,7 @@ export class BlogGenerationEngine {
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
maxPostsPerPage: number,
|
||||
htmlDir: string,
|
||||
pageContext: { page_title: string; language: string; pico_stylesheet_href?: string },
|
||||
pageContext: { page_title: string; language: string; menu_items?: TemplateMenuItem[]; pico_stylesheet_href?: string },
|
||||
pageRenderer: PageRenderer,
|
||||
categorySettings: Record<string, CategoryRenderSettings>,
|
||||
onPageGenerated: (message: string) => void,
|
||||
@@ -1522,7 +1528,7 @@ export class BlogGenerationEngine {
|
||||
posts: PostData[],
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
htmlDir: string,
|
||||
pageContext: { page_title: string; language: string; pico_stylesheet_href?: string },
|
||||
pageContext: { page_title: string; language: string; menu_items?: TemplateMenuItem[]; pico_stylesheet_href?: string },
|
||||
pageRenderer: PageRenderer,
|
||||
onPageGenerated: (message: string) => void,
|
||||
): Promise<number> {
|
||||
@@ -1551,7 +1557,7 @@ export class BlogGenerationEngine {
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
maxPostsPerPage: number,
|
||||
htmlDir: string,
|
||||
pageContext: { page_title: string; language: string; pico_stylesheet_href?: string },
|
||||
pageContext: { page_title: string; language: string; menu_items?: TemplateMenuItem[]; pico_stylesheet_href?: string },
|
||||
pageRenderer: PageRenderer,
|
||||
categorySettings: Record<string, CategoryRenderSettings>,
|
||||
onPageGenerated: (message: string) => void,
|
||||
@@ -1602,7 +1608,7 @@ export class BlogGenerationEngine {
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
maxPostsPerPage: number,
|
||||
htmlDir: string,
|
||||
pageContext: { page_title: string; language: string; pico_stylesheet_href?: string },
|
||||
pageContext: { page_title: string; language: string; menu_items?: TemplateMenuItem[]; pico_stylesheet_href?: string },
|
||||
pageRenderer: PageRenderer,
|
||||
categorySettings: Record<string, CategoryRenderSettings>,
|
||||
onPageGenerated: (message: string) => void,
|
||||
@@ -1655,7 +1661,7 @@ export class BlogGenerationEngine {
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
maxPostsPerPage: number,
|
||||
htmlDir: string,
|
||||
pageContext: { page_title: string; language: string; pico_stylesheet_href?: string },
|
||||
pageContext: { page_title: string; language: string; menu_items?: TemplateMenuItem[]; pico_stylesheet_href?: string },
|
||||
pageRenderer: PageRenderer,
|
||||
categorySettings: Record<string, CategoryRenderSettings>,
|
||||
onPageGenerated: (message: string) => void,
|
||||
@@ -1708,7 +1714,7 @@ export class BlogGenerationEngine {
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
maxPostsPerPage: number,
|
||||
htmlDir: string,
|
||||
pageContext: { page_title: string; language: string; pico_stylesheet_href?: string },
|
||||
pageContext: { page_title: string; language: string; menu_items?: TemplateMenuItem[]; pico_stylesheet_href?: string },
|
||||
pageRenderer: PageRenderer,
|
||||
categorySettings: Record<string, CategoryRenderSettings>,
|
||||
onPageGenerated: (message: string) => void,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { marked } from 'marked';
|
||||
import { Liquid } from 'liquidjs';
|
||||
import type { MediaData } from './MediaEngine';
|
||||
import type { PostData } from './PostEngine';
|
||||
import type { MenuDocument, MenuItemData } from './MenuEngine';
|
||||
import { PICO_THEME_NAMES } from '../shared/picoThemes';
|
||||
import { TAG_CLOUD_RUNTIME_JS } from './assets/tagCloudRuntime';
|
||||
import { resolveRenderLanguageFromProjectPreferences, translateRender } from '../shared/i18n';
|
||||
@@ -50,6 +51,7 @@ export type DateArchiveContext = {
|
||||
export interface PostListTemplateContext {
|
||||
page_title: string;
|
||||
language: string;
|
||||
menu_items: TemplateMenuItem[];
|
||||
pico_stylesheet_href?: string;
|
||||
html_theme_attribute?: string;
|
||||
is_date_archive: boolean;
|
||||
@@ -78,6 +80,7 @@ export interface PostListTemplateContext {
|
||||
export interface SinglePostTemplateContext {
|
||||
page_title: string;
|
||||
language: string;
|
||||
menu_items: TemplateMenuItem[];
|
||||
pico_stylesheet_href?: string;
|
||||
html_theme_attribute?: string;
|
||||
post: TemplatePostEntry;
|
||||
@@ -88,12 +91,20 @@ export interface SinglePostTemplateContext {
|
||||
export interface NotFoundTemplateContext {
|
||||
page_title: string;
|
||||
language: string;
|
||||
menu_items?: TemplateMenuItem[];
|
||||
not_found_message?: string;
|
||||
not_found_back_label?: string;
|
||||
pico_stylesheet_href?: string;
|
||||
html_theme_attribute?: string;
|
||||
}
|
||||
|
||||
export interface TemplateMenuItem {
|
||||
title: string;
|
||||
href: string;
|
||||
has_children: boolean;
|
||||
children: TemplateMenuItem[];
|
||||
}
|
||||
|
||||
export interface RoutePagination {
|
||||
pathname: string;
|
||||
page: number;
|
||||
@@ -260,6 +271,48 @@ export function parseIntegerParam(value: string | undefined): number | null {
|
||||
return Number.isInteger(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function buildMenuItemHref(item: MenuItemData): string {
|
||||
if (item.kind === 'home') {
|
||||
return '/';
|
||||
}
|
||||
|
||||
if (item.kind === 'category-archive') {
|
||||
const categoryName = (item.categoryName || '').trim();
|
||||
return categoryName.length > 0 ? `/category/${encodeURIComponent(categoryName)}/` : '/';
|
||||
}
|
||||
|
||||
if (item.kind === 'page') {
|
||||
const normalizedSlug = (item.pageSlug || '')
|
||||
.split('/')
|
||||
.map((segment) => segment.trim())
|
||||
.filter((segment) => segment.length > 0)
|
||||
.map((segment) => encodeURIComponent(segment))
|
||||
.join('/');
|
||||
return normalizedSlug.length > 0 ? `/${normalizedSlug}/` : '/';
|
||||
}
|
||||
|
||||
return '#';
|
||||
}
|
||||
|
||||
function toTemplateMenuItem(item: MenuItemData): TemplateMenuItem {
|
||||
const children = (Array.isArray(item.children) ? item.children : []).map((child) => toTemplateMenuItem(child));
|
||||
return {
|
||||
title: item.title,
|
||||
href: buildMenuItemHref(item),
|
||||
has_children: children.length > 0,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTemplateMenuItems(menu: MenuDocument | null | undefined): TemplateMenuItem[] {
|
||||
const items = menu?.items;
|
||||
if (!Array.isArray(items)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return items.map((item) => toTemplateMenuItem(item));
|
||||
}
|
||||
|
||||
export function normalizeMacroName(name: string): string {
|
||||
if (name === 'photo_album') {
|
||||
return 'photo_archive';
|
||||
@@ -776,6 +829,7 @@ export class PageRenderer {
|
||||
basePathname: string;
|
||||
page_title: string;
|
||||
language: string;
|
||||
menu_items?: TemplateMenuItem[];
|
||||
pico_stylesheet_href?: string;
|
||||
html_theme_attribute?: string;
|
||||
pagination?: PaginationContext;
|
||||
@@ -890,6 +944,7 @@ export class PageRenderer {
|
||||
return {
|
||||
page_title: options.page_title,
|
||||
language: options.language,
|
||||
menu_items: options.menu_items ?? [],
|
||||
pico_stylesheet_href: options.pico_stylesheet_href,
|
||||
html_theme_attribute: options.html_theme_attribute,
|
||||
is_date_archive: options.routeKind === 'date',
|
||||
@@ -945,6 +1000,7 @@ export class PageRenderer {
|
||||
basePathname: string;
|
||||
page_title: string;
|
||||
language: string;
|
||||
menu_items?: TemplateMenuItem[];
|
||||
pico_stylesheet_href?: string;
|
||||
html_theme_attribute?: string;
|
||||
pagination?: PaginationContext;
|
||||
@@ -971,7 +1027,7 @@ export class PageRenderer {
|
||||
async renderSinglePost(
|
||||
post: PostData,
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
pageContext: { page_title: string; language: string; pico_stylesheet_href?: string; html_theme_attribute?: string },
|
||||
pageContext: { page_title: string; language: string; menu_items?: TemplateMenuItem[]; pico_stylesheet_href?: string; html_theme_attribute?: string },
|
||||
postEngine?: PostEngineContract,
|
||||
): Promise<string> {
|
||||
const renderablePost = postEngine
|
||||
@@ -979,6 +1035,7 @@ export class PageRenderer {
|
||||
: post;
|
||||
const context: SinglePostTemplateContext = {
|
||||
...pageContext,
|
||||
menu_items: pageContext.menu_items ?? [],
|
||||
post: {
|
||||
id: renderablePost.id,
|
||||
title: renderablePost.title,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { getMetaEngine, type ProjectMetadata } from './MetaEngine';
|
||||
import { getMediaEngine, type MediaData } from './MediaEngine';
|
||||
import { getMenuEngine, type MenuDocument } from './MenuEngine';
|
||||
import { getPostMediaEngine } from './PostMediaEngine';
|
||||
import { getPostEngine, type PostData, type PostFilter } from './PostEngine';
|
||||
import { getProjectEngine } from './ProjectEngine';
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
PageRenderer,
|
||||
PREVIEW_ASSETS,
|
||||
PREVIEW_IMAGE_ASSETS,
|
||||
buildTemplateMenuItems,
|
||||
buildCanonicalPostPath,
|
||||
clampMaxPostsPerPage,
|
||||
parseRoutePagination,
|
||||
@@ -43,11 +45,17 @@ interface MetaEngineContract {
|
||||
syncOnStartup?: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface MenuEngineContract {
|
||||
getMenu: () => Promise<MenuDocument>;
|
||||
setProjectContext: (projectId: string, dataDir?: string) => void;
|
||||
}
|
||||
|
||||
interface PreviewServerDependencies {
|
||||
postEngine: PostEngineContract;
|
||||
mediaEngine: MediaEngineContract;
|
||||
postMediaEngine: PostMediaEngineContract;
|
||||
settingsEngine: MetaEngineContract;
|
||||
menuEngine: MenuEngineContract;
|
||||
getActiveProjectContext: () => Promise<ActiveProjectContext>;
|
||||
}
|
||||
|
||||
@@ -56,6 +64,7 @@ export class PreviewServer {
|
||||
private readonly mediaEngine: MediaEngineContract;
|
||||
private readonly postMediaEngine: PostMediaEngineContract;
|
||||
private readonly settingsEngine: MetaEngineContract;
|
||||
private readonly menuEngine: MenuEngineContract;
|
||||
private readonly getActiveProjectContext: () => Promise<ActiveProjectContext>;
|
||||
private readonly pageRenderer: PageRenderer;
|
||||
private server: Server | null = null;
|
||||
@@ -66,6 +75,7 @@ export class PreviewServer {
|
||||
this.mediaEngine = dependencies?.mediaEngine ?? getMediaEngine();
|
||||
this.postMediaEngine = dependencies?.postMediaEngine ?? getPostMediaEngine();
|
||||
this.settingsEngine = dependencies?.settingsEngine ?? getMetaEngine();
|
||||
this.menuEngine = dependencies?.menuEngine ?? getMenuEngine();
|
||||
this.getActiveProjectContext = dependencies?.getActiveProjectContext ?? (async () => {
|
||||
const projectEngine = getProjectEngine();
|
||||
const activeProject = await projectEngine.getActiveProject();
|
||||
@@ -165,12 +175,15 @@ export class PreviewServer {
|
||||
this.mediaEngine.setProjectContext?.(context.projectId, context.dataDir, context.dataDir);
|
||||
this.postMediaEngine.setProjectContext(context.projectId);
|
||||
this.settingsEngine.setProjectContext(context.projectId, context.dataDir);
|
||||
this.menuEngine.setProjectContext(context.projectId, context.dataDir);
|
||||
|
||||
if (this.settingsEngine.isInitialized && this.settingsEngine.syncOnStartup && !this.settingsEngine.isInitialized()) {
|
||||
await this.settingsEngine.syncOnStartup();
|
||||
}
|
||||
|
||||
const metadata = await this.settingsEngine.getProjectMetadata();
|
||||
const menu = await this.menuEngine.getMenu().catch(() => ({ items: [] }));
|
||||
const menuItems = buildTemplateMenuItems(menu);
|
||||
const categorySettings = this.resolveCategorySettings(metadata);
|
||||
const listExcludedCategories = this.resolveListExcludedCategories(categorySettings);
|
||||
const language = metadata?.mainLanguage?.trim() || 'en';
|
||||
@@ -190,6 +203,7 @@ export class PreviewServer {
|
||||
const stylePreviewHtml = await this.renderStylePreview(htmlRewriteContext, {
|
||||
pageTitle,
|
||||
language,
|
||||
menuItems,
|
||||
picoStylesheetHref,
|
||||
htmlThemeAttribute: previewThemeMode && previewThemeMode !== 'auto' ? `data-theme="${previewThemeMode}"` : undefined,
|
||||
}, categorySettings, listExcludedCategories);
|
||||
@@ -218,6 +232,7 @@ export class PreviewServer {
|
||||
const result = await this.resolveRoute(pathname, maxPostsPerPage, htmlRewriteContext, {
|
||||
pageTitle,
|
||||
language,
|
||||
menuItems,
|
||||
picoStylesheetHref,
|
||||
htmlThemeAttribute: undefined,
|
||||
}, categorySettings, listExcludedCategories, {
|
||||
@@ -228,6 +243,7 @@ export class PreviewServer {
|
||||
const notFoundHtml = await this.pageRenderer.renderNotFound({
|
||||
page_title: '404 Not Found',
|
||||
language,
|
||||
menu_items: menuItems,
|
||||
pico_stylesheet_href: picoStylesheetHref,
|
||||
html_theme_attribute: undefined,
|
||||
});
|
||||
@@ -246,7 +262,7 @@ export class PreviewServer {
|
||||
pathname: string,
|
||||
maxPostsPerPage: number,
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
pageContext: { pageTitle: string; language: string; picoStylesheetHref: string; htmlThemeAttribute?: string },
|
||||
pageContext: { pageTitle: string; language: string; menuItems: ReturnType<typeof buildTemplateMenuItems>; picoStylesheetHref: string; htmlThemeAttribute?: string },
|
||||
categorySettings: Record<string, CategoryRenderSettings>,
|
||||
listExcludedCategories: string[],
|
||||
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string },
|
||||
@@ -274,6 +290,7 @@ export class PreviewServer {
|
||||
return this.pageRenderer.renderSinglePost(post, rewriteContext, {
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -287,6 +304,7 @@ export class PreviewServer {
|
||||
return this.pageRenderer.renderSinglePost(post, rewriteContext, {
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -303,6 +321,7 @@ export class PreviewServer {
|
||||
return this.pageRenderer.renderSinglePost(post, rewriteContext, {
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -316,6 +335,7 @@ export class PreviewServer {
|
||||
return this.pageRenderer.renderSinglePost(post, rewriteContext, {
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -332,6 +352,7 @@ export class PreviewServer {
|
||||
categorySettings,
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -350,6 +371,7 @@ export class PreviewServer {
|
||||
categorySettings,
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -384,6 +406,7 @@ export class PreviewServer {
|
||||
return this.pageRenderer.renderSinglePost(post, rewriteContext, {
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -407,6 +430,7 @@ export class PreviewServer {
|
||||
categorySettings,
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -427,6 +451,7 @@ export class PreviewServer {
|
||||
categorySettings,
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -445,6 +470,7 @@ export class PreviewServer {
|
||||
categorySettings,
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -459,6 +485,7 @@ export class PreviewServer {
|
||||
return this.pageRenderer.renderSinglePost(pagePost, rewriteContext, {
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
@@ -469,7 +496,7 @@ export class PreviewServer {
|
||||
|
||||
private async renderStylePreview(
|
||||
rewriteContext: HtmlRewriteContext,
|
||||
pageContext: { pageTitle: string; language: string; picoStylesheetHref: string; htmlThemeAttribute?: string },
|
||||
pageContext: { pageTitle: string; language: string; menuItems: ReturnType<typeof buildTemplateMenuItems>; picoStylesheetHref: string; htmlThemeAttribute?: string },
|
||||
categorySettings: Record<string, CategoryRenderSettings>,
|
||||
listExcludedCategories: string[],
|
||||
): Promise<string> {
|
||||
@@ -482,6 +509,7 @@ export class PreviewServer {
|
||||
return this.pageRenderer.renderNotFound({
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
});
|
||||
@@ -496,6 +524,7 @@ export class PreviewServer {
|
||||
categorySettings,
|
||||
page_title: pageContext.pageTitle,
|
||||
language: pageContext.language,
|
||||
menu_items: pageContext.menuItems,
|
||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||
}, this.postEngine);
|
||||
|
||||
16
src/main/engine/templates/partials/menu-items.liquid
Normal file
16
src/main/engine/templates/partials/menu-items.liquid
Normal file
@@ -0,0 +1,16 @@
|
||||
<ul class="blog-menu-list">
|
||||
{% for item in items %}
|
||||
<li class="blog-menu-item{% if item.has_children %} blog-menu-item-with-children{% endif %}">
|
||||
{% if item.href == '#' %}
|
||||
<span class="blog-menu-link">{{ item.title }}</span>
|
||||
{% else %}
|
||||
<a class="blog-menu-link" href="{{ item.href }}">{{ item.title }}</a>
|
||||
{% endif %}
|
||||
{% if item.has_children %}
|
||||
<div class="blog-menu-submenu">
|
||||
{% render 'partials/menu-items', items: item.children %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
5
src/main/engine/templates/partials/menu.liquid
Normal file
5
src/main/engine/templates/partials/menu.liquid
Normal file
@@ -0,0 +1,5 @@
|
||||
{% if menu_items and menu_items.size > 0 %}
|
||||
<nav class="blog-menu">
|
||||
{% render 'partials/menu-items', items: menu_items %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
@@ -2,6 +2,17 @@
|
||||
:root { color-scheme: light dark; }
|
||||
body { max-width: 960px; margin: 0 auto; padding: 2rem 1rem 4rem; background: var(--pico-background-color, var(--background-color)); color: var(--pico-color, var(--color)); }
|
||||
main { display: grid; gap: 1rem; }
|
||||
.blog-menu { border-top: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-bottom: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: .4rem 0; margin: -.15rem 0 .2rem; }
|
||||
.blog-menu-list { list-style: none; display: flex; flex-wrap: wrap; gap: .25rem .75rem; margin: 0; padding: 0; }
|
||||
.blog-menu-item { position: relative; }
|
||||
.blog-menu-link { display: inline-flex; align-items: center; color: var(--pico-muted-color, var(--muted-color)); text-decoration: none; font-size: .94rem; line-height: 1.4; padding: .2rem .1rem; }
|
||||
.blog-menu-item-with-children > .blog-menu-link::after { content: '▾'; font-size: .7em; margin-left: .38rem; opacity: .72; }
|
||||
.blog-menu-link:hover,
|
||||
.blog-menu-link:focus-visible { color: var(--pico-color, var(--color)); text-decoration: underline; }
|
||||
.blog-menu-submenu { position: absolute; top: calc(100% + .12rem); left: 0; min-width: 12rem; display: none; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-card-background-color, var(--card-background-color)); padding: .4rem; z-index: 10; }
|
||||
.blog-menu-submenu .blog-menu-list { flex-direction: column; flex-wrap: nowrap; gap: .2rem; }
|
||||
.blog-menu-item-with-children:hover > .blog-menu-submenu,
|
||||
.blog-menu-item-with-children:focus-within > .blog-menu-submenu { display: block; }
|
||||
.post { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: 1rem; background: var(--pico-card-background-color, var(--card-background-color)); }
|
||||
.post iframe { width: 100%; min-height: 20rem; }
|
||||
.macro-gallery, .macro-photo-archive, .macro-tag-cloud { border: 1px dashed var(--pico-muted-border-color, var(--muted-border-color)); padding: .75rem; margin: 1rem 0; }
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% render 'partials/menu', menu_items: menu_items, language: language %}
|
||||
|
||||
<section class="post-list" data-template="post-list" data-list-page="{{ is_list_page }}" data-first-page="{{ is_first_page }}" data-last-page="{{ is_last_page }}">
|
||||
{% for day_block in day_blocks %}
|
||||
{% if day_block.show_date_marker %}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<main>
|
||||
<article class="single-post" data-template="single-post">
|
||||
<h1>{{ post.title }}</h1>
|
||||
{% render 'partials/menu', menu_items: menu_items, language: language %}
|
||||
<div class="post">{{ post.content | markdown: post.id, canonical_post_path_by_slug, canonical_media_path_by_source_path, language }}</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getProjectEngine } from '../engine/ProjectEngine';
|
||||
import { getMetaEngine } from '../engine/MetaEngine';
|
||||
import { getMediaEngine } from '../engine/MediaEngine';
|
||||
import { getPostMediaEngine } from '../engine/PostMediaEngine';
|
||||
import { getMenuEngine } from '../engine/MenuEngine';
|
||||
import { taskManager } from '../engine/TaskManager';
|
||||
import {
|
||||
getBlogGenerationEngine,
|
||||
@@ -24,6 +25,7 @@ export function registerBlogHandlers(safeHandle: SafeHandle): void {
|
||||
const metaEngine = getMetaEngine();
|
||||
const mediaEngine = getMediaEngine();
|
||||
const postMediaEngine = getPostMediaEngine();
|
||||
const menuEngine = getMenuEngine();
|
||||
|
||||
const project = await projectEngine.getActiveProject();
|
||||
if (!project) {
|
||||
@@ -35,12 +37,14 @@ export function registerBlogHandlers(safeHandle: SafeHandle): void {
|
||||
metaEngine.setProjectContext(project.id, dataDir);
|
||||
mediaEngine.setProjectContext(project.id, dataDir, dataDir);
|
||||
postMediaEngine.setProjectContext(project.id);
|
||||
menuEngine.setProjectContext(project.id, dataDir);
|
||||
|
||||
if (!metaEngine.isInitialized()) {
|
||||
await metaEngine.syncOnStartup();
|
||||
}
|
||||
|
||||
const metadata = await metaEngine.getProjectMetadata();
|
||||
const menu = await menuEngine.getMenu();
|
||||
const baseUrl = resolvePublicBaseUrl(metadata?.publicUrl);
|
||||
if (!baseUrl) {
|
||||
await dialog.showMessageBox({
|
||||
@@ -66,6 +70,7 @@ export function registerBlogHandlers(safeHandle: SafeHandle): void {
|
||||
pageTitle,
|
||||
picoTheme: metadata?.picoTheme,
|
||||
categorySettings: (metadata as any)?.categorySettings,
|
||||
menu,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user