Changes
15 changed files (+260/-37)
-
-
@@ -136,6 +136,7 @@ export async function run("base-url", "og-image", "user-css", "not-found-filename", ], boolean: [ "help",
-
@@ -399,6 +400,18 @@ export async function run(? 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 @@ export async function run(image: ogImage, }, userCSS, notFoundPage: notFoundPageDisabled ? undefined : { filename: notFoundPageFilename, }, }); const documentTree = await treeBuilder.build({
-
@@ -535,6 +551,16 @@ ${title("Options")}:}. 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. Corresponding config key is ${p("output.baseURL")} (${t("string")}).
-
-
-
@@ -42,6 +42,21 @@ Path needs to be relative and inside the vault, and will be resolved from the coAssets 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 @@ updatedAt: 2024-05-29T22:50:00+09:00- [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 @@ export interface MacanaConfig {baseURL?: string; userCSS?: string; notFoundPage?: { enabled?: boolean; filename?: string; }; }; metadata?: {
-
@@ -80,6 +86,10 @@ function configParser(configFilePath: string): parser.Parser<MacanaConfig> {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 @@ export interface Assets {} export interface BuildContext { /** * The document currently building. */ document: Document; documentTree: DocumentTree; /**
-
@@ -46,3 +41,10 @@ export interface BuildContext {*/ copyFile(file: FileReader): void; } export interface DocumentBuildContext extends BuildContext { /** * The document currently building. */ document: Document; }
-
-
-
@@ -13,7 +13,7 @@ import { type State } from "../../../../deps/esm.sh/mdast-util-to-hast/mod.ts";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 @@ function sizeProperties(node: Mdast.Node): { width?: number; height?: number } {}; } 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 @@ function getUrl(url: string, node: Mdast.Node, context: BuildContext): string {} export interface EmbedHandlersParameters { context: BuildContext; context: DocumentBuildContext; buildDocumentContent( document: Document,
-
-
-
@@ -16,7 +16,7 @@ import { type OfmWikilink } from "../../../../lib/mdast_util_ofm_wikilink/mod.tsimport { 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 @@ interface LinkHandlersOptions {*/ openExternalLinkInBlank?: boolean; context: BuildContext; context: DocumentBuildContext; } function link(
-
@@ -86,7 +86,11 @@ function link(]); } 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 const fromMdastStyles = joinCss(export interface FromMdastParameters extends Pick<EmbedHandlersParameters, "buildDocumentContent"> { context: BuildContext; context: DocumentBuildContext; } export function fromMdast(
-
-
-
@@ -41,7 +41,7 @@ import type {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 @@ import {} 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 @@ export interface DefaultThemeBuilderConstructorParameters {data: Uint8Array; }; }; notFoundPage?: { filename: string; }; } /**
-
@@ -162,6 +167,7 @@ export class DefaultThemeBuilder implements PageBuilder {#baseURL?: URL | string; #openGraph: DefaultThemeBuilderConstructorParameters["openGraph"]; #userCSS?: readonly string[]; #notFoundPage: DefaultThemeBuilderConstructorParameters["notFoundPage"]; constructor( {
-
@@ -173,6 +179,7 @@ export class DefaultThemeBuilder implements PageBuilder {baseURL, openGraph, userCSS, notFoundPage, }: DefaultThemeBuilderConstructorParameters, ) { this.#copyright = copyright;
-
@@ -183,6 +190,7 @@ export class DefaultThemeBuilder implements PageBuilder {this.#baseURL = baseURL; this.#openGraph = openGraph; this.#userCSS = userCSS; this.#notFoundPage = notFoundPage; } async build(
-
@@ -198,6 +206,7 @@ export class DefaultThemeBuilder implements PageBuilder {markdownPageStyles, jsonCanvasPageStyles, jsonCanvasRendererStyles, notFoundPageStyles, ); const userCss = await this.#loadUserCSSAndCopyAssets(
-
@@ -293,6 +302,48 @@ export class DefaultThemeBuilder implements PageBuilder {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 @@ export class DefaultThemeBuilder implements PageBuilder {} #buildEmbed( context: BuildContext, context: DocumentBuildContext, target: Document, fragments: readonly string[] = [], ): Hast.Nodes {
-
@@ -443,7 +494,7 @@ export class DefaultThemeBuilder implements PageBuilder {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 @@ const ownScript = javascript``; 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 @@ const ownScript = javascript``; 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 @@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 @@ export function template({ body, context, scripts = [] }: TemplateProps) {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 { h } from "../../../../deps/esm.sh/hastscript/mod.ts";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 @@ export const documentTreeScript = javascript``; export interface DocumentTreeProps { context: Readonly<BuildContext>; context: Readonly<BuildContext | DocumentBuildContext>; } export function documentTree({ context }: DocumentTreeProps) {
-
@@ -152,7 +152,9 @@ export function documentTree({ context }: DocumentTreeProps) {{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 @@ export function documentTree({ context }: DocumentTreeProps) {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 @@ function node({ currentPath, value, context }: NodeProps) {"", ]); 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 @@ function node({ currentPath, value, context }: NodeProps) {); } 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 @@ function node({ currentPath, value, context }: NodeProps) {{value.entries.map((entry) => ( node({ value: entry, currentPath: currentPath.slice(1), currentPath: currentPath?.slice(1), context, }) ))}
-
-
-
@@ -6,14 +6,14 @@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(
-