Update packages/remark-wiki-link/src/lib/remarkWikiLink.ts
Some checks failed
Release / Release (push) Failing after 1h6m8s

This commit is contained in:
knight 2025-04-08 18:12:43 +00:00
parent 816db6858c
commit 47b4ffece8

View File

@ -1,42 +1,203 @@
import { toMarkdown } from "mdast-util-wiki-link";
import { syntax, SyntaxOptions } from "./syntax";
import { fromMarkdown, FromMarkdownOptions } from "./fromMarkdown";
import { isSupportedFileFormat } from './isSupportedFileFormat';
let warningIssued = false;
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];
};
type RemarkWikiLinkOptions = FromMarkdownOptions & SyntaxOptions;
function remarkWikiLink(opts: RemarkWikiLinkOptions = {}) {
const data = this.data(); // this is a reference to the processor
function add(field, value) {
if (data[field]) data[field].push(value);
else data[field] = [value];
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
}
if (
!warningIssued &&
((this.Parser &&
this.Parser.prototype &&
this.Parser.prototype.blockTokenizers) ||
(this.Compiler &&
this.Compiler.prototype &&
this.Compiler.prototype.visitors))
) {
warningIssued = true;
console.warn(
"[remark-wiki-link] Warning: please upgrade to remark 13 to use this plugin"
export function getImageSize(size: string) {
// eslint-disable-next-line prefer-const
let [width, height] = size.split('x');
if (!height) height = width;
return { width, height };
}
// 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
);
}
// add extensions to packages used by remark-parse
// micromark extensions
add("micromarkExtensions", syntax(opts));
// mdast-util-from-markdown extensions
add("fromMarkdownExtensions", fromMarkdown(opts));
// mdast-util-to-markdown extensions
add("toMarkdownExtensions", toMarkdown(opts));
function exitWikiLinkTarget(token) {
const target = this.sliceSerialize(token);
const current = top(this.stack);
current.data.target = target;
}
export default remarkWikiLink;
export { remarkWikiLink };
function exitWikiLinkAlias(token) {
const alias = this.sliceSerialize(token);
const current = top(this.stack);
current.data.alias = alias;
}
function exitWikiLink(token) {
const wikiLink = top(this.stack)
const {
data: {isEmbed, target, alias},
} = wikiLink;
this.exit(token);
// eslint-disable-next-line no-useless-escape
const wikiLinkWithHeadingPattern = /^(.*?)(#.*)?$/u;
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+/g, '-').toLowerCase();
let classNames = wikiLinkClassName;
if (!matchingPermalink) {
classNames += ' ' + newClassName;
}
if (isEmbed) {
const [isSupportedFormat, format] = isSupportedFileFormat(target);
if (!isSupportedFormat) {
// Temporarily render note transclusion as a regular wiki link
if (!format) {
wikiLink.data.hName = 'a';
wikiLink.data.hProperties = {
className: classNames + ' ' + 'transclusion',
href: hrefTemplate(link) + headingId,
};
wikiLink.data.hChildren = [{ type: 'text', value: displayName }];
} else {
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 {
const hasDimensions = alias && /^\d+(x\d+)?$/.test(alias);
// Take the target as alt text except if alt name was provided [[target|alt text]]
const altText = hasDimensions || !alias ? target : alias;
wikiLink.data.hName = 'img';
wikiLink.data.hProperties = {
className: classNames,
src: hrefTemplate(link),
alt: altText
};
if (hasDimensions) {
const { width, height } = getImageSize(alias as string);
Object.assign(wikiLink.data.hProperties, {
width,
height,
});
}
}
} 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 };