links.ts
2.7 kB · typescript · 82 lines
1import { dirname, isAbsolute, relative, resolve as under } from "node:path";2import { config as gitConfig, dirRoute, isGit, link as gitLink, owner } from "../git/git.ts";3import { label, type Site } from "./build.ts";45/* TYPES */67export type Index = { base: string; slug: string; branch: string; map: Map<string, string>; git: Set<string> };89/* INDEX */1011export function index(site: Site): Index {12 const git = gitConfig(site);13 const roots = new Set(Object.values(site.inputs ?? {}).map((one) => one.path));14 const map = new Map<string, string>();15 const put = (file: string, route: string) => {16 if (!file) return;17 const name = label(site, file);18 map.set(file, route);19 if (name.includes("/") || roots.has(file)) map.set(name, route);20 };21 for (const route of site.routes) {22 if (isGit(route)) continue;23 if (route.source) put(route.source, route.route);24 for (const one of route.urls ?? []) if (one.source) put(one.source, one.route);25 }26 return {27 base: git?.root ?? site.root,28 slug: git?.slug ?? "",29 branch: git?.branch ?? "main",30 map,31 git: new Set(site.routes.filter(isGit).map((route) => route.route)),32 };33}3435/* STAMP */3637export const stamp = (idx: Index) =>38 JSON.stringify([39 [...idx.map].filter(([key]) => key.startsWith(idx.base)).map(([key, route]) => [key.slice(idx.base.length), route]),40 [...idx.git],41 ]);4243/* RESOLVE */4445const OUT = /^(https?:|mailto:|tel:|#|\/)/;4647const DENY = /^(?:javascript|data|vbscript):/i;4849const denied = (url: string) => DENY.test(url.replace(/[\u0000-\u0020]/g, ""));5051const keys = (path: string) => [path, `${path}.md`, path.replace(/\.md$/, "")];5253const known = (idx: Index, path: string) => {54 for (const key of keys(path)) {55 const hit = idx.map.get(key);56 if (hit) return hit;57 }58 return "";59};6061export function resolve(site: Site, from: string, url: string): string {62 const idx = site.index;63 if (denied(url)) return "#";64 if (!idx || OUT.test(url)) return url;65 const cut = url.search(/[#?]/);66 const tail = cut < 0 ? "" : url.slice(cut);67 const head = cut < 0 ? url : url.slice(0, cut);68 if (!head) return url;69 const file = under(idx.base, from);70 const target = under(dirname(file), head);71 const path = relative(idx.base, target);72 const hit = known(idx, target) || known(idx, path);73 if (hit) return hit + tail;74 if (!path || path.startsWith("..") || isAbsolute(path)) return url;75 if (idx.git.size) {76 const where = gitLink(relative(idx.base, dirname(file)), head);77 const page = where.startsWith("/raw/") ? (owner(where) ?? "") : where;78 if (idx.git.has(page)) return where + tail;79 return idx.git.has(dirRoute(path)) ? dirRoute(path) + tail : url;80 }81 return idx.slug ? `https://github.com/${idx.slug}/blob/${idx.branch}/${path}${tail}` : url;82}