mirror of
https://github.com/danbulant/lezer-markdown-obsidian
synced 2026-07-08 04:20:51 +00:00
alphabetize, add export to index
This commit is contained in:
parent
e8212b6644
commit
1b3e3db022
3 changed files with 290 additions and 278 deletions
|
|
@ -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}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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: [
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
export {
|
export {
|
||||||
Footnote,
|
Footnote,
|
||||||
|
Hashtag,
|
||||||
InternalLink,
|
InternalLink,
|
||||||
ObsidianMDExtensions,
|
ObsidianMDExtensions,
|
||||||
parser,
|
parser,
|
||||||
TaskList,
|
TaskList,
|
||||||
|
YAMLFrontMatter,
|
||||||
} from "./extensions";
|
} from "./extensions";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue