alphabetize, add export to index

This commit is contained in:
Eric Rykwalder 2022-03-05 21:27:43 -05:00
parent e8212b6644
commit 1b3e3db022
3 changed files with 290 additions and 278 deletions

View file

@ -10,6 +10,7 @@ const specParser = new SpecParser(parser, {
__proto__: null as any, __proto__: null as any,
T: "Task", T: "Task",
t: "TaskMarker", t: "TaskMarker",
/* End Copyright */
EM: "Embed", EM: "Embed",
eM: "EmbedMark", eM: "EmbedMark",
H: "Hashtag", H: "Hashtag",
@ -29,6 +30,10 @@ const specParser = new SpecParser(parser, {
yc: "YAMLContent", yc: "YAMLContent",
}); });
/*
Copyright (C) 2020 by Marijn Haverbeke <marijnh@gmail.com> and others
https://github.com/lezer-parser/markdown/blob/f49eb8c8c82cfe45aa213ca1fe2cebc95305b88b/LICENSE
*/
function test(name: string, spec: string, p = parser, only = false) { function test(name: string, spec: string, p = parser, only = false) {
let f = it; let f = it;
if (only) { if (only) {
@ -39,45 +44,57 @@ function test(name: string, spec: string, p = parser, only = false) {
compareTree(p.parse(doc), tree); compareTree(p.parse(doc), tree);
}); });
} }
/* End Copyright */
describe("Obsidian Extension", () => { describe("Obsidian Extension", () => {
test( test(
"Task list (in unordered list)", "Footnotes",
` `
{BL:{LI:{l:-} {T:{t:[ ]} foo}} {P:Some info{FN:{fM:[^}{fL:1}{fM:]}}}
{LI:{l:-} {T:{t:[x]} bar}}}`
{P:Some more info{FN:{fM:[^}{fL:a$wacky^foot-note}{fM:]}}}
`
); );
test( test(
"Task list (in nested list)", "Footnote Reference (Simple)",
` `
{BL:{LI:{l:-} {T:{t:[x]} foo} {FR:{fM:[^}{fL:1}{fM:]:} Some basic info}
{BL:{LI:{l:-} {T:{t:[ ]} bar}} {FR:{fM:[^}{fL:2}{fM:]:} Some {St:{e:**}bold{e:**}} info}
{LI:{l:-} {T:{t:[x]} baz}}}} `
{LI:{l:-} {T:{t:[ ]} bim}}}`
); );
test( test(
"Task list (in ordered list)", "Footnote Reference (Multiline)",
` `
{OL:{LI:{l:1.} {T:{t:[X]} Okay}}}` {FR:{fM:[^}{fL:1}{fM:]:} Line 1
Line 2}
{FR:{fM:[^}{fL:2}{fM:]:} Line 3
Line 4
Line 5}
`
); );
test( test(
"Task list (versus setext header)", "Footnote Reference (Interspersed with Bullets)",
` `
{OL:{LI:{l:1.} {SH1:{Ln:{L:[}X{L:]}} foo {FR:{fM:[^}{fL:1}{fM:]:} Line 1}
{h:===}}}}` {BL:{LI:{l:-} {P:Line 2
{FN:{fM:[^}{fL:2}{fM:]}}: Line 3}}}
{FR:{fM:[^}{fL:2}{fM:]:} Line 5}
{BL:{LI:{l:-} {P:Line 6}}}
`
); );
/* End Copyright */
test( test(
"Task list (different markers)", "Hashtag",
`
{BL:{LI:{l:-} {T:{t:[a]} foo}}
{LI:{l:-} {T:{t:[[]} bar}}
{LI:{l:-} {T:{t:[]]} baz}}
{LI:{l:-} {T:{t:[\\]} bim}}}
` `
{P:Some text. {H:{hm:#}{hl:tag}} {H:{hm:#}{hl:other-tag9}}^not part
{H:{hm:#}{hl:ñáø}}}
{P:Test number #1234}
`
); );
test( test(
@ -129,48 +146,51 @@ describe("Obsidian Extension", () => {
` `
); );
/*
Copyright (C) 2020 by Marijn Haverbeke <marijnh@gmail.com> and others
https://github.com/lezer-parser/markdown/blob/f49eb8c8c82cfe45aa213ca1fe2cebc95305b88b/LICENSE
*/
test( test(
"Footnotes", "Task list (in unordered list)",
` `
{P:Some info{FN:{fM:[^}{fL:1}{fM:]}}} {BL:{LI:{l:-} {T:{t:[ ]} foo}}
{LI:{l:-} {T:{t:[x]} bar}}}`
{P:Some more info{FN:{fM:[^}{fL:a$wacky^foot-note}{fM:]}}}
`
); );
test( test(
"Footnote Reference (Simple)", "Task list (in nested list)",
` `
{FR:{fM:[^}{fL:1}{fM:]:} Some basic info} {BL:{LI:{l:-} {T:{t:[x]} foo}
{FR:{fM:[^}{fL:2}{fM:]:} Some {St:{e:**}bold{e:**}} info} {BL:{LI:{l:-} {T:{t:[ ]} bar}}
` {LI:{l:-} {T:{t:[x]} baz}}}}
{LI:{l:-} {T:{t:[ ]} bim}}}`
); );
test( test(
"Footnote Reference (Multiline)", "Task list (in ordered list)",
` `
{FR:{fM:[^}{fL:1}{fM:]:} Line 1 {OL:{LI:{l:1.} {T:{t:[X]} Okay}}}`
Line 2}
{FR:{fM:[^}{fL:2}{fM:]:} Line 3
Line 4
Line 5}
`
); );
test( test(
"Footnote Reference (Interspersed with Bullets)", "Task list (versus setext header)",
`
{OL:{LI:{l:1.} {SH1:{Ln:{L:[}X{L:]}} foo
{h:===}}}}`
);
/* End Copyright */
test(
"Task list (different markers)",
`
{BL:{LI:{l:-} {T:{t:[a]} foo}}
{LI:{l:-} {T:{t:[[]} bar}}
{LI:{l:-} {T:{t:[]]} baz}}
{LI:{l:-} {T:{t:[\\]} bim}}}
` `
{FR:{fM:[^}{fL:1}{fM:]:} Line 1}
{BL:{LI:{l:-} {P:Line 2
{FN:{fM:[^}{fL:2}{fM:]}}: Line 3}}}
{FR:{fM:[^}{fL:2}{fM:]:} Line 5}
{BL:{LI:{l:-} {P:Line 6}}}
`
); );
test( test(
"Frontmatter", "YAMLFrontMatter",
` `
{YF:{ym:---} {YF:{ym:---}
{yc:tags: blah} {yc:tags: blah}
@ -186,7 +206,7 @@ Line 5}
); );
test( test(
"Frontmatter (trailing text)", "YAMLFrontMatter (trailing text)",
` `
{YF:{ym:---} {YF:{ym:---}
{yc:tags: blah} {yc:tags: blah}
@ -202,7 +222,7 @@ Line 5}
); );
test( test(
"Not Frontmatter (no close)", "Not YAMLFrontMatter (no close)",
` `
{HR:---} {HR:---}
@ -214,7 +234,7 @@ Line 5}
); );
test( test(
"Not Frontmatter (close indented)", "Not YAMLFrontMatter (close indented)",
` `
{HR:---} {HR:---}
@ -225,7 +245,7 @@ Line 5}
); );
test( test(
"Not Frontmatter (space after open)", "Not YAMLFrontMatter (space after open)",
` `
{HR:--- } {HR:--- }
@ -236,7 +256,7 @@ Line 5}
); );
test( test(
"Not Frontmatter (data before open)", "Not YAMLFrontMatter (data before open)",
` `
{P:some text} {P:some text}
@ -247,14 +267,4 @@ Line 5}
{HR:---} {HR:---}
` `
); );
test(
"Hashtag",
`
{P:Some text. {H:{hm:#}{hl:tag}} {H:{hm:#}{hl:other-tag9}}^not part
{H:{hm:#}{hl:ñáø}}}
{P:Test number #1234}
`
);
}); });

View file

@ -19,224 +19,6 @@ declare module "@lezer/markdown" {
} }
} }
/*
Copyright (C) 2020 by Marijn Haverbeke <marijnh@gmail.com> and others
https://github.com/lezer-parser/markdown/blob/f49eb8c8c82cfe45aa213ca1fe2cebc95305b88b/LICENSE
*/
class TaskParser implements LeafBlockParser {
nextLine() {
return false;
}
finish(cx: BlockContext, leaf: LeafBlock) {
cx.addLeafElement(
leaf,
cx.elt("Task", leaf.start, leaf.start + leaf.content.length, [
cx.elt("TaskMarker", leaf.start, leaf.start + 3),
...cx.parser.parseInline(leaf.content.slice(3), leaf.start + 3),
])
);
return true;
}
}
/// Extension providing
/// [GFM-style](https://github.github.com/gfm/#task-list-items-extension-)
/// task list items, where list items can be prefixed with `[ ]` or
/// `[x]` to add a checkbox.
/// `x` can be any character
export const TaskList: MarkdownConfig = {
defineNodes: [{ name: "Task", block: true }, "TaskMarker"],
parseBlock: [
{
name: "TaskList",
leaf(cx, leaf) {
return /^\[.\]/.test(leaf.content) && cx.parentType().name == "ListItem"
? new TaskParser()
: null;
},
after: "SetextHeading",
},
],
};
/* End Copyright */
const hashtagRE =
/^[^\u2000-\u206F\u2E00-\u2E7F'!"#$%&()*+,.:;<=>?@^`{|}~\[\]\\\s]+/;
export const Hashtag: MarkdownConfig = {
defineNodes: ["Hashtag", "HashtagMark", "HashtagLabel"],
parseInline: [
{
name: "Hashtag",
parse(cx, next, pos) {
if (next != 35 /* # */) {
return -1;
}
const start = pos;
pos += 1;
const match = hashtagRE.exec(cx.text.slice(pos - cx.offset));
if (match && /\D/.test(match[0])) {
pos += match[0].length;
return cx.addElement(
cx.elt("Hashtag", start, pos, [
cx.elt("HashtagMark", start, start + 1),
cx.elt("HashtagLabel", start + 1, pos),
])
);
}
return -1;
},
},
],
};
function parseInternalLink(cx: InlineContext, pos: number): Element | null {
if (
cx.char(pos) != 91 /* [ */ ||
cx.char(pos + 1) != 91 ||
!isClosedLink(cx, pos)
) {
return null;
}
const contents: Element[] = [];
contents.push(cx.elt("InternalMark", pos, pos + 2));
pos = cx.skipSpace(pos + 2);
const path = parsePath(cx, pos - cx.offset, cx.offset);
if (path) {
contents.push(path);
pos = cx.skipSpace(path.to);
}
const subpath = parseSubpath(cx, pos);
if (subpath) {
contents.push(subpath);
pos = cx.skipSpace(subpath.to);
}
if (path == null && subpath == null) {
return null;
}
if (cx.char(pos) == 124 /* | */) {
contents.push(cx.elt("InternalMark", pos, pos + 1));
pos += 1;
const display = parseDisplay(cx, pos);
if (display) {
contents.push(display);
pos = cx.skipSpace(display.to);
}
}
contents.push(cx.elt("InternalMark", pos, pos + 2));
return cx.elt(
"InternalLink",
contents[0].from,
contents[contents.length - 1].to,
contents
);
}
export const InternalLink: MarkdownConfig = {
defineNodes: [
"Embed",
"EmbedMark",
"InternalLink",
"InternalMark",
"InternalPath",
"InternalSubpath",
"InternalDisplay",
],
parseInline: [
{
name: "InternalLink",
parse(cx: InlineContext, _: number, pos: number) {
const el = parseInternalLink(cx, pos);
if (el) {
return cx.addElement(el);
}
return -1;
},
before: "Link",
},
{
name: "Embed",
parse(cx: InlineContext, next: number, pos: number): number {
if (next != 33) {
return -1;
}
const link = parseInternalLink(cx, pos + 1);
if (link) {
const embedMark = cx.elt("EmbedMark", pos, pos + 1);
return cx.addElement(
cx.elt("Embed", pos, link.to, [embedMark, link])
);
}
return -1;
},
before: "Image",
},
],
};
function isClosedLink(cx: InlineContext, start: number): boolean {
for (let pos = start + 2; pos < cx.end; pos++) {
if (cx.char(pos) == 91 /* [ */ && cx.char(pos + 1) == 91) {
return false;
} else if (cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) {
// return false for empty
// true otherwise
return pos > start + 2;
}
}
return false;
}
function parsePath(
cx: InlineContext,
start: number,
offset: number
): Element | null {
// anything but: |[]#^\/
const match = /^[^[\]|#^\\/]+/.exec(cx.text.slice(start));
if (match) {
return cx.elt(
"InternalPath",
offset + start,
offset + start + match[0].length
);
}
return null;
}
function parseSubpath(cx: InlineContext, start: number): Element | null {
if (cx.char(start) != 35 /* # */) {
return null;
}
for (let pos = start + 1; pos < cx.end; pos++) {
if (
cx.char(pos) == 124 /* | */ ||
(cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93)
) {
return cx.elt("InternalSubpath", start, pos);
}
}
return null;
}
function parseDisplay(cx: InlineContext, start: number): Element | null {
for (let pos = start; pos < cx.end; pos++) {
if (cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) {
if (pos == start) {
return null;
}
return cx.elt("InternalDisplay", start, pos);
}
}
return null;
}
function isFootnoteRef(content: string): number {
const match = /^\[\^[^\s[\]]+\]:/.exec(content);
return match ? match[0].length : -1;
}
class FootnoteReferenceParser implements LeafBlockParser { class FootnoteReferenceParser implements LeafBlockParser {
constructor(private labelEnd: number) {} constructor(private labelEnd: number) {}
@ -317,6 +99,224 @@ export const Footnote: MarkdownConfig = {
], ],
}; };
function isFootnoteRef(content: string): number {
const match = /^\[\^[^\s[\]]+\]:/.exec(content);
return match ? match[0].length : -1;
}
const hashtagRE =
/^[^\u2000-\u206F\u2E00-\u2E7F'!"#$%&()*+,.:;<=>?@^`{|}~\[\]\\\s]+/;
export const Hashtag: MarkdownConfig = {
defineNodes: ["Hashtag", "HashtagMark", "HashtagLabel"],
parseInline: [
{
name: "Hashtag",
parse(cx, next, pos) {
if (next != 35 /* # */) {
return -1;
}
const start = pos;
pos += 1;
const match = hashtagRE.exec(cx.text.slice(pos - cx.offset));
if (match && /\D/.test(match[0])) {
pos += match[0].length;
return cx.addElement(
cx.elt("Hashtag", start, pos, [
cx.elt("HashtagMark", start, start + 1),
cx.elt("HashtagLabel", start + 1, pos),
])
);
}
return -1;
},
},
],
};
export const InternalLink: MarkdownConfig = {
defineNodes: [
"Embed",
"EmbedMark",
"InternalLink",
"InternalMark",
"InternalPath",
"InternalSubpath",
"InternalDisplay",
],
parseInline: [
{
name: "InternalLink",
parse(cx: InlineContext, _: number, pos: number) {
const el = parseInternalLink(cx, pos);
if (el) {
return cx.addElement(el);
}
return -1;
},
before: "Link",
},
{
name: "Embed",
parse(cx: InlineContext, next: number, pos: number): number {
if (next != 33) {
return -1;
}
const link = parseInternalLink(cx, pos + 1);
if (link) {
const embedMark = cx.elt("EmbedMark", pos, pos + 1);
return cx.addElement(
cx.elt("Embed", pos, link.to, [embedMark, link])
);
}
return -1;
},
before: "Image",
},
],
};
function parseInternalLink(cx: InlineContext, pos: number): Element | null {
if (
cx.char(pos) != 91 /* [ */ ||
cx.char(pos + 1) != 91 ||
!isClosedLink(cx, pos)
) {
return null;
}
const contents: Element[] = [];
contents.push(cx.elt("InternalMark", pos, pos + 2));
pos = cx.skipSpace(pos + 2);
const path = parsePath(cx, pos - cx.offset, cx.offset);
if (path) {
contents.push(path);
pos = cx.skipSpace(path.to);
}
const subpath = parseSubpath(cx, pos);
if (subpath) {
contents.push(subpath);
pos = cx.skipSpace(subpath.to);
}
if (path == null && subpath == null) {
return null;
}
if (cx.char(pos) == 124 /* | */) {
contents.push(cx.elt("InternalMark", pos, pos + 1));
pos += 1;
const display = parseDisplay(cx, pos);
if (display) {
contents.push(display);
pos = cx.skipSpace(display.to);
}
}
contents.push(cx.elt("InternalMark", pos, pos + 2));
return cx.elt(
"InternalLink",
contents[0].from,
contents[contents.length - 1].to,
contents
);
}
function isClosedLink(cx: InlineContext, start: number): boolean {
for (let pos = start + 2; pos < cx.end; pos++) {
if (cx.char(pos) == 91 /* [ */ && cx.char(pos + 1) == 91) {
return false;
} else if (cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) {
// return false for empty
// true otherwise
return pos > start + 2;
}
}
return false;
}
function parsePath(
cx: InlineContext,
start: number,
offset: number
): Element | null {
// anything but: |[]#^\/
const match = /^[^[\]|#^\\/]+/.exec(cx.text.slice(start));
if (match) {
return cx.elt(
"InternalPath",
offset + start,
offset + start + match[0].length
);
}
return null;
}
function parseSubpath(cx: InlineContext, start: number): Element | null {
if (cx.char(start) != 35 /* # */) {
return null;
}
for (let pos = start + 1; pos < cx.end; pos++) {
if (
cx.char(pos) == 124 /* | */ ||
(cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93)
) {
return cx.elt("InternalSubpath", start, pos);
}
}
return null;
}
function parseDisplay(cx: InlineContext, start: number): Element | null {
for (let pos = start; pos < cx.end; pos++) {
if (cx.char(pos) == 93 /* ] */ && cx.char(pos + 1) == 93) {
if (pos == start) {
return null;
}
return cx.elt("InternalDisplay", start, pos);
}
}
return null;
}
/*
Copyright (C) 2020 by Marijn Haverbeke <marijnh@gmail.com> and others
https://github.com/lezer-parser/markdown/blob/f49eb8c8c82cfe45aa213ca1fe2cebc95305b88b/LICENSE
*/
class TaskParser implements LeafBlockParser {
nextLine() {
return false;
}
finish(cx: BlockContext, leaf: LeafBlock) {
cx.addLeafElement(
leaf,
cx.elt("Task", leaf.start, leaf.start + leaf.content.length, [
cx.elt("TaskMarker", leaf.start, leaf.start + 3),
...cx.parser.parseInline(leaf.content.slice(3), leaf.start + 3),
])
);
return true;
}
}
/// Extension providing
/// [GFM-style](https://github.github.com/gfm/#task-list-items-extension-)
/// task list items, where list items can be prefixed with `[ ]` or
/// `[x]` to add a checkbox.
/// `x` can be any character
export const TaskList: MarkdownConfig = {
defineNodes: [{ name: "Task", block: true }, "TaskMarker"],
parseBlock: [
{
name: "TaskList",
leaf(cx, leaf) {
return /^\[.\]/.test(leaf.content) && cx.parentType().name == "ListItem"
? new TaskParser()
: null;
},
after: "SetextHeading",
},
],
};
/* End Copyright */
export const YAMLFrontMatter: MarkdownConfig = { export const YAMLFrontMatter: MarkdownConfig = {
defineNodes: ["YAMLFrontMatter", "YAMLMarker", "YAMLContent"], defineNodes: ["YAMLFrontMatter", "YAMLMarker", "YAMLContent"],
parseBlock: [ parseBlock: [

View file

@ -1,7 +1,9 @@
export { export {
Footnote, Footnote,
Hashtag,
InternalLink, InternalLink,
ObsidianMDExtensions, ObsidianMDExtensions,
parser, parser,
TaskList, TaskList,
YAMLFrontMatter,
} from "./extensions"; } from "./extensions";