* [packages][m]: mv @flowershow/core package here * [packages/core][xs]: rename to @portaljs/core * [package.json][xs]: setup npm workspaces * [packages/core][xs]:replace deprecated rollup executor * [core/package.json][s]: fix mermaid versions * [core/tsconfig][xs]: rm extends * [core/jest.config][xs]: rm coverageDirectory * [core/package.json][xs]: install core-js * [packages.json][s]:use same version for all nrwl packages * [core/.eslintrc][xs]: adjust ignorePatterns * [core/project.json][xs]: rm publish targets * [packages][m]: mv @flowershow/remark-wiki-link here * [packages][m]: mv @flowershow/remark-wiki-link here * [packages][m]: mv @flowershow/remark-embed here * [remark-callouts/project.json][xs]: adjst test pattern * [package.json][s]: install missing deps * [remark-callouts][xs]: adjst fields in package.json * [remark-callouts][s]: rm pubish targets and adjst build executor * [remark-embed/jest.config][xs]: rm unknown option coverageDirectory * [remark-embed][xs]: rm publish targets * [remark-embed][s]: rename to @portaljs/remark-embed * [remark-wiki-link/eslintrc][xs]:adjst ignorePatterns * [package.json][xs]: install missing deps * [remark-wiki-link/test][xs]:specify format - also temporarily force any type on htmlExtension * [remark-wiki-link/README][xs]: replace @flowershow with @portaljs * [remark-wiki-link][xs]:rm old changelog * [remark-wiki-link][xs]: adjst package.json * [remark-wiki-link/project.json][xs]: rm publish targets * [core][s]: rm old changelog * [core/README][xs]:correct scope name * [remark-callouts/README][xs]: add @portaljs to pckg name * [remark-embed/README][xs]: add @portaljs to pckg name * [package-lock.json][xs]: refresh after rebasing on main
173 lines
5.3 KiB
TypeScript
173 lines
5.3 KiB
TypeScript
import { isSupportedFileFormat } from "./isSupportedFileFormat";
|
|
|
|
const defaultWikiLinkResolver = (target: string) => {
|
|
// for [[#heading]] links
|
|
if (!target) {
|
|
return [];
|
|
}
|
|
let permalink = target.replace(/\/index$/, "");
|
|
// TODO what to do with [[index]] link?
|
|
if (permalink.length === 0) {
|
|
permalink = "/";
|
|
}
|
|
return [permalink];
|
|
};
|
|
|
|
export interface FromMarkdownOptions {
|
|
pathFormat?:
|
|
| "raw" // default; use for regular relative or absolute paths
|
|
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
|
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
|
|
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
|
|
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
|
|
newClassName?: string; // class name to add to links that don't have a matching permalink
|
|
wikiLinkClassName?: string; // class name to add to all wiki links
|
|
hrefTemplate?: (permalink: string) => string; // function to generate the href attribute of a link
|
|
}
|
|
|
|
// mdas-util-from-markdown extension
|
|
// https://github.com/syntax-tree/mdast-util-from-markdown#extension
|
|
function fromMarkdown(opts: FromMarkdownOptions = {}) {
|
|
const pathFormat = opts.pathFormat || "raw";
|
|
const permalinks = opts.permalinks || [];
|
|
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
|
|
const newClassName = opts.newClassName || "new";
|
|
const wikiLinkClassName = opts.wikiLinkClassName || "internal";
|
|
const defaultHrefTemplate = (permalink: string) => permalink;
|
|
|
|
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
|
|
|
|
function top(stack) {
|
|
return stack[stack.length - 1];
|
|
}
|
|
|
|
function enterWikiLink(token) {
|
|
this.enter(
|
|
{
|
|
type: "wikiLink",
|
|
data: {
|
|
isEmbed: token.isType === "embed",
|
|
target: null, // the target of the link, e.g. "Foo Bar#Heading" in "[[Foo Bar#Heading]]"
|
|
alias: null, // the alias of the link, e.g. "Foo" in "[[Foo Bar|Foo]]"
|
|
permalink: null, // TODO shouldn't this be named just "link"?
|
|
exists: null, // TODO is this even needed here?
|
|
// fields for mdast-util-to-hast (used e.g. by remark-rehype)
|
|
hName: null,
|
|
hProperties: null,
|
|
hChildren: null,
|
|
},
|
|
},
|
|
token
|
|
);
|
|
}
|
|
|
|
function exitWikiLinkTarget(token) {
|
|
const target = this.sliceSerialize(token);
|
|
const current = top(this.stack);
|
|
current.data.target = target;
|
|
}
|
|
|
|
function exitWikiLinkAlias(token) {
|
|
const alias = this.sliceSerialize(token);
|
|
const current = top(this.stack);
|
|
current.data.alias = alias;
|
|
}
|
|
|
|
function exitWikiLink(token) {
|
|
const wikiLink = this.exit(token);
|
|
const {
|
|
data: { isEmbed, target, alias },
|
|
} = wikiLink;
|
|
// eslint-disable-next-line no-useless-escape
|
|
const wikiLinkWithHeadingPattern = /([\w\s\/\.-]*)(#.*)?/;
|
|
const [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
|
|
|
|
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
|
|
|
|
const matchingPermalink = permalinks.find((e) => {
|
|
return possibleWikiLinkPermalinks.find((p) => {
|
|
if (pathFormat === "obsidian-short") {
|
|
if (e === p || e.endsWith(p)) {
|
|
return true;
|
|
}
|
|
} else if (pathFormat === "obsidian-absolute") {
|
|
if (e === "/" + p) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (e === p) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
});
|
|
|
|
// TODO this is ugly
|
|
const link =
|
|
matchingPermalink ||
|
|
(pathFormat === "obsidian-absolute"
|
|
? "/" + possibleWikiLinkPermalinks[0]
|
|
: possibleWikiLinkPermalinks[0]) ||
|
|
"";
|
|
|
|
wikiLink.data.exists = !!matchingPermalink;
|
|
wikiLink.data.permalink = link;
|
|
|
|
// remove leading # if the target is a heading on the same page
|
|
const displayName = alias || target.replace(/^#/, "");
|
|
const headingId = heading.replace(/\s+/, "-").toLowerCase();
|
|
let classNames = wikiLinkClassName;
|
|
if (!matchingPermalink) {
|
|
classNames += " " + newClassName;
|
|
}
|
|
|
|
if (isEmbed) {
|
|
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
|
if (!isSupportedFormat) {
|
|
wikiLink.data.hName = "p";
|
|
wikiLink.data.hChildren = [
|
|
{
|
|
type: "text",
|
|
value: `![[${target}]]`,
|
|
},
|
|
];
|
|
} else if (format === "pdf") {
|
|
wikiLink.data.hName = "iframe";
|
|
wikiLink.data.hProperties = {
|
|
className: classNames,
|
|
width: "100%",
|
|
src: `${hrefTemplate(link)}#toolbar=0`,
|
|
};
|
|
} else {
|
|
wikiLink.data.hName = "img";
|
|
wikiLink.data.hProperties = {
|
|
className: classNames,
|
|
src: hrefTemplate(link),
|
|
alt: displayName,
|
|
};
|
|
}
|
|
} else {
|
|
wikiLink.data.hName = "a";
|
|
wikiLink.data.hProperties = {
|
|
className: classNames,
|
|
href: hrefTemplate(link) + headingId,
|
|
};
|
|
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
|
|
}
|
|
}
|
|
|
|
return {
|
|
enter: {
|
|
wikiLink: enterWikiLink,
|
|
},
|
|
exit: {
|
|
wikiLinkTarget: exitWikiLinkTarget,
|
|
wikiLinkAlias: exitWikiLinkAlias,
|
|
wikiLink: exitWikiLink,
|
|
},
|
|
};
|
|
}
|
|
|
|
export { fromMarkdown };
|