Changes
11 changed files (+685/-3)
-
-
@@ -14,6 +14,8 @@ import { ofmHighlight } from "./obsidian_markdown/micromark_extension_ofm_highlight.ts";import { ofmImageSize } from "./obsidian_markdown/mdast_util_ofm_image_size.ts"; import { ofmWikilink } from "./obsidian_markdown/micromark_extension_ofm_wikilink.ts"; import { ofmWikilinkFromMarkdown } from "./obsidian_markdown/mdast_util_ofm_wikilink.ts"; import { ofmComment } from "./obsidian_markdown/micromark_extension_ofm_comment.ts"; import { ofmCommentFromMarkdown } from "./obsidian_markdown/mdast_util_ofm_comment.ts"; import { macanaMarkAssets } from "./obsidian_markdown/mdast_util_macana_mark_assets.ts"; import { macanaMarkDocumentToken } from "./obsidian_markdown/mdast_util_macana_mark_document_token.ts"; import { ofmCalloutFromMarkdown } from "./obsidian_markdown/mdast_util_ofm_callout.ts";
-
@@ -29,6 +31,7 @@ export { macanaReplaceAssetTokens } from "./obsidian_markdown/mdast_util_macana_replace_asset_tokens.ts";export { macanaReplaceDocumentToken } from "./obsidian_markdown/mdast_util_macana_replace_document_tokens.ts"; export { ofmWikilinkToHastHandlers } from "./obsidian_markdown/mdast_util_ofm_wikilink.ts"; export { ofmCalloutToHastHandlers } from "./obsidian_markdown/mdast_util_ofm_callout.ts"; export { ofmCommentToHastHandlers } from "./obsidian_markdown/mdast_util_ofm_comment.ts"; export type { CalloutType } from "./obsidian_markdown/mdast_util_ofm_callout.ts"; function getFrontMatterValue(
-
@@ -101,8 +104,9 @@ "getAssetToken" | "getDocumentToken">, ): Promise<Mdast.Root> { const mdast = fromMarkdown(markdown, { extensions: [gfm(), ofmHighlight(), ofmWikilink()], extensions: [gfm(), ofmHighlight(), ofmWikilink(), ofmComment()], mdastExtensions: [ ofmCommentFromMarkdown(), gfmFromMarkdown(), ofmHighlightFromMarkdown(), ofmWikilinkFromMarkdown(),
-
-
-
@@ -0,0 +1,45 @@// 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 { ofmComment } from "./micromark_extension_ofm_comment.ts"; import { ofmCommentFromMarkdown } from "./mdast_util_ofm_comment.ts"; Deno.test("Should convert comment into Mdast", () => { const mdast = fromMarkdown("This is an %%inline%% comment.", { extensions: [ofmComment()], mdastExtensions: [ofmCommentFromMarkdown()], }); assertObjectMatch(mdast, { type: "root", children: [ { type: "paragraph", children: [ { type: "text", value: "This is an ", }, { type: "ofmComment", children: [ { type: "ofmCommentBody", value: "inline", }, ], }, { type: "text", value: " comment.", }, ], }, ], }); });
-
-
-
@@ -0,0 +1,97 @@// 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 type { Extension } from "../../deps/esm.sh/mdast-util-from-markdown/mod.ts"; import type { State } from "../../deps/esm.sh/mdast-util-to-hast/mod.ts"; import type * as Hast from "../../deps/esm.sh/hast/types.ts"; export interface OfmComment extends Mdast.Node { type: "ofmComment"; children: [OfmCommentBody]; } export interface OfmCommentBody extends Mdast.Node { type: "ofmCommentBody"; value: string; } export function ofmCommentFromMarkdown(): Extension { return { enter: { ofmInlineComment(token) { this.enter({ // @ts-expect-error: unist-related libraries heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. type: "ofmComment", children: [], }, token); }, ofmBlockComment(token) { this.enter({ // @ts-expect-error: unist-related libraries heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. type: "ofmComment", children: [], }, token); }, ofmCommentBody(token) { this.enter({ // @ts-expect-error: unist-related libraries heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. type: "ofmCommentBody", children: [], value: this.sliceSerialize(token), }, token); }, }, exit: { ofmInlineComment(token) { this.exit(token); }, ofmBlockComment(token) { this.exit(token); }, ofmCommentBody(token) { this.exit(token); }, }, }; } export interface OfmCommentToHastHandlersOptions { preserveAsHtmlComment?: boolean; } export function ofmCommentToHastHandlers( { preserveAsHtmlComment = false }: OfmCommentToHastHandlersOptions = {}, ) { if (!preserveAsHtmlComment) { return { ofmComment(): Hast.Nodes { return { type: "comment", value: "", }; }, }; } return { ofmComment(_state: State, node: OfmComment): Hast.Nodes { const [body] = node.children; if (!body) { return { type: "comment", value: "", }; } return { type: "comment", value: body.value, }; }, }; }
-
-
-
@@ -0,0 +1,128 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import { assertEquals } from "../../deps/deno.land/std/assert/mod.ts"; import { micromark } from "../../deps/esm.sh/micromark/mod.ts"; import { ofmComment, ofmCommentHtml, type OfmCommentHtmlOptions, } from "./micromark_extension_ofm_comment.ts"; function f(markdown: string, opts?: OfmCommentHtmlOptions): string { return micromark(markdown, { extensions: [ofmComment()], htmlExtensions: [ofmCommentHtml(opts)], }); } Deno.test("Should parse inline comment", () => { assertEquals( f("This is an %%inline%% comment."), `<p>This is an comment.</p>`, ); }); Deno.test("Should not treat non-terminated inline comment", () => { assertEquals( f("This is an %%inline comment."), `<p>This is an %%inline comment.</p>`, ); }); Deno.test("Should abort if it finds line-endings inside inline comment", () => { assertEquals( f("This is an %%inline\n comment.%%"), `<p>This is an %%inline\ncomment.%%</p>`, ); }); Deno.test("Should keep as HTML comment", () => { assertEquals( f("This is an %%inline%% comment.", { preserveAsHtmlComment: true, }), `<p>This is an <!--inline--> comment.</p>`, ); }); Deno.test("Should ignore non-sequential percent sign", () => { assertEquals( f("This is an %%%inline%% comment.", { preserveAsHtmlComment: true, }), `<p>This is an <!--%inline--> comment.</p>`, ); }); Deno.test("Should handle escape", () => { assertEquals( f("This is an \\%%inline%% comment%%.", { preserveAsHtmlComment: true, }), `<p>This is an %%inline<!-- comment-->.</p>`, ); }); Deno.test("Should parse block comment", () => { const code = ` %% This is a block comment. Block comments can span multiple lines. %% `.trim(); assertEquals( f(code, { preserveAsHtmlComment: true }), `<!-- This is a block comment. Block comments can span multiple lines. -->`, ); }); Deno.test("Should not parse contents inside block comment", () => { const code = ` %% **This is a block comment**. Block comments can span multiple lines. %% `.trim(); assertEquals( f(code, { preserveAsHtmlComment: true }), `<!-- **This is a block comment**. Block comments can span multiple lines. -->`, ); }); Deno.test("Should parse block comment in a document", () => { const code = ` This is a paragraph. %% This is a block comment. Block comments can span multiple lines. %% This is a paragraph too. `.trim(); assertEquals( f(code, { preserveAsHtmlComment: true }), `<p>This is a paragraph.</p> <!-- This is a block comment. Block comments can span multiple lines. --> <p>This is a paragraph too.</p>`, ); });
-
-
-
@@ -0,0 +1,332 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import type { CompileContext, Construct, Extension, HtmlExtension, State, Token, } from "../../deps/esm.sh/micromark-util-types/types.ts"; import { codes, types } from "../../deps/esm.sh/micromark-util-symbol/mod.ts"; import { markdownLineEnding } from "../../deps/esm.sh/micromark-util-character/mod.ts"; const enum TokenTypeMap { inline = "ofmInlineComment", block = "ofmBlockComment", chunk = "ofmCommentBodyChunk", body = "ofmCommentBody", } export interface OfmCommentHtmlOptions { /** * Whether to preserve comments as HTML comment (<!-- this -->) */ preserveAsHtmlComment?: boolean; } export function ofmCommentHtml( { preserveAsHtmlComment = false }: OfmCommentHtmlOptions = {}, ): HtmlExtension { if (!preserveAsHtmlComment) { return {}; } return { enter: { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. [TokenTypeMap.block](this: CompileContext) { this.raw("<!--"); }, [TokenTypeMap.inline](this: CompileContext) { this.raw("<!--"); }, [TokenTypeMap.chunk](this: CompileContext, token: Token) { // Prevent outputting broken HTML this.raw(this.sliceSerialize(token).replaceAll("-->", "")); }, }, exit: { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. [TokenTypeMap.block](this: CompileContext) { this.raw("-->"); }, [TokenTypeMap.inline](this: CompileContext) { this.raw("-->"); }, }, }; } export function ofmComment(): Extension { return { text: { [codes.percentSign]: { name: "ofm-inline-comment", tokenize(effects, ok, nok) { /** * ```markdown * > | %%body%% * ^^ * ``` */ const start: State = (code) => { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.enter(TokenTypeMap.inline); return effects.attempt(startMarker, beforeBody, nok)(code); }; const beforeBody: State = (code) => { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.enter(TokenTypeMap.body); // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.enter(TokenTypeMap.chunk); return body(code); }; const body: State = (code) => { if (code === codes.eof || markdownLineEnding(code)) { return nok(code); } if (code === codes.percentSign) { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.exit(TokenTypeMap.chunk); return effects.attempt(endMarker, beforeEnd, unbondedPercentSign)( code, ); } effects.consume(code); return body; }; const unbondedPercentSign: State = (code) => { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.enter(TokenTypeMap.chunk); effects.consume(code); return body; }; const beforeEnd: State = (code) => { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.exit(TokenTypeMap.inline); return ok(code); }; return start; }, }, }, flow: { [codes.percentSign]: { name: "ofm-block-comment", concrete: true, tokenize(effects, ok, nok) { /** * ```markdown * > | %% * ^^ * > | body * > | %% * ``` */ const start: State = (code) => { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.enter(TokenTypeMap.block); return effects.attempt(startMarker, beforeBody, nok)(code); }; const beforeBody: State = (code) => { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.enter(TokenTypeMap.body); return beforeChunk(code); }; const beforeChunk: State = (code) => { if (markdownLineEnding(code)) { effects.enter(types.lineEnding); effects.consume(code); effects.exit(types.lineEnding); return beforeChunk; } if (code === codes.percentSign) { return effects.attempt(endMarker, beforeEnd, unbondedPercentSign)( code, ); } // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.enter(TokenTypeMap.chunk); return chunk(code); }; /** * ```markdown * > | %% * > | body * ^^^^ * > | %% * ``` */ const chunk: State = (code) => { if (markdownLineEnding(code)) { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.exit(TokenTypeMap.chunk); effects.enter(types.lineEnding); effects.consume(code); effects.exit(types.lineEnding); return beforeChunk; } if (code === codes.percentSign) { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.exit(TokenTypeMap.chunk); return effects.attempt(endMarker, beforeEnd, unbondedPercentSign)( code, ); } effects.consume(code); return chunk; }; const unbondedPercentSign: State = (code) => { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.enter(TokenTypeMap.chunk); effects.consume(code); return chunk; }; const beforeEnd: State = (code) => { // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.exit(TokenTypeMap.block); return ok(code); }; return start; }, }, }, }; } const startMarker: Construct = { partial: true, tokenize(effects, ok, nok) { const { previous } = this; /** * ```markdown * > | %% * ^ * ``` */ const first: State = (code) => { if (code !== codes.percentSign || previous === codes.backslash) { return nok(code); } effects.consume(code); return second; }; /** * ```markdown * > | %% * ^ * ``` */ const second: State = (code) => { if (code !== codes.percentSign) { return nok(code); } effects.consume(code); return ok(code); }; return first; }, }; const endMarker: Construct = { partial: true, tokenize(effects, ok, nok) { const { previous } = this; /** * ```markdown * > | %% * ^ * ``` */ const first: State = (code) => { if (code !== codes.percentSign || previous === codes.backslash) { return nok(code); } // @ts-expect-error: micromark heavily relies on ambient module declarations, // which Deno does not support. APIs also don't accept type parameters. effects.exit(TokenTypeMap.body); effects.consume(code); return second; }; /** * ```markdown * > | %% * ^ * ``` */ const second: State = (code) => { if (code !== codes.percentSign) { return nok(code); } effects.consume(code); return ok(code); }; return first; }, };
-
-
-
@@ -281,6 +281,8 @@ "https://esm.sh/v135/micromark-factory-space@2.0.0/denonext/micromark-factory-space.mjs": "1ac7c90dec53f7f634767c5470c2dcf204f4df99ec318a27832786153d5c8110","https://esm.sh/v135/micromark-factory-title@2.0.0/denonext/micromark-factory-title.mjs": "1b202816c09c57894c8e254962ffbd8ad559439b0eb90ff0eca743f0dbfff470", "https://esm.sh/v135/micromark-factory-whitespace@2.0.0/denonext/micromark-factory-whitespace.mjs": "f85efacaec053453a9445b4147d2fb7ce02c1d083f0b0da9a730a112ff934d9e", "https://esm.sh/v135/micromark-util-character@2.0.1/denonext/micromark-util-character.mjs": "18b451d148e1ccc3a9b18e5c4061d44a0485e8ec65ad805d20b2950a51c7213b", "https://esm.sh/v135/micromark-util-character@2.1.0": "dab3a3101833f22c1e8c7f90eef3d03d9a6658b72204a004abaec47d9f238465", "https://esm.sh/v135/micromark-util-character@2.1.0/denonext/micromark-util-character.mjs": "9d6a0ba08282a3a306b38d255e77d4636ac1447de61008f7213f725c07f6b029", "https://esm.sh/v135/micromark-util-chunked@2.0.0": "6137fb50257da8a18125ac21ac885bd56f79e6ead241b0b004b0416c7906eaa2", "https://esm.sh/v135/micromark-util-chunked@2.0.0/denonext/micromark-util-chunked.mjs": "531cf323ba53649fdc30cd39ebba54253dfd847a4b23f806058ecc6cf67bca69", "https://esm.sh/v135/micromark-util-classify-character@2.0.0": "c92c93be5f8370abbd73783460c6e183f2b31fe74085175389e1c86d96aeef9b",
-
-
-
@@ -0,0 +1,5 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 export * from "https://esm.sh/v135/micromark-util-character@2.1.0";
-
-
-
@@ -0,0 +1,66 @@## Basics ### Inline ```markdown %%This is a comment%% ``` %%This is a comment%% ```markdown Inline %%comment can't include line-endings%% ``` Inline %%comment can't include line-endings%% The latter one is buggy inside Obsidian: renders this as "Inline (rest commented-out)" in edit view but `Inline %%comment can't include line-endings%%` in reading view. ### Block ```markdown %%This is a comment, too %% ``` %%This is a comment, too %% ```markdown %% **bold** %% ``` %% **bold** %% Obsidian renders "bold" in bold in edit view. ## Escapes ```markdown \%%This isn't a comment%% but this is%% ``` \%%This isn't a comment%% but this is%% ## Edge cases ### Triple percent sign ```markdown %%%This is a comment%% ``` %%%This is a comment%%
-
-
-
@@ -6,4 +6,5 @@Most of the unit tests are picked up from this folder. - [[Highlight extension]] - [[Callout extension]] - [[Callout extension]] - [[Comment extension]]
-
-
-
@@ -36,7 +36,7 @@ - [ ] List from another file- [ ] Search results - [x] ==Highlight== - [x] Callouts - [ ] Comments %% You can check this item once I'm no longer visible %% - [x] Comments %% You can check this item once I'm no longer visible %% - [ ] Strip Raw HTML (only `<title>` is troublesome, but align behavior to Obsidian's) - [ ] `<script>` <script>console.log("This tag should be eliminated: escaping is not suffice")</script> - [ ] `<title>` <title>Foo</title>
-
-
-
@@ -20,6 +20,7 @@ import {type CalloutType, type ObsidianMarkdownDocument, ofmCalloutToHastHandlers, ofmCommentToHastHandlers, ofmWikilinkToHastHandlers, } from "../../../content_parser/obsidian_markdown.ts"; import type { JSONCanvasDocument } from "../../../content_parser/json_canvas.ts";
-
@@ -196,6 +197,7 @@ children: [],}; }, }), ...ofmCommentToHastHandlers(), ...ofmWikilinkToHastHandlers, ...syntaxHighlightingHandlers(), },
-