Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121import type { Transporter } from "@sveltejs/kit/hooks";import type { CommitSummary } from "#lib/api/repo.ts";import { isMarkdownFile } from "#lib/markup/index.ts";
export type BlobKind = "code" | "markup" | "svg" | "image" | "video" | "submodule" | "other";
export type BlobViewMode = "code" | "rendered";
export interface RepoBlobView { ref: string; path: string; kind: BlobKind; sizeLabel: string | null; lines: number | null; contents: string | null; renderedHtml: string | null; fileTooLarge: boolean; defaultView: BlobViewMode; submodule: { name: string; url: string } | null; lastCommit: CommitSummary | null; // only set when contents is null. the browser checks its read-back against it contentsCheck?: string;}
// not crypto, a collision only buys the text that is already on screenexport const textCheck = (text: string): string => { let h1 = 0xdeadbeef; let h2 = 0x41c6ce57; for (let i = 0; i < text.length; i++) { const c = text.charCodeAt(i); h1 = Math.imul(h1 ^ c, 2654435761); h2 = Math.imul(h2 ^ c, 1597334677); } h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); return `${text.length}:${(4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36)}`;};
const inPage = new WeakSet<RepoBlobView>();
// text with \r or NUL never survives pierre's markupexport const renderedInPage = (blob: RepoBlobView): RepoBlobView => { if (blob.defaultView === "code" && blob.contents !== null && !/[\r\0]/.test(blob.contents)) inPage.add(blob); return blob;};
export const blobTransport: Transporter<RepoBlobView, [RepoBlobView]> = { encode: (blob) => inPage.has(blob) && blob.contents !== null && [ { ...blob, contents: null, contentsCheck: textCheck(blob.contents) } ], decode: ([blob]) => blob};
// same 1MiB inline cap as the appview's maxBlobSizeexport const MAX_INLINE_SIZE = 1 << 20;
// go-humanize's Bytesexport const formatBytes = (bytes: number): string => { if (bytes < 1000) return `${bytes} B`; const units = ["kB", "MB", "GB", "TB", "PB", "EB"]; let value = bytes; let unit = "B"; for (const next of units) { if (value < 1000) break; value /= 1000; unit = next; } let rendered = value < 10 ? value.toFixed(1) : String(Math.round(value)); // rounding can spill over the boundary ("1000 kB"), promote to the // next unit and render again ("1.0 MB") if (Number.parseFloat(rendered) >= 1000 && unit !== "EB") { value /= 1000; unit = units[units.indexOf(unit) + 1]; rendered = value < 10 ? value.toFixed(1) : String(Math.round(value)); } return `${rendered} ${unit}`;};
// textual application/* types (json, toml, ...) arrive with isBinary=false// instead of a text/* mimeexport const classifyBlob = (blob: { path: string; mimeType?: string; isBinary?: boolean; submodule?: unknown;}): BlobKind => { if (blob.submodule) return "submodule"; const mediaType = (blob.mimeType ?? "").split(";")[0].trim().toLowerCase(); if (mediaType === "image/svg+xml") return "svg"; if (mediaType.startsWith("image/")) return "image"; if (mediaType.startsWith("video/")) return "video"; if (blob.isBinary === false || mediaType.startsWith("text/")) { return isMarkdownFile(blob.path) ? "markup" : "code"; } return "other";};
export const hasTextView = (kind: BlobKind): boolean => kind === "code" || kind === "markup" || kind === "svg";
export const hasRenderedView = (kind: BlobKind): boolean => kind === "markup" || kind === "svg";
// a trailing newline terminates the last line instead of starting a new oneexport const countLines = (text: string): number => { if (text === "") return 0; const lines = text.split("\n").length; return text.endsWith("\n") ? lines - 1 : lines;};
const textEncoder = new TextEncoder();
export const contentStats = ( contents: string): { lines: number; bytes: number; sizeLabel: string } => { const bytes = textEncoder.encode(contents).length; return { lines: countLines(contents), bytes, sizeLabel: formatBytes(bytes) };};