forks/sorcery
git clone https://git.gae.moe/forks/sorcery
c166af0
typst_support
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 { 24const segments = path . toLowerCase (). split ( "/" ); 25const name = segments . pop () ! ; 26return 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 { 35if ( bytes < 1024 ) return ` ${ bytes } B` ; 36if ( bytes < 1 << 20 ) return ` ${( bytes / 1024 ). toFixed ( 1 )} KiB` ; 37return ` ${( 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 { 50const seconds = Math . max ( 0 , ( now . getTime () - then . getTime ()) / 1000 ); 51const [ unit , size ] = UNITS . find (([, size ]) => seconds >= size ) ?? UNITS . at ( - 1 ) ! ; 52return 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 { 57const offset = ( Number ( id . tz . slice ( 0 , 3 )) * 60 + Number ( id . tz [ 0 ] + id . tz . slice ( 3 ))) * 60000 ; 58const local = new Date ( id . time * 1000 + offset ). toISOString (); 59return ` ${ 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 { 67const then = new Date ( id . time * 1000 ); 68return ( 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 { 75const time = document . createElement ( "time" ); 76time . dateTime = new Date ( id . time * 1000 ). toISOString (); 77time . title = identityDate ( id ); 78time . textContent = commitDate ( id ); 79return 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 { 84const then = new Date ( time . dateTime ); 85if ( Number . isNaN ( then . getTime ()) || ( now . getTime () - then . getTime ()) / 1000 >= RECENT_SECONDS ) return ; 86time . title ||= time . textContent ?? "" ; 87time . textContent = timeAgo ( then , now ); 88}