Changes
15 changed files (+260/-37)
-
-
@@ -136,6 +136,7 @@ "lang","base-url", "og-image", "user-css", "not-found-filename", ], boolean: [ "help",
-
@@ -399,6 +400,18 @@ const userCSS = userCSSInput? fileSystemReader.fromFsPath(userCSSInput) : undefined; const notFoundPageDisabled = configFile.output?.notFoundPage?.enabled === false; if (notFoundPageDisabled) { log.debug("Not Found page generation is disabled"); } const notFoundPageFilename = flags["not-found-filename"] || configFile.output?.notFoundPage?.filename || "404.html"; if (!notFoundPageDisabled && notFoundPageFilename) { log.debug(`output.notFoundPage.filename = "${notFoundPageFilename}"`); } const pageBuilder = new DefaultThemeBuilder({ siteName, copyright,
-
@@ -410,6 +423,9 @@ openGraph: ogImage && {image: ogImage, }, userCSS, notFoundPage: notFoundPageDisabled ? undefined : { filename: notFoundPageFilename, }, }); const documentTree = await treeBuilder.build({
-
@@ -534,6 +550,16 @@ this CSS can override every styles. The target file MUST be inside ${b("VAULT_PATH") }. Corresponding config key is ${p("output.userCSS")} (${t("string")}). --not-found-filename <FILENAME> File name of Not Found page. Macana generates Not Found page only if ${ b("--base-url") } is set. [default: 404.html] Corresponding config key is ${p("output.notFoundPage.filename")} (${ t("string") }). --base-url <PATH OR URL> URL or path to base at.
-
-
-
@@ -42,6 +42,21 @@Assets referenced in the CSS file are automatically included in the final output, such as font file. Referenced assets needs to be relative and inside the vault too. ### `output.notFoundPage.enabled` - Type: `boolean` - Default: `true` if `output.baseURL` is full URL or absolute path, otherwise `false` Enable generation of Not Found page. ### `output.notFoundPage.filename` - Type: `string` (file name) - CLI: `--not-found-filename <FILENAME>` option - Default: `404.html` Filename of generated Not Found page. ### `output.precompress` - Type: `boolean`
-
-
-
@@ -19,7 +19,7 @@ - [x] Option for OpenGraph attributes- [x] Custom fonts per language - [x] User provided global CSS - [ ] Headings down levelling option (render `# Foo` as `<h2>Foo</h2>`) - [ ] 404 page - [x] 404 page ## v0.1.2
-
-
-
@@ -19,6 +19,12 @@baseURL?: string; userCSS?: string; notFoundPage?: { enabled?: boolean; filename?: string; }; }; metadata?: {
-
@@ -80,6 +86,10 @@ path: fsPathParser,baseURL: parser.string({ nonEmpty: true, trim: true }), precompress: parser.boolean, userCSS: fsPathParser, notFoundPage: parser.object({ enabled: parser.boolean, filename: parser.string({ nonEmpty: true, trim: true }), }), }), metadata: parser.object({ name: parser.string({ nonEmpty: true }),
-
-
-
@@ -9,11 +9,6 @@ openGraphImage?: readonly string[];} export interface BuildContext { /** * The document currently building. */ document: Document; documentTree: DocumentTree; /**
-
@@ -46,3 +41,10 @@ * Copy file to the output directory.*/ copyFile(file: FileReader): void; } export interface DocumentBuildContext extends BuildContext { /** * The document currently building. */ document: Document; }
-
-
-
@@ -13,7 +13,7 @@import { type OfmWikilinkEmbed } from "../../../../lib/mdast_util_ofm_wikilink/mod.ts"; import type { Document } from "../../../types.ts"; import type { BuildContext } from "../context.ts"; import type { DocumentBuildContext } from "../context.ts"; import { hasAssetToken, hasDocumentToken } from "./utils.ts";
-
@@ -30,7 +30,11 @@ : undefined,}; } function getUrl(url: string, node: Mdast.Node, context: BuildContext): string { function getUrl( url: string, node: Mdast.Node, context: DocumentBuildContext, ): string { if (!hasAssetToken(node)) { return url; }
-
@@ -43,7 +47,7 @@ return context.resolveURL(file.path);} export interface EmbedHandlersParameters { context: BuildContext; context: DocumentBuildContext; buildDocumentContent( document: Document,
-
-
-
@@ -16,7 +16,7 @@ import { type OfmWikilink } from "../../../../lib/mdast_util_ofm_wikilink/mod.ts";import { buildClasses, css, join } from "../css.ts"; import * as lucide from "../icons/lucide.tsx"; import type { BuildContext } from "../context.ts"; import type { DocumentBuildContext } from "../context.ts"; import { hasDocumentToken } from "./utils.ts";
-
@@ -60,7 +60,7 @@ * external location.*/ openExternalLinkInBlank?: boolean; context: BuildContext; context: DocumentBuildContext; } function link(
-
@@ -86,7 +86,11 @@ lucide.externalLink({ className: c.externalIcon, "aria-hidden": "true" }),]); } function getUrl(url: string, node: Mdast.Node, context: BuildContext): string { function getUrl( url: string, node: Mdast.Node, context: DocumentBuildContext, ): string { if (!hasDocumentToken(node)) { return url; }
-
-
-
@@ -11,7 +11,7 @@ import { ofmHtml } from "../../../../lib/hast_util_ofm_html/mod.ts";import { ofmToHastHandlers } from "../../../../lib/mdast_util_ofm/mod.ts"; import { buildClasses, css, join as joinCss } from "../css.ts"; import { type BuildContext } from "../context.ts"; import { type DocumentBuildContext } from "../context.ts"; import { calloutHandlers, calloutStyles } from "./callout.tsx"; import { embedHandlers, type EmbedHandlersParameters } from "./embed.tsx";
-
@@ -145,7 +145,7 @@ );export interface FromMdastParameters extends Pick<EmbedHandlersParameters, "buildDocumentContent"> { context: BuildContext; context: DocumentBuildContext; } export function fromMdast(
-
-
-
@@ -41,7 +41,7 @@ } from "../../types.ts";import * as css from "./css.ts"; import { globalStyles } from "./global_styles.ts"; import type { Assets, BuildContext } from "./context.ts"; import type { Assets, DocumentBuildContext } from "./context.ts"; import { fromMdast,
-
@@ -54,6 +54,7 @@ jsonCanvasStyles as jsonCanvasRendererStyles,} from "./json_canvas/mod.tsx"; import { indexRedirect } from "./pages/index_redirect.tsx"; import { markdownPage, markdownPageStyles } from "./pages/markdown.tsx"; import { notFoundPage, notFoundPageStyles } from "./pages/not_found.tsx"; import { jsonCanvasPage, jsonCanvasPageStyles } from "./pages/json_canvas.tsx"; export type { BuildContext } from "./context.ts";
-
@@ -141,6 +142,10 @@ ext: string;data: Uint8Array; }; }; notFoundPage?: { filename: string; }; } /**
-
@@ -162,6 +167,7 @@ #siteName: string;#baseURL?: URL | string; #openGraph: DefaultThemeBuilderConstructorParameters["openGraph"]; #userCSS?: readonly string[]; #notFoundPage: DefaultThemeBuilderConstructorParameters["notFoundPage"]; constructor( {
-
@@ -173,6 +179,7 @@ siteName,baseURL, openGraph, userCSS, notFoundPage, }: DefaultThemeBuilderConstructorParameters, ) { this.#copyright = copyright;
-
@@ -183,6 +190,7 @@ this.#siteName = siteName;this.#baseURL = baseURL; this.#openGraph = openGraph; this.#userCSS = userCSS; this.#notFoundPage = notFoundPage; } async build(
-
@@ -198,6 +206,7 @@ fromMdastStyles,markdownPageStyles, jsonCanvasPageStyles, jsonCanvasRendererStyles, notFoundPageStyles, ); const userCss = await this.#loadUserCSSAndCopyAssets(
-
@@ -293,6 +302,48 @@ ["index.html"],new TextEncoder().encode(redirectHtml), ); if (this.#notFoundPage) { if (this.#baseURL) { const resolveURL = this.#resolveURL; const writeTasks: Promise<unknown>[] = []; const html = toHtml(notFoundPage({ context: { documentTree, language: documentTree.defaultLanguage, assets, websiteTitle: this.#siteName, copyright: this.#copyright, resolveURL(to) { return resolveURL(to, []); }, copyFile(file) { writeTasks.push( file.read().then((bytes) => { fileSystemWriter.write(file.path, bytes); }), ); }, }, })); await Promise.all(writeTasks); await fileSystemWriter.write( [this.#notFoundPage.filename], new TextEncoder().encode(html), ); } else { logger().warn( "Skipping Not Found page generation due to base URL not specified", { filename: this.#notFoundPage.filename, }, ); } } await Promise.all(documentTree.nodes.map((item) => this.#build({ item,
-
@@ -311,7 +362,7 @@ });} #buildEmbed( context: BuildContext, context: DocumentBuildContext, target: Document, fragments: readonly string[] = [], ): Hast.Nodes {
-
@@ -443,7 +494,7 @@if ("file" in item) { const writeTasks: Promise<unknown>[] = []; const context: BuildContext = { const context: DocumentBuildContext = { document: item, documentTree: tree, language: item.metadata.language || parentLanguage,
-
-
-
@@ -9,7 +9,7 @@ import type * as Hast from "../../../../deps/esm.sh/hast/types.ts";import type { JSONCanvas } from "../../../content_parser/json_canvas/types.ts"; import type { BuildContext } from "../context.ts"; import type { DocumentBuildContext } from "../context.ts"; import { buildClasses, css, join } from "../css.ts"; import { javascript } from "../script.ts";
-
@@ -521,7 +521,7 @@ enchanceJSONCanvas();`; export interface JsonCanvasPageProps { context: Readonly<BuildContext>; context: Readonly<DocumentBuildContext>; content: JSONCanvas<Hast.Nodes>; }
-
-
-
@@ -9,7 +9,7 @@ import type * as Hast from "../../../../deps/esm.sh/hast/types.ts";import type { TocItem } from "../../../../lib/hast_util_toc/mod.ts"; import type { BuildContext } from "../context.ts"; import type { DocumentBuildContext } from "../context.ts"; import { buildClasses, css, join } from "../css.ts"; import { javascript } from "../script.ts";
-
@@ -132,7 +132,7 @@ enchanceToc();`; export interface MarkdownPageProps { context: Readonly<BuildContext>; context: Readonly<DocumentBuildContext>; content: Hast.Nodes;
-
-
-
@@ -0,0 +1,105 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 /** @jsx h */ import { h } from "../../../../deps/esm.sh/hastscript/mod.ts"; import type { BuildContext } from "../context.ts"; import { buildClasses, css, join } from "../css.ts"; import { documentTree, documentTreeScript, documentTreeStyles, } from "../widgets/document_tree.tsx"; import { footer, footerStyles } from "../widgets/footer.tsx"; import { layout, layoutScript, layoutStyles } from "../widgets/layout.tsx"; import { template } from "./template.tsx"; const c = buildClasses("p-nf", [ "container", "layout", "title", "topLink", ]); const ownStyles = css` .${c.container} { margin-top: calc(var(--baseline) * 3rem); display: flex; justify-content: center; align-items: center; } .${c.layout} { display: flex; flex-direction: column; justify-content: center; align-items: start; gap: calc(var(--baseline) * 1rem); } .${c.title} { font-weight: bold; font-size: 2rem; line-height: calc(var(--baseline) * 2rem); margin: 0; } .${c.topLink} { font-weight: 500; color: var(--color-fg-sub); text-decoration: underline; transition: color 0.15s ease; } .${c.topLink}:hover { color: var(--color-primary); } `; export const notFoundPageStyles = join( layoutStyles, documentTreeStyles, footerStyles, ownStyles, ); export interface NotFoundPageProps { context: Readonly<BuildContext>; } export function notFoundPage({ context }: NotFoundPageProps) { return h(null, [ { type: "doctype" }, template({ context, scripts: [layoutScript, documentTreeScript], body: layout({ context, nav: documentTree({ context }), footer: footer({ copyright: context.copyright }), main: ( <div class={c.container}> <div class={c.layout}> <h1 class={c.title}>404 - Not Found</h1> <a class={c.topLink} href={context.resolveURL([ ...context.documentTree.defaultDocument.path, "", ])} > {context.documentTree.defaultDocument.metadata.title} </a> </div> </div> ), }), }), ]); }
-
-
-
@@ -6,26 +6,29 @@ /** @jsx h */import { type Child, h } from "../../../../deps/esm.sh/hastscript/mod.ts"; import type { BuildContext } from "../context.ts"; import type { BuildContext, DocumentBuildContext } from "../context.ts"; export interface TemplateProps { body: Child; context: Readonly<BuildContext>; context: Readonly<BuildContext | DocumentBuildContext>; scripts?: readonly string[]; } export function template({ body, context, scripts = [] }: TemplateProps) { const { language, document, websiteTitle, assets, resolveURL } = context; const { language, websiteTitle, assets, resolveURL } = context; const document = "document" in context ? context.document : null; return ( <html lang={language}> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{document.metadata.title} - {websiteTitle}</title> {document.metadata.description && ( {document ? <title>{document.metadata.title} - {websiteTitle}</title> : <title>{websiteTitle}</title>} {document?.metadata.description && ( <meta name="description" content={document.metadata.description} /> )} <link
-
@@ -46,7 +49,7 @@ type="image/png"href={resolveURL(assets.faviconPng)} /> )} {assets.openGraphImage && ( {document && assets.openGraphImage && ( h(null, [ <meta name="og:title" content={document.metadata.title} />, <meta name="og:type" content="article" />,
-
-
-
@@ -9,7 +9,7 @@import type { Document, DocumentDirectory } from "../../../types.ts"; import { buildClasses, css, join } from "../css.ts"; import type { BuildContext } from "../context.ts"; import type { BuildContext, DocumentBuildContext } from "../context.ts"; import { javascript } from "../script.ts"; import * as icons from "../icons/lucide.tsx";
-
@@ -143,7 +143,7 @@ enchanceDocumentTree();`; export interface DocumentTreeProps { context: Readonly<BuildContext>; context: Readonly<BuildContext | DocumentBuildContext>; } export function documentTree({ context }: DocumentTreeProps) {
-
@@ -152,7 +152,9 @@ <ul className={c.root} lang={context.documentTree.defaultLanguage}>{context.documentTree.nodes.map((entry) => ( node({ value: entry, currentPath: context.document.path, currentPath: "document" in context ? context.document.path : undefined, context, }) ))}
-
@@ -163,9 +165,9 @@interface NodeProps { value: Document | DocumentDirectory; currentPath: readonly string[]; currentPath?: readonly string[]; context: Readonly<BuildContext>; context: Readonly<BuildContext | DocumentBuildContext>; } function node({ currentPath, value, context }: NodeProps) {
-
@@ -176,7 +178,8 @@ // For trailing slash"", ]); const isCurrent = value.path.length === context.document.path.length && const isCurrent = "document" in context && value.path.length === context.document.path.length && value.path.every((x, i) => context.document.path[i] === x); return (
-
@@ -192,7 +195,7 @@ </li>); } const defaultOpened = currentPath[0] === value.metadata.name; const defaultOpened = currentPath && currentPath[0] === value.metadata.name; return ( <li lang={value.metadata.language ?? undefined}>
-
@@ -210,7 +213,7 @@ <ul className={c.list}>{value.entries.map((entry) => ( node({ value: entry, currentPath: currentPath.slice(1), currentPath: currentPath?.slice(1), context, }) ))}
-
-
-
@@ -6,14 +6,14 @@ /** @jsx h */import { h } from "../../../../deps/esm.sh/hastscript/mod.ts"; import type { BuildContext } from "../context.ts"; import type { DocumentBuildContext } from "../context.ts"; import { datetime, datetimeScript } from "./datetime.tsx"; export const pageMetadataScript = datetimeScript; export interface PageMetadataProps { context: BuildContext; context: DocumentBuildContext; } export function pageMetadata(
-