diff --git a/__tests__/extensions.ts b/__tests__/extensions.ts index 8fed666..e2567ad 100644 --- a/__tests__/extensions.ts +++ b/__tests__/extensions.ts @@ -21,6 +21,9 @@ const specParser = new SpecParser(parser, { fM: "FootnoteMark", fL: "FootnoteLabel", FR: "FootnoteReference", + YF: "YAMLFrontMatter", + ym: "YAMLMarker", + yc: "YAMLContent", }); function test(name: string, spec: string, p = parser, only = false) { @@ -162,4 +165,83 @@ Line 5} {BL:{LI:{l:-} {P:Line 6}}} ` ); + + test( + "Frontmatter", + ` +{YF:{ym:---} +{yc:tags: blah} +{ym:---}} + +{HR:---} + +{P:some text} + +{SH2:A header +{h:---}} + ` + ); + + test( + "Frontmatter (trailing text)", + ` +{YF:{ym:---} +{yc:tags: blah} +{ym:---}}{P:test} + +{HR:---} + +{P:some text} + +{SH2:A header +{h:---}} + ` + ); + + test( + "Not Frontmatter (no close)", + ` +{HR:---} + +{P:some text} + +{SH1:A header +{h:===}} + ` + ); + + test( + "Not Frontmatter (close indented)", + ` +{HR:---} + +{P:some text} + + {HR:---} + ` + ); + + test( + "Not Frontmatter (space after open)", + ` +{HR:--- } + +{P:some text} + +{HR:---} + ` + ); + + test( + "Not Frontmatter (data before open)", + ` +{P:some text} + +{HR:---} + +{P:some text} + +{HR:---} + ` + ); }); diff --git a/src/extensions.ts b/src/extensions.ts index 88640a0..27e53ed 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -1,3 +1,4 @@ +import { Input, PartialParse, Tree } from "@lezer/common"; import { BlockContext, Element, @@ -11,6 +12,13 @@ import { Table, } from "@lezer/markdown"; +declare module "@lezer/markdown" { + class BlockContext { + readonly input: Input; + checkedYaml: boolean | null; + } +} + /* Copyright (C) 2020 by Marijn Haverbeke and others https://github.com/lezer-parser/markdown/blob/f49eb8c8c82cfe45aa213ca1fe2cebc95305b88b/LICENSE @@ -279,8 +287,42 @@ export const Footnote: MarkdownConfig = { ], }; +export const YAMLFrontMatter: MarkdownConfig = { + defineNodes: ["YAMLFrontMatter", "YAMLMarker", "YAMLContent"], + parseBlock: [ + { + name: "YAMLFrontMatter", + parse(cx, line) { + if (cx.checkedYaml) { + return false; + } + cx.checkedYaml = true; + const fmRegex = /(^|^\s*\n)(---\n.+?\n---)/s; + const match = fmRegex.exec(cx.input.chunk(0)); + if (match) { + const start = match[1].length; + const end = start + match[2].length; + cx.addElement( + cx.elt("YAMLFrontMatter", start, end, [ + cx.elt("YAMLMarker", start, start + 3), + cx.elt("YAMLContent", start + 4, end - 4), + cx.elt("YAMLMarker", end - 3, end), + ]) + ); + while (cx.lineStart + line.text.length < end && cx.nextLine()) {} + line.pos = 3; + return true; + } + return false; + }, + before: "LinkReference", + }, + ], +}; + export const ObsidianMDExtensions = [ Footnote, + YAMLFrontMatter, InternalLink, Strikethrough, Table,