forks/sorcery

git clone https://git.gae.moe/forks/sorcery

Charlotte Somfeat: give commits absolute <time> timestamps, relativized while recentc166af0

grace-patches
4.0 KiB88 linesraw
1export const utf8 = new TextDecoder();
2export const looksBinary = (data: Uint8Array) => data.subarray(0, 8192).includes(0);
3// Raster formats only: raw SVG remains inert text, not same-origin active content.
4export const isImage = (path: string) => /\.(png|jpe?g|gif|webp|avif|bmp|ico)$/i.test(path);
5
6// mirrors excluded_directory / excluded_file in src/languages.rs; keep them in sync
7const GENERATED_DIRECTORIES = new Set([
8  "node_modules", "vendor", "third_party", "third-party", "target", "dist", "build",
9  "coverage", ".next", ".nuxt", ".output", ".yarn", ".gradle", ".terraform", ".dart_tool",
10  ".direnv", ".venv", "venv", "__pycache__", "pods", "carthage", "generated",
11]);
12const GENERATED_FILES = new Set([
13  "cargo.lock", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml",
14  "bun.lock", "bun.lockb", "deno.lock", "composer.lock", "gemfile.lock", "go.sum", "poetry.lock",
15  "uv.lock", "pipfile.lock", "pdm.lock", "flake.lock", "mix.lock", "pubspec.lock",
16  "package.resolved", "packages.lock.json", "gradle.lockfile", "podfile.lock", "cartfile.resolved",
17]);
18const GENERATED_SUFFIXES = [
19  ".lock", ".min.js", ".min.css", ".g.dart", ".pb.go", ".pb.rs", ".pb.cc", ".pb.h", "_pb2.py",
20  ".designer.cs",
21];
22
23export function looksGenerated(path: string): boolean {
24  const segments = path.toLowerCase().split("/");
25  const name = segments.pop()!;
26  return segments.some(dir => GENERATED_DIRECTORIES.has(dir))
27    || GENERATED_FILES.has(name)
28    || GENERATED_SUFFIXES.some(suffix => name.endsWith(suffix))
29    || name.includes(".generated.")
30    || name.includes("_generated.");
31}
32
33/** mirrors human_size in src/render.rs */
34export function humanSize(bytes: number): string {
35  if (bytes < 1024) return `${bytes} B`;
36  if (bytes < 1 << 20) return `${(bytes / 1024).toFixed(1)} KiB`;
37  return `${(bytes / (1 << 20)).toFixed(1)} MiB`;
38}
39
40export const plural = (n: number, noun: string) => `${n} ${noun}${n === 1 ? "" : "s"}`;
41
42const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
43  ["year", 365 * 86400], ["month", 30 * 86400], ["week", 7 * 86400],
44  ["day", 86400], ["hour", 3600], ["minute", 60], ["second", 1],
45];
46const relative = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
47
48/** "3 days ago"; a clock-skewed future reads as now */
49export function timeAgo(then: Date, now = new Date()): string {
50  const seconds = Math.max(0, (now.getTime() - then.getTime()) / 1000);
51  const [unit, size] = UNITS.find(([, size]) => seconds >= size) ?? UNITS.at(-1)!;
52  return relative.format(-Math.floor(seconds / size), unit);
53}
54
55/** the identity's own timezone, like git log */
56export function identityDate(id: { time: number; tz: string }): string {
57  const offset = (Number(id.tz.slice(0, 3)) * 60 + Number(id.tz[0] + id.tz.slice(3))) * 60000;
58  const local = new Date(id.time * 1000 + offset).toISOString();
59  return `${local.slice(0, 10)} ${local.slice(11, 16)} ${id.tz}`;
60}
61
62/** beyond this, a commit's date says more than "n weeks ago" */
63const RECENT_SECONDS = 30 * 86400;
64
65/** a commit's display date: relative while recent, otherwise its absolute date */
66export function commitDate(id: { time: number; tz: string }, now = new Date()): string {
67  const then = new Date(id.time * 1000);
68  return (now.getTime() - then.getTime()) / 1000 < RECENT_SECONDS
69    ? timeAgo(then, now)
70    : identityDate(id).slice(0, 10);
71}
72
73/** a commit's `<time>`: absolute in `datetime` and `title`, relative in text while recent */
74export function commitTime(id: { time: number; tz: string }): HTMLTimeElement {
75  const time = document.createElement("time");
76  time.dateTime = new Date(id.time * 1000).toISOString();
77  time.title = identityDate(id);
78  time.textContent = commitDate(id);
79  return time;
80}
81
82/** soften a statically rendered absolute `<time>` in place, while it's recent */
83export function relativizeTime(time: HTMLTimeElement, now = new Date()): void {
84  const then = new Date(time.dateTime);
85  if (Number.isNaN(then.getTime()) || (now.getTime() - then.getTime()) / 1000 >= RECENT_SECONDS) return;
86  time.title ||= time.textContent ?? "";
87  time.textContent = timeAgo(then, now);
88}