Changes
13 changed files (+198/-33)
-
-
@@ -0,0 +1,23 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import type { DocumentMetadata } from "../metadata_parser/interface.ts"; import type { FileReader } from "../filesystem_reader/interface.ts"; export interface DocumentContent< Kind extends string = string, Content = unknown, > { kind: Kind; content: Content; } export interface ParseParameters { fileReader: FileReader; documentMetadata: DocumentMetadata; } export interface ContentParser { parse(params: ParseParameters): Promise<DocumentContent>; }
-
-
content_parser/noop.ts (new)
-
@@ -0,0 +1,17 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import type { ContentParser, DocumentContent } from "./interface.ts"; export type NullDocument = DocumentContent<"null", null>; // No-op parser for testing. export const noopParser: ContentParser = { async parse(): Promise<NullDocument> { return { kind: "null", content: null, }; }, };
-
-
-
@@ -0,0 +1,55 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import { assertObjectMatch } from "../deps/deno.land/std/assert/mod.ts"; import { VaultParser } from "../metadata_parser/vault_parser.ts"; import type { DocumentMetadata } from "../metadata_parser/interface.ts"; import { MemoryFsReader } from "../filesystem_reader/memory_fs.ts"; import type { FileReader } from "../filesystem_reader/interface.ts"; import { ObsidianMarkdownParser } from "./obsidian_markdown.ts"; const metadataParser = new VaultParser(); Deno.test("Should parse CommonMark syntax", async () => { const fs = new MemoryFsReader([ { path: "Test.md", content: ` # H1 ## H2 ### H3 `, }, ]); const fileReader = (await (fs.getRootDirectory().then((dir) => dir.read()).then((entries) => entries[0] ))) as FileReader; const documentMetadata = (await metadataParser.parse(fileReader)) as DocumentMetadata; const parser = new ObsidianMarkdownParser(); const content = await parser.parse({ documentMetadata, fileReader }); assertObjectMatch(content.content, { type: "root", children: [ { type: "heading", depth: 1, }, { type: "heading", depth: 2, }, { type: "heading", depth: 3, }, ], }); });
-
-
-
@@ -0,0 +1,28 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import type * as Mdast from "../deps/esm.sh/mdast/types.ts"; import { fromMarkdown } from "../deps/esm.sh/mdast-util-from-markdown/mod.ts"; import type { ContentParser, DocumentContent, ParseParameters, } from "./interface.ts"; export type ObsidianMarkdownDocument = DocumentContent< "obsidian_markdown", Mdast.Nodes >; export class ObsidianMarkdownParser implements ContentParser { async parse( { fileReader }: ParseParameters, ): Promise<ObsidianMarkdownDocument> { return { kind: "obsidian_markdown", content: fromMarkdown(await fileReader.read()), }; } }
-
-
-
@@ -226,8 +226,10 @@ "https://esm.sh/v128/preact@10.20.1": "5c0877793f441a58c8109d0f6b8e07cce501f3dc4389ee4405d87a50de44db3c","https://esm.sh/v135/@shelf/fast-uslug@1.0.0": "46107bc12821cd3fd8605a1a837fc9288a254d8e4e406126059b5a6031695ad0", "https://esm.sh/v135/@shelf/fast-uslug@1.0.0/denonext/fast-uslug.mjs": "d140f64633d7ede6e407d8b9ea3236b6c614da96c5fce03297f353e735106cf0", "https://esm.sh/v135/@ungap/structured-clone@1.2.0/denonext/structured-clone.mjs": "e683ab48ef7a3afd3bce9d1589d14177ddbdbf76fa1483524dddbeb6b142469f", "https://esm.sh/v135/boolbase@1.0.0/denonext/boolbase.mjs": "4e3bd67e9b1c5c55094eae98345d0107c6a44ef57bd3d4b9579698fa44722280", "https://esm.sh/v135/character-entities@2.0.2/denonext/character-entities.mjs": "9e8657f056310ac3ca8058eaf96cef695ee13a4bf6c302674796a882464f305c", "https://esm.sh/v135/comma-separated-tokens@2.0.3/denonext/comma-separated-tokens.mjs": "ad5df8a36487e0a63d15bbbb6bab8b153e08583d0d5eb6d0058cd0fc619252e0", "https://esm.sh/v135/css-selector-parser@3.0.2/denonext/css-selector-parser.mjs": "4e3d11f08e30de48bdf107a92e992cd25ae845b1aee47bfe1d4fdd467241ab49", "https://esm.sh/v135/decode-named-character-reference@1.0.2/denonext/decode-named-character-reference.mjs": "1a5a8f9cbe302be478e964ab701be8644dbdfd4d8ce9f14de186cf84ee2a4bc1", "https://esm.sh/v135/devlop@1.1.0/denonext/devlop.mjs": "05fffa5a5168daec45963b784734dbc468758e130a340af874adfe0d457e394a", "https://esm.sh/v135/estree-util-is-identifier-name@3.0.0/denonext/estree-util-is-identifier-name.mjs": "2d1080530be602e98e40807bbb760a08a017b315be59fe869b57afca5d667dca",
-
@@ -272,6 +274,7 @@ "https://esm.sh/v135/micromark-util-resolve-all@2.0.0/denonext/micromark-util-resolve-all.mjs": "c11d87d63d808a26231323012295490931159830a66c00854693ec9279fa09fd","https://esm.sh/v135/micromark-util-sanitize-uri@2.0.0/denonext/micromark-util-sanitize-uri.mjs": "cde22cced5a18a41dcdacbad2cc9e6fc930450118bff7dd764e60eea4aa6c5b5", "https://esm.sh/v135/micromark-util-subtokenize@2.0.0/denonext/micromark-util-subtokenize.mjs": "670326123b1f9d91218cee035248437b97e5b6828f1d1eb01d40bc46f89bdc64", "https://esm.sh/v135/micromark@4.0.0/denonext/micromark.mjs": "dc73ed793c5bbc49ad7201a326fc724d08bc94372a291c184167b22c4edc320d", "https://esm.sh/v135/nth-check@2.1.1/denonext/nth-check.mjs": "638b4f5a22236cd05c7d1d43e5c6ea719695c4a8bc7beccdf8d97a434bea96dc", "https://esm.sh/v135/preact-render-to-string@6.4.1": "a9ca466e5daf03595e041d96829c74f8e493f928e35a64b56365d952fda04ec3", "https://esm.sh/v135/preact-render-to-string@6.4.1/denonext/preact-render-to-string.mjs": "0226110b9ae616d3143c7ea00b822faa1cdd96588188fc3f28e2bf8ead94ba4f", "https://esm.sh/v135/preact@10.20.1": "5c0877793f441a58c8109d0f6b8e07cce501f3dc4389ee4405d87a50de44db3c",
-
@@ -287,11 +290,14 @@ "https://esm.sh/v135/style-to-object@1.0.5/denonext/style-to-object.mjs": "fa46c0fbe36f636aec3f7a3f5af231c37b9265a444c343cd3067ec2d214c18d0","https://esm.sh/v135/trim-lines@3.0.1/denonext/trim-lines.mjs": "f01a20253341eb2554f307ab05bc9cd93d6f33bcbb24fde2fc9fcd857564283e", "https://esm.sh/v135/unist-util-is@6.0.0/denonext/unist-util-is.mjs": "d92da46b3a1084450f150cc06c02f3bf85b93ab4c4b43a960d72a4f5678e95cc", "https://esm.sh/v135/unist-util-position@5.0.0/denonext/unist-util-position.mjs": "aaef05774a54f2b84400e98325219e2ba6cf966e318173bcd895647c56bb8871", "https://esm.sh/v135/unist-util-select@5.1.0": "de17fcc16abc04d486c293b61e430c5e5a154ce67f25600ab15a4b9b41b995fa", "https://esm.sh/v135/unist-util-select@5.1.0/denonext/unist-util-select.mjs": "2bc0f79fd11696cecbe35b3fc8427ca2035052d5bbd0bc2bd0eb85eb5f35c242", "https://esm.sh/v135/unist-util-stringify-position@4.0.0/denonext/unist-util-stringify-position.mjs": "dabd32cb2b590bbb077fc6f6591a2e065cffd6c55646ba383455926a27ea64d7", "https://esm.sh/v135/unist-util-visit-parents@6.0.1/denonext/do-not-use-color.js": "a1c0a6b93471dd4ed996804dd8a2b9f753c83c4a2da98373253e6b312c8492e2", "https://esm.sh/v135/unist-util-visit-parents@6.0.1/denonext/unist-util-visit-parents.mjs": "5f7ececae47bea6d87b7e323153a7415ad8f299dc42e61aefda6d28eaf264c64", "https://esm.sh/v135/unist-util-visit@5.0.0": "39c6a28445ca31c6ad1e97c663c9d6f86d8820a2b5893c77e2d363cb630c8dc5", "https://esm.sh/v135/unist-util-visit@5.0.0/denonext/unist-util-visit.mjs": "6c1b5b3d517cc6dbc88406b2dbab1735d503a797e994dbd4a89f3764098318f7", "https://esm.sh/v135/vfile-message@4.0.2/denonext/vfile-message.mjs": "efc85b18bedda337fb1c20cdc452fac3addac32ee55948cebf2845396ae641ac" "https://esm.sh/v135/vfile-message@4.0.2/denonext/vfile-message.mjs": "efc85b18bedda337fb1c20cdc452fac3addac32ee55948cebf2845396ae641ac", "https://esm.sh/v135/zwitch@2.0.4/denonext/zwitch.mjs": "c0e8c246a1f38b425335ea78cc366a7801d3ef89701229a35f85a51310e6e49f" } }
-
-
-
@@ -5,6 +5,7 @@import { DenoFsReader } from "../filesystem_reader/deno_fs.ts"; import { DenoFsWriter } from "../filesystem_writer/deno_fs.ts"; import { DefaultTreeBuilder } from "../tree_builder/default_tree_builder.ts"; import { ObsidianMarkdownParser } from "../content_parser/obsidian_markdown.ts"; import { VaultParser } from "../metadata_parser/vault_parser.ts"; import { DefaultThemeBuilder } from "../page_builder/default_theme/builder.tsx";
-
@@ -33,6 +34,7 @@ return node.name.startsWith(".") ||(node.path.length === 1 && node.name.endsWith(".ts")); }, }); const contentParser = new ObsidianMarkdownParser(); const metadataParser = new VaultParser({ override(node) { if (
-
@@ -66,6 +68,7 @@const documentTree = await treeBuilder.build({ fileSystemReader, metadataParser, contentParser, }); await pageBuilder.build({ documentTree,
-
-
-
@@ -3,26 +3,27 @@ "nodes":[{"id":"16ecec9768556d1e","type":"group","x":-540,"y":-1240,"width":1580,"height":1280,"color":"2","label":"Macana"}, {"id":"cef7273c31316035","type":"group","x":10,"y":-1560,"width":740,"height":220,"label":"Legends"}, {"id":"1f5682267b09b226","type":"text","text":"# Tree Builder\nResponsible for generating a *document tree* by accessing *FileSystem reader*.","x":0,"y":-860,"width":340,"height":140,"color":"5"}, {"id":"7e6026f0fc91341d","type":"text","text":"# Metadata Parser\nResponsible for parsing a file or a directory and returning a *document metadata*.\n","x":-70,"y":-1150,"width":480,"height":160,"color":"5"}, {"id":"666a11c598e85979","type":"text","text":"# User script\nDeno script user wrote.","x":-920,"y":-554,"width":320,"height":128,"color":"5"}, {"id":"ecd9e84968c62b30","type":"text","text":"# Core\nAct as an endpoint for the Macana API. Schedule and coordinates various modules.","x":-420,"y":-580,"width":384,"height":180,"color":"5"}, {"id":"b47e4fabfd3da80f","type":"text","text":"# CLI","x":-505,"y":-126,"width":250,"height":100,"color":"1"}, {"id":"ed3a9fdae2c1eb0e","type":"text","text":"# User Agent\nMostly web browser.","x":-255,"y":120,"width":300,"height":160}, {"id":"d27605d3bbfcf821","type":"text","text":"# Dev Server","x":-210,"y":-119,"width":210,"height":86,"color":"1"}, {"id":"eea88958e6425901","type":"text","text":"# Obsidian Vault\nPage source data is stored as Markdown files in plain arbitrary directory.","x":1100,"y":-812,"width":300,"height":184}, {"id":"a4415114ae6abd10","type":"text","text":"# Module\nBox with this color indicates the box is a module.","x":30,"y":-1520,"width":280,"height":140,"color":"5"}, {"id":"985228be1f82ddfd","type":"text","text":"# Program\nBox with this color indicates the box is an executable.","x":388,"y":-1520,"width":280,"height":140,"color":"1"}, {"id":"bcf0d415d8a93b32","type":"text","text":"# FileSystem Reader\nResponsible for listing, reading, and watching directory or file. This module can only operate inside a source directory.","x":640,"y":-860,"width":340,"height":188,"color":"5"}, {"id":"ecd9e84968c62b30","type":"text","text":"# Core\nAct as an endpoint for the Macana API. Schedule and coordinates various modules.","x":-420,"y":-580,"width":384,"height":180,"color":"5"}, {"id":"d9484aaae39c7cfd","type":"text","text":"# FileSystem Writer\nResponsible for creating and writing directory or file. This can only operate inside an output directory.","x":96,"y":-170,"width":324,"height":188,"color":"5"}, {"id":"b47e4fabfd3da80f","type":"text","text":"# CLI","x":-505,"y":-126,"width":250,"height":100,"color":"1"}, {"id":"b4d105b9f43d32e4","type":"text","text":"# Generated site\nHTML/CSS/JS files, along with RSS feed and other site metadata things.","x":100,"y":120,"width":320,"height":160}, {"id":"ed3a9fdae2c1eb0e","type":"text","text":"# User Agent\nMostly web browser.","x":-255,"y":120,"width":300,"height":160}, {"id":"d27605d3bbfcf821","type":"text","text":"# Dev Server","x":-210,"y":-119,"width":210,"height":86,"color":"1"}, {"id":"c637b07c530db189","type":"text","text":"# Page Builder\nResponsible for generating a HTML page from a *document tree* and a *document*.","x":88,"y":-480,"width":340,"height":160,"color":"5"}, {"id":"eea88958e6425901","type":"text","text":"# Obsidian Vault\nPage source data is stored as Markdown files in plain arbitrary directory.","x":1100,"y":-812,"width":300,"height":184} {"id":"7e6026f0fc91341d","type":"text","text":"# Metadata Parser\nResponsible for parsing a file or a directory and returning a *document metadata*.\n","x":258,"y":-1180,"width":480,"height":160,"color":"5"}, {"id":"d9c3ec6b020b2ca1","type":"text","text":"# Content Parser\nResponsible for parsing a file and returning a *document content*.\n","x":-345,"y":-1180,"width":480,"height":160,"color":"5"} ], "edges":[ {"id":"9d28e2e189fcc6d1","fromNode":"ed3a9fdae2c1eb0e","fromSide":"top","toNode":"d27605d3bbfcf821","toSide":"bottom","fromEnd":"arrow","label":"HTTP"}, {"id":"2502c36efb8650a2","fromNode":"1f5682267b09b226","fromSide":"top","toNode":"7e6026f0fc91341d","toSide":"bottom","color":"1","label":"File / Directory"}, {"id":"d283db6e20fddc67","fromNode":"7e6026f0fc91341d","fromSide":"right","toNode":"1f5682267b09b226","toSide":"right","color":"6","label":"Document metadata"}, {"id":"05e0aed4c96b7b73","fromNode":"ecd9e84968c62b30","fromSide":"top","toNode":"1f5682267b09b226","toSide":"left","color":"1","label":"Build request"}, {"id":"67d567fd897faaa5","fromNode":"1f5682267b09b226","fromSide":"bottom","toNode":"c637b07c530db189","toSide":"top","color":"6","label":"Document tree"}, {"id":"67d567fd897faaa5","fromNode":"1f5682267b09b226","fromSide":"bottom","toNode":"c637b07c530db189","toSide":"top","color":"3","label":"Document tree"}, {"id":"8284d0da786f676b","fromNode":"c637b07c530db189","fromSide":"bottom","toNode":"d9484aaae39c7cfd","toSide":"top","color":"4","label":"Generated files"}, {"id":"7b4d9467cbcec90e","fromNode":"bcf0d415d8a93b32","fromSide":"left","toNode":"1f5682267b09b226","toSide":"bottom","color":"1","label":"File / Directory"}, {"id":"a0543dccaeca0491","fromNode":"bcf0d415d8a93b32","fromSide":"bottom","toNode":"c637b07c530db189","toSide":"right","color":"3","label":"File"},
-
@@ -31,6 +32,8 @@ {"id":"b5b8d60304fb4839","fromNode":"d27605d3bbfcf821","fromSide":"top","toNode":"ecd9e84968c62b30","toSide":"bottom"},{"id":"372c5518dfc96cc2","fromNode":"b47e4fabfd3da80f","fromSide":"top","toNode":"ecd9e84968c62b30","toSide":"bottom"}, {"id":"d1dfb8d829247781","fromNode":"d27605d3bbfcf821","fromSide":"right","toNode":"d9484aaae39c7cfd","toSide":"left"}, {"id":"8af32a521e2be033","fromNode":"eea88958e6425901","fromSide":"left","toNode":"bcf0d415d8a93b32","toSide":"right"}, {"id":"a2dff019b740cad3","fromNode":"666a11c598e85979","fromSide":"right","toNode":"ecd9e84968c62b30","toSide":"left"} {"id":"a2dff019b740cad3","fromNode":"666a11c598e85979","fromSide":"right","toNode":"ecd9e84968c62b30","toSide":"left"}, {"id":"1c69fcf3635a69ef","fromNode":"1f5682267b09b226","fromSide":"top","toNode":"d9c3ec6b020b2ca1","toSide":"bottom","color":"1","label":"File"}, {"id":"ad5ba77ef1e159bf","fromNode":"d9c3ec6b020b2ca1","fromSide":"left","toNode":"1f5682267b09b226","toSide":"left","color":"6","label":"Document content"} ] }
-
-
-
@@ -24,4 +24,8 @@ A document directory consists of *document metadata* of its own and zero or more *document metadata* and/or *document directory*.## Document tree Tree structured data contains *document metadata* and *document directories*. Tree structured data contains *document metadata* and *document directories*. ## Document content A parsed content of note or canvas.
-
-
-
@@ -5,7 +5,6 @@/** @jsx h */ import { h, renderSSR } from "../../deps/deno.land/x/nano_jsx/mod.ts"; import { fromMarkdown } from "../../deps/esm.sh/mdast-util-from-markdown/mod.ts"; import type { Document,
-
@@ -13,12 +12,18 @@ DocumentDirectory,DocumentTree, } from "../../tree_builder/interface.ts"; import type { BuildParameters, PageBuilder } from "../interface.ts"; import type { DocumentContent } from "../../content_parser/interface.ts"; import type { ObsidianMarkdownDocument } from "../../content_parser/obsidian_markdown.ts"; import * as css from "./css.ts"; import * as Html from "./components/html.tsx"; import { PathResolverProvider } from "./contexts/path_resolver.tsx"; function isObsidianMarkdown(x: DocumentContent): x is ObsidianMarkdownDocument { return x.kind === "obsidian_markdown"; } interface InnerBuildParameters { item: DocumentDirectory | Document;
-
@@ -69,16 +74,15 @@ ): Promise<void> {const { fileSystemWriter } = buildParameters; if ("file" in item) { const content = await item.file.read(); if (item.file.name.endsWith(".md")) { if (isObsidianMarkdown(item.content)) { const content = item.content.content; const html = "<!DOCTYPE html>" + renderSSR( () => ( // Adds 1 to depth due to `<name>/index.html` conversion. <PathResolverProvider depth={pathPrefix.length + 1}> <Html.View tree={tree} content={fromMarkdown(content)} content={content} document={item} language={item.metadata.language || parentLanguage} copyright={this.#copyright}
-
@@ -97,15 +101,7 @@ ], enc.encode(html));return; } if (item.file.name.endsWith(".canvas")) { // TODO: Proper logging console.warn( "Default theme page builder does not support Canvas yet.", ); return; } return; throw new Error(`Unsupported content type: ${item.content.kind}`); } await Promise.all(item.entries.map((entry) =>
-
-
-
@@ -16,7 +16,6 @@ import { toJsxRuntime } from "../../../deps/esm.sh/hast-util-to-jsx-runtime/mod.ts";import type { Document, DocumentDirectory, DocumentTree, } from "../../../tree_builder/interface.ts";
-
@@ -60,7 +59,7 @@/** * The document's content HTML. */ content: Mdast.Root; content: Mdast.Nodes; language: string;
-
-
-
@@ -9,7 +9,10 @@ } from "../deps/deno.land/std/assert/mod.ts";import { MemoryFsReader } from "../filesystem_reader/memory_fs.ts"; import { VaultParser } from "../metadata_parser/vault_parser.ts"; import { noopParser } from "../content_parser/noop.ts"; import { DefaultTreeBuilder } from "./default_tree_builder.ts"; const contentParser = noopParser; Deno.test("Should read from top-level directory, as-is", async () => { const fileSystemReader = new MemoryFsReader([
-
@@ -19,7 +22,11 @@ ]);const metadataParser = new VaultParser(); const builder = new DefaultTreeBuilder({ defaultLanguage: "en" }); const tree = await builder.build({ fileSystemReader, metadataParser }); const tree = await builder.build({ fileSystemReader, metadataParser, contentParser, }); assertObjectMatch(tree.nodes[0], { metadata: {
-
@@ -69,7 +76,11 @@ return node.name === "foo";}, }); const tree = await builder.build({ fileSystemReader, metadataParser }); const tree = await builder.build({ fileSystemReader, metadataParser, contentParser, }); assertEquals(tree.nodes.length, 2);
-
-
-
@@ -6,7 +6,6 @@ import type {DirectoryReader, FileReader, } from "../filesystem_reader/interface.ts"; import type { MetadataParser } from "../metadata_parser/interface.ts"; import type { BuildParameters, Document,
-
@@ -39,14 +38,16 @@ this.#ignore = ignore;} async build( { fileSystemReader, metadataParser }: BuildParameters, { fileSystemReader, metadataParser, contentParser }: BuildParameters, ): Promise<DocumentTree> { const root = await fileSystemReader.getRootDirectory(); const children = await root.read(); const entries = await Promise.all( children.map((child) => this.#build(child, metadataParser)), children.map((child) => this.#build(child, { metadataParser, contentParser }) ), ); return {
-
@@ -60,7 +61,10 @@ }async #build( node: FileReader | DirectoryReader, metadataParser: MetadataParser, { metadataParser, contentParser }: Omit< BuildParameters, "fileSystemReader" >, parentPath: readonly string[] = [], ): Promise<DocumentDirectory | Document | null> { if (this.#ignore && this.#ignore(node)) {
-
@@ -78,10 +82,16 @@ return null;} if (node.type === "file") { const content = await contentParser.parse({ fileReader: node, documentMetadata: metadata, }); return { type: "document", metadata, file: node, content, path: [...parentPath, metadata.name], }; }
-
@@ -90,7 +100,10 @@ const children = await node.read();const entries = await Promise.all( children.map((child) => this.#build(child, metadataParser, [...parentPath, metadata.name]) this.#build(child, { metadataParser, contentParser }, [ ...parentPath, metadata.name, ]) ), );
-
-
-
@@ -11,11 +11,17 @@ import type {DocumentMetadata, MetadataParser, } from "../metadata_parser/interface.ts"; import type { ContentParser, DocumentContent, } from "../content_parser/interface.ts"; export interface Document { readonly type: "document"; readonly metadata: DocumentMetadata; readonly file: FileReader; readonly content: DocumentContent; /** * Document path: list of names, not file paths.
-
@@ -45,6 +51,7 @@export interface BuildParameters { readonly fileSystemReader: FileSystemReader; readonly metadataParser: MetadataParser; readonly contentParser: ContentParser; } export interface TreeBuilder {
-