This repository has no description
1export interface MarkupContext {
2 /** `owner/repo` */
3 repo: string;
4 ref: string;
5 dir?: string;
6 host?: string;
7}
8
9const ABSOLUTE = /^[a-z][a-z0-9+.-]*:|^\/\//i;
10
11export const isAbsoluteUrl = (url: string): boolean => ABSOLUTE.test(url);
12
13export const isRepoRelative = (url: string): boolean =>
14 url !== "" && !isAbsoluteUrl(url) && !url.startsWith("#");
15
16// `.` and `..` have to collapse here instead of reaching the url
17const normalize = (path: string): string => {
18 const parts: string[] = [];
19 for (const part of path.split("/")) {
20 if (part === "" || part === ".") continue;
21 if (part === "..") parts.pop();
22 else parts.push(part);
23 }
24 return parts.join("/");
25};
26
27const withinRepo = (path: string, ctx: MarkupContext): string =>
28 normalize(path.startsWith("/") ? path : `${ctx.dir ?? ""}/${path}`);
29
30const splitSuffix = (url: string): [string, string] => {
31 const at = url.search(/[?#]/);
32 return at === -1 ? [url, ""] : [url.slice(0, at), url.slice(at)];
33};
34
35// markdown-it percent-encodes destinations before we ever see them, so only the
36// ref still needs encoding
37const repoUrl = (kind: string, url: string, ctx: MarkupContext): string => {
38 const [path, suffix] = splitSuffix(url);
39 if (path === "") return url;
40 const ref = encodeURIComponent(ctx.ref);
41 return `/${ctx.repo}/${kind}/${ref}/${withinRepo(path, ctx)}${suffix}`;
42};
43
44export const treeUrl = (url: string, ctx: MarkupContext): string => repoUrl("tree", url, ctx);
45
46export const rawUrl = (url: string, ctx: MarkupContext): string => repoUrl("raw", url, ctx);
47
48export const rawSrcset = (srcset: string, ctx: MarkupContext): string =>
49 srcset
50 .split(",")
51 .map((candidate) => {
52 const [url, ...descriptors] = candidate.trim().split(/\s+/);
53 if (!isRepoRelative(url)) return candidate.trim();
54 return [rawUrl(url, ctx), ...descriptors].join(" ");
55 })
56 .join(", ");