This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / markup / paths.ts
2.5 kB 77 lines
1export interface MarkupContext { 2 /** `owner/repo` */ 3 repo: string; 4 ref: string; 5 dir?: string; 6 host?: string; 7 camo?: boolean; 8} 9 10const ABSOLUTE = /^[a-z][a-z0-9+.-]*:|^\/\//i; 11 12export const isAbsoluteUrl = (url: string): boolean => ABSOLUTE.test(url); 13 14export const isRepoRelative = (url: string): boolean => 15 url !== "" && !isAbsoluteUrl(url) && !url.startsWith("#"); 16 17// `.` and `..` have to collapse here instead of reaching the url 18const normalize = (path: string): string => { 19 const parts: string[] = []; 20 for (const part of path.split("/")) { 21 if (part === "" || part === ".") continue; 22 if (part === "..") parts.pop(); 23 else parts.push(part); 24 } 25 return parts.join("/"); 26}; 27 28const withinRepo = (path: string, ctx: MarkupContext): string => 29 normalize(path.startsWith("/") ? path : `${ctx.dir ?? ""}/${path}`); 30 31const splitSuffix = (url: string): [string, string] => { 32 const at = url.search(/[?#]/); 33 return at === -1 ? [url, ""] : [url.slice(0, at), url.slice(at)]; 34}; 35 36// markdown-it percent-encodes destinations before we ever see them, so only the 37// ref still needs encoding 38const repoUrl = (kind: string, url: string, ctx: MarkupContext): string => { 39 const [path, suffix] = splitSuffix(url); 40 if (path === "") return url; 41 const ref = encodeURIComponent(ctx.ref); 42 return `/${ctx.repo}/${kind}/${ref}/${withinRepo(path, ctx)}${suffix}`; 43}; 44 45export const treeUrl = (url: string, ctx: MarkupContext): string => repoUrl("tree", url, ctx); 46 47export const rawUrl = (url: string, ctx: MarkupContext): string => repoUrl("raw", url, ctx); 48 49const hostOf = (url: string): string | null => { 50 try { 51 // the base only matters for protocol relative urls, which have a host anyway 52 return new URL(url, "https://invalid.").host; 53 } catch { 54 return null; 55 } 56}; 57 58// camo wants the target hex encoded 59const toHex = (value: string): string => 60 Array.from(new TextEncoder().encode(value), (byte) => byte.toString(16).padStart(2, "0")).join( 61 "" 62 ); 63 64export const mediaUrl = (url: string, ctx: MarkupContext): string => { 65 if (isRepoRelative(url)) return rawUrl(url, ctx); 66 if (!ctx.camo || !isAbsoluteUrl(url) || hostOf(url) === ctx.host) return url; 67 return `/camo/${toHex(url)}`; 68}; 69 70export const mediaSrcset = (srcset: string, ctx: MarkupContext): string => 71 srcset 72 .split(",") 73 .map((candidate) => { 74 const [url, ...descriptors] = candidate.trim().split(/\s+/); 75 return [mediaUrl(url, ctx), ...descriptors].join(" "); 76 }) 77 .join(", ");