Changes
10 changed files (+194/-35)
-
-
@@ -27,6 +27,8 @@ export interface ParseParameters {): DocumentToken | Promise<DocumentToken>; } export interface ContentParser { parse(params: ParseParameters): Promise<ContentParseResult>; export interface ContentParser< Content extends DocumentContent = DocumentContent, > { parse(params: ParseParameters): Promise<ContentParseResult<Content>>; }
-
-
-
@@ -2,10 +2,14 @@// // SPDX-License-Identifier: Apache-2.0 import type * as Mdast from "../deps/esm.sh/mdast/types.ts"; import type { ContentParser, ParseParameters } from "./interface.ts"; import type { DocumentContent } from "../types.ts"; import { isJSONCanvas, type JSONCanvas } from "./json_canvas/types.ts"; import { mapText } from "./json_canvas/utils.ts"; import { parseMarkdown } from "./obsidian_markdown.ts"; export class JSONCanvasParseError extends Error {}
-
@@ -25,13 +29,16 @@ export class InvalidJSONError extends JSONCanvasParseError {} } export type JSONCanvasDocument = DocumentContent< export type JSONCanvasDocument<T> = DocumentContent< "json_canvas", JSONCanvas JSONCanvas<T> >; export class JSONCanvasParser implements ContentParser { async parse({ fileReader }: ParseParameters): Promise<JSONCanvasDocument> { export class JSONCanvasParser implements ContentParser<JSONCanvasDocument<Mdast.Nodes>> { async parse( { fileReader, getDocumentToken, getAssetToken }: ParseParameters, ): Promise<JSONCanvasDocument<Mdast.Nodes>> { const text = new TextDecoder().decode(await fileReader.read()); let json: unknown;
-
@@ -47,7 +54,12 @@ export class JSONCanvasParser implements ContentParser {return { kind: "json_canvas", content: json, content: await mapText(json, async (node) => { return parseMarkdown(node.text, { getAssetToken, getDocumentToken, }); }), }; } }
-
-
-
@@ -137,7 +137,11 @@ export function isGenericNode(x: unknown): x is GenericNode {return true; } export interface TextNode extends GenericNode { /** * You can change `Markdown` type parameter to other types. * For example, AST if it needs to be displayed as HTML. */ export interface TextNode<Markdown = string> extends GenericNode { type: "text"; /**
-
@@ -146,7 +150,7 @@ export interface TextNode extends GenericNode {* using/parsing their Obsidian Flavored Markdown. Parsing and displaying * of this property is UB. */ text: string; text: Markdown; } export function isTextNode(x: GenericNode): x is TextNode {
-
@@ -269,7 +273,11 @@ export function isGroupNode(x: GenericNode): x is GroupNode {return true; } export type Node = TextNode | FileNode | LinkNode | GroupNode; export type Node<Markdown = string> = | TextNode<Markdown> | FileNode | LinkNode | GroupNode; export function isNode(x: unknown): x is Node { if (!isGenericNode(x)) {
-
@@ -398,8 +406,8 @@ export function isEdge(x: unknown): x is Edge {return true; } export interface JSONCanvas { nodes?: Node[]; export interface JSONCanvas<Markdown = string> { nodes?: Node<Markdown>[]; edges?: Edge[]; }
-
-
-
@@ -0,0 +1,59 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import type { JSONCanvas, Node, TextNode } from "./types.ts"; export function mapTextSync<A, B>( tree: JSONCanvas<A>, f: (node: TextNode<A>) => B, ): JSONCanvas<B> { if (!tree.nodes?.map?.length) { // No nodes = no text nodes = Markdown type does not matter. return (tree as unknown as JSONCanvas<B>); } const nodes = tree.nodes.map<Node<B>>((node) => { if (node.type !== "text") { return node; } return { ...node, text: f(node), }; }); return { ...tree, nodes, }; } export async function mapText<A, B>( tree: JSONCanvas<A>, f: (node: TextNode<A>) => B | Promise<B>, ): Promise<JSONCanvas<B>> { if (!tree.nodes?.map?.length) { // No nodes = no text nodes = Markdown type does not matter. return Promise.resolve(tree as unknown as JSONCanvas<B>); } const nodes = await Promise.all( tree.nodes.map<Promise<Node<B>>>(async (node) => { if (node.type !== "text") { return node; } return { ...node, text: await f(node), }; }), ); return { ...tree, nodes, }; }
-
-
-
@@ -96,7 +96,7 @@ export interface ObsidianMarkdownParserOptions {frontmatter?: boolean; } async function parseMarkdown( export async function parseMarkdown( markdown: string | Uint8Array, { getAssetToken, getDocumentToken }: Pick< ParseParameters,
-
@@ -122,7 +122,8 @@ async function parseMarkdown(return mdast; } export class ObsidianMarkdownParser implements ContentParser { export class ObsidianMarkdownParser implements ContentParser<ObsidianMarkdownDocument> { #frontmatter: boolean; constructor({ frontmatter = false }: ObsidianMarkdownParserOptions = {}) {
-
-
-
@@ -0,0 +1,15 @@{ "nodes":[ {"id":"a2a6b20bb3129a7a","x":-125,"y":-30,"width":250,"height":450,"type":"text","text":"# Heading1\n## Heading2\n### Heading3\n\n> [!warning]-\n> Yay\n\n- List Item\n- List Item\n\t- Nested List Item\n\n```ts\nconst foo = \"bar\"\n```"}, {"id":"f64025c69470260f","x":318,"y":32,"width":400,"height":400,"type":"file","file":"en/Roadmap.md"}, {"id":"e42673f255295e01","x":-200,"y":-720,"width":400,"height":400,"type":"file","file":"Assets/dog.jpg"}, {"id":"21bafbf34e0554ed","x":500,"y":-720,"width":400,"height":400,"type":"link","url":"https://wikipedia.org"} ], "edges":[ {"id":"4a196cf19d0b3085","fromNode":"a2a6b20bb3129a7a","fromSide":"right","toNode":"f64025c69470260f","toSide":"left"}, {"id":"8b9aeb669e298cc6","fromNode":"a2a6b20bb3129a7a","fromSide":"top","toNode":"e42673f255295e01","toSide":"bottom"}, {"id":"822080eccc4237a9","fromNode":"e42673f255295e01","fromSide":"right","toNode":"f64025c69470260f","toSide":"top"}, {"id":"4741e419a45256ae","fromNode":"e42673f255295e01","fromSide":"bottom","toNode":"f64025c69470260f","toSide":"top"}, {"id":"19dc23365e8229c5","fromNode":"21bafbf34e0554ed","fromSide":"left","toNode":"a2a6b20bb3129a7a","toSide":"top"} ] }
-
-
-
@@ -48,7 +48,7 @@ updatedAt: 2024-04-20T01:00:00+09:00- [ ] Node rendering - [x] Basic shapes - [x] Colors - [ ] Text node - [x] Text node - [ ] Group node - [ ] Link node - [ ] File node
-
-
-
@@ -5,6 +5,7 @@/** @jsx h */ import { h, renderSSR } from "../../deps/deno.land/x/nano_jsx/mod.ts"; import type * as Mdast from "../../deps/esm.sh/mdast/types.ts"; import { logger } from "../../logger.ts";
-
@@ -15,6 +16,7 @@ import {type ObsidianMarkdownDocument, } from "../../content_parser/obsidian_markdown.ts"; import type { JSONCanvasDocument } from "../../content_parser/json_canvas.ts"; import * as jsonCanvas from "../../content_parser/json_canvas/utils.ts"; import type { Document, DocumentDirectory, DocumentTree } from "../../types.ts"; import * as css from "./css.ts";
-
@@ -29,7 +31,9 @@ function isObsidianMarkdown(return x.content.kind === "obsidian_markdown"; } function isJSONCanvas(x: Document): x is Document<JSONCanvasDocument> { function isJSONCanvas( x: Document, ): x is Document<JSONCanvasDocument<Mdast.Nodes>> { return x.content.kind === "json_canvas"; }
-
@@ -189,6 +193,35 @@ export class DefaultThemeBuilder implements PageBuilder {if (isObsidianMarkdown(item) || isJSONCanvas(item)) { const assetWrites: Promise<unknown>[] = []; if (item.content.kind === "json_canvas") { await jsonCanvas.mapText(item.content.content, async (node) => { await macanaReplaceAssetTokens( node.text, async (token) => { const file = tree.exchangeToken(token); assetWrites.push(fileSystemWriter.write( file.path, await file.read(), )); return toRelativePath(file.path, item.path); }, ); await macanaReplaceDocumentToken( node.text, async (token) => { const document = tree.exchangeToken(token); return { path: toRelativePath([...document.path, ""], item.path), }; }, ); }); } if (item.content.kind === "obsidian_markdown") { await macanaReplaceAssetTokens( item.content.content,
-
-
-
@@ -8,6 +8,7 @@import { Fragment, h } from "../../../deps/deno.land/x/nano_jsx/mod.ts"; import * as jsxRuntime from "../../../deps/deno.land/x/nano_jsx/jsx-runtime/index.ts"; import { toHast } from "../../../deps/esm.sh/mdast-util-to-hast/mod.ts"; import type * as Mdast from "../../../deps/esm.sh/mdast/types.ts"; import { toJsxRuntime } from "../../../deps/esm.sh/hast-util-to-jsx-runtime/mod.ts"; import * as HastToJSXRuntime from "../../../deps/esm.sh/hast-util-to-jsx-runtime/mod.ts";
-
@@ -24,6 +25,7 @@ import {ofmWikilinkToHastHandlers, } from "../../../content_parser/obsidian_markdown.ts"; import type { JSONCanvasDocument } from "../../../content_parser/json_canvas.ts"; import * as jsonCanvas from "../../../content_parser/json_canvas/utils.ts"; import { usePathResolver } from "../contexts/path_resolver.tsx"; import * as css from "../css.ts";
-
@@ -174,14 +176,8 @@ function MetadataDates({ metadata }: MetadataDatesProps) {); } interface ObsidianMarkdownBodyProps extends ViewProps { content: ObsidianMarkdownDocument; } function ObsidianMarkdownBody( { content, document, tree, copyright, assets }: ObsidianMarkdownBodyProps, ) { const hast = toHast(content.content, { function mdastToHast(input: Mdast.Nodes) { return toHast(input, { // @ts-expect-error: unist-related libraries heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. handlers: {
-
@@ -202,6 +198,16 @@ function ObsidianMarkdownBody(...syntaxHighlightingHandlers(), }, }); } interface ObsidianMarkdownBodyProps extends ViewProps { content: ObsidianMarkdownDocument; } function ObsidianMarkdownBody( { content, document, tree, copyright, assets }: ObsidianMarkdownBodyProps, ) { const hast = mdastToHast(content.content); const toc = tocMut(hast).map((item) => mapTocItem(item, (item) => toNode({ type: "root", children: item }))
-
@@ -230,12 +236,16 @@ function ObsidianMarkdownBody(} interface JSONCanvasBodyProps extends ViewProps { content: JSONCanvasDocument; content: JSONCanvasDocument<Mdast.Nodes>; } function JSONCanvasBody( { content, document, copyright, tree, assets }: JSONCanvasBodyProps, ) { const canvas = jsonCanvas.mapTextSync(content.content, (node) => { return toNode(mdastToHast(node.text)); }); return ( <SiteLayout.View nav={
-
@@ -250,13 +260,15 @@ function JSONCanvasBody(> <h1>{document.metadata.title}</h1> <MetadataDates metadata={document.metadata} /> <JSONCanvasRenderer.View data={content.content} /> <JSONCanvasRenderer.View data={canvas} /> </SiteLayout.View> ); } export interface ViewProps { document: Document<ObsidianMarkdownDocument | JSONCanvasDocument>; document: Document< ObsidianMarkdownDocument | JSONCanvasDocument<Mdast.Nodes> >; /** * Root document tree, for navigations and links.
-
-
-
@@ -19,6 +19,7 @@ import type {const enum C { Wrapper = "jcr--wr", Embed = "jcr--em", } export const styles = css`
-
@@ -32,6 +33,17 @@ export const styles = css`border-radius: calc(1rem / 4); padding: 4px; } .${C.Embed} { padding: 4px 8px; overflow: auto; } .${C.Embed} > p { margin-block-start: calc(var(--baseline) * 0.5rem); } .${C.Embed} > :first-child { margin-block-start: 0; } `; interface Rect {
-
@@ -42,7 +54,10 @@ interface Rect {} // TODO: Automatically calculate padding based on stroke-width, edges and shadows. function getBoundingBox(canvas: JSONCanvas, padding: number = 20): Rect { function getBoundingBox( canvas: JSONCanvas<unknown>, padding: number = 20, ): Rect { let minX: number = 0; let minY: number = 0; let maxX: number = 0;
-
@@ -65,7 +80,7 @@ function getBoundingBox(canvas: JSONCanvas, padding: number = 20): Rect {type Point2D = readonly [number, number]; function getConnectionPoint(node: Node, side: NodeSide): Point2D { function getConnectionPoint(node: Node<unknown>, side: NodeSide): Point2D { switch (side) { case "top": return [node.x + node.width * 0.5, node.y];
-
@@ -93,7 +108,7 @@ function magnitude(v: Vec2D): number {* We need this function because `fromSide` and `toSide` property is optional... * but Obsidian does not render Canvas if either of one is missing. wtf */ function getNearestSideToPoint(node: Node, p: Point2D): NodeSide { function getNearestSideToPoint(node: Node<unknown>, p: Point2D): NodeSide { const center: Point2D = [ node.x + node.width * 0.5, node.y + node.height * 0.5,
-
@@ -142,9 +157,9 @@ function getNearestSideToPoint(node: Node, p: Point2D): NodeSide {} function getClosestSides( a: Node, a: Node<unknown>, aSide: NodeSide | undefined, b: Node, b: Node<unknown>, bSide: NodeSide | undefined, ): readonly [NodeSide, NodeSide] { if (aSide && bSide) {
-
@@ -252,7 +267,8 @@ function EdgeArrow({ target, pointTo, size = 15, ...rest }: EdgeArrowProps) {} export interface JSONCanvasRendererProps { data: JSONCanvas; // nano-jsx does not ship working typings, thus "unknown" (no JSX.Element). data: JSONCanvas<unknown>; radius?: number;
-
@@ -275,7 +291,7 @@ export function JSONCanvasRenderer(* Edges refer nodes by ID. This map helps and optimizes its retrieving operation. * Without using `Map`, lookup takes `O(N)`. */ const nodes = new Map<string, Node>( const nodes = new Map<string, Node<unknown>>( data.nodes?.map((node) => [node.id, node]), );
-
@@ -332,7 +348,8 @@ export function JSONCanvasRenderer(> <div xmlns="http://www.w3.org/1999/xhtml" style="padding: 1em;white-space: pre-wrap;" style={`width: ${node.width}px;height: ${node.height}px;`} className={C.Embed} > {node.text} </div>
-