Changes
4 changed files (+268/-2)
-
-
@@ -10,6 +10,7 @@ import { gfm } from "../deps/esm.sh/micromark-extension-gfm/mod.ts";import { ofmHighlightFromMarkdown } from "./obsidian_markdown/mdast_util_ofm_highlight.ts"; import { ofmHighlight } from "./obsidian_markdown/micromark_extension_ofm_highlight.ts"; import { ofmImageSize } from "./obsidian_markdown/mdast_util_ofm_image_size.ts"; import type { ContentParser,
-
@@ -61,10 +62,14 @@ frontmatter?: boolean;} function parseMarkdown(markdown: string | Uint8Array) { return fromMarkdown(markdown, { const mdast = fromMarkdown(markdown, { extensions: [gfm(), ofmHighlight()], mdastExtensions: [gfmFromMarkdown(), ofmHighlightFromMarkdown()], }); ofmImageSize(mdast); return mdast; } export class ObsidianMarkdownParser implements ContentParser {
-
-
-
@@ -0,0 +1,154 @@// 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 { fromMarkdown } from "../../deps/esm.sh/mdast-util-from-markdown/mod.ts"; import { toHast } from "../../deps/esm.sh/mdast-util-to-hast/mod.ts"; import { ofmImageSize } from "./mdast_util_ofm_image_size.ts"; Deno.test("Should parse full size attribute", () => { const mdast = fromMarkdown(""); ofmImageSize(mdast); assertObjectMatch(mdast, { type: "root", children: [ { type: "paragraph", children: [ { type: "image", alt: "Foo", data: { width: 100, height: 200, }, }, ], }, ], }); }); Deno.test("Should ignore negative sizes", () => { const mdast = fromMarkdown(""); ofmImageSize(mdast); assertObjectMatch(mdast, { type: "root", children: [ { type: "paragraph", children: [ { type: "image", alt: "Foo|-5x-3", }, ], }, ], }); }); Deno.test("Should ignore zero as a size", () => { const mdast = fromMarkdown(""); ofmImageSize(mdast); assertObjectMatch(mdast, { type: "root", children: [ { type: "paragraph", children: [ { type: "image", alt: "Foo|100x0", }, ], }, ], }); }); Deno.test("Should parse width-only attribute", () => { const mdast = fromMarkdown(""); ofmImageSize(mdast); assertObjectMatch(mdast, { type: "root", children: [ { type: "paragraph", children: [ { type: "image", alt: "Foo", data: { width: 123, }, }, ], }, ], }); }); Deno.test("Should work for image reference too", () => { const mdast = fromMarkdown("![Foo|999x888][foo]\n\n[foo]: ./foo.png"); ofmImageSize(mdast); assertObjectMatch(mdast, { type: "root", children: [ { type: "paragraph", children: [ { type: "imageReference", alt: "Foo", data: { width: 999, height: 888, }, }, ], }, ], }); }); Deno.test("Should set HTML attributes", () => { const mdast = fromMarkdown(""); ofmImageSize(mdast); const hast = toHast(mdast); assertObjectMatch(hast, { type: "root", children: [ { type: "element", tagName: "p", children: [ { type: "element", tagName: "img", properties: { width: 123, height: 456, alt: "Foo", }, }, ], }, ], }); });
-
-
-
@@ -0,0 +1,107 @@// 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 { SKIP, visit } from "../../deps/esm.sh/unist-util-visit/mod.ts"; const SEPARATOR = "|"; const SIZE_ATTR_REGEXP_PATTERN = /^([1-9][0-9]*)(x([1-9][0-9]*))?$/; function parseSegment( segment: string, ): { width: number; height: number | null } | null { const match = segment.match(SIZE_ATTR_REGEXP_PATTERN); if (!match) { return null; } const wStr = match[1]; const hStr = match[3]; const width = parseInt(wStr, 10); const height = typeof hStr === "string" ? parseInt(hStr, 10) : null; // Reject invalid values if ( !Number.isFinite(width) || width <= 0 || (typeof height === "number" && (!Number.isFinite(height) || height <= 0)) ) { return null; } return { width, height }; } function setSizeToNode(node: Mdast.Node, width: number, height: number | null) { node.data ??= {}; // @ts-expect-error: unist-related libraries heavily relies on ambiend module declarations, // which Deno does not support. APIs also don't accept type parameters. node.data.width = width; if (typeof height === "number") { // @ts-expect-error: unist-related libraries heavily relies on ambiend module declarations, // which Deno does not support. APIs also don't accept type parameters. node.data.height = height; } // @ts-expect-error: unist-related libraries heavily relies on ambiend module declarations, // which Deno does not support. APIs also don't accept type parameters. node.data.hProperties = { // @ts-expect-error: unist-related libraries heavily relies on ambiend module declarations, // which Deno does not support. APIs also don't accept type parameters. ...node.data.hProperties ?? {}, width, height: typeof height === "number" ? height : undefined, }; } /** * This function parses Obsidian's image size attribute extension, and modifies * image label and data in-place. * * @param tree - Tree to change. This function mutates this argument. */ export function ofmImageSize(tree: Mdast.Nodes): void { visit(tree, (node) => { return node.type === "image" || node.type === "imageReference"; }, (node_) => { // Tested on the `check` function. const node = node_ as (Mdast.Image | Mdast.ImageReference); if (!node.alt) { return SKIP; } const segments = node.alt.split(SEPARATOR); switch (segments.length) { case 1: { const result = parseSegment(segments[0]); if (!result) { return SKIP; } setSizeToNode(node, result.width, result.height); return; } case 2: { const [alt, segment] = segments; const result = parseSegment(segment); if (!result) { return SKIP; } setSizeToNode(node, result.width, result.height); node.alt = alt; return; } default: { return SKIP; } } }); }
-
-
-
@@ -20,7 +20,7 @@ - [ ] Label- [ ] Heading - [ ] Block reference - [ ] Defining a block - [ ] Image size annotation - [x] Image size annotation - [ ] Embeddings - [ ] Image file - [ ] Audio file
-