char/sorcery
static-files based git repo viewer
git clone https://git.t4t.associates/char/sorcery
c166af0
main
1import type { Commit , LocatedObject } from "./git/types.ts" ; 2import { log , type PathChange , pathHistory } from "./git/walk.ts" ; 3import { paginate } from "./pagination.tsx" ; 4import { canonical , type Site , type View } from "./route.ts" ; 5import { kindAtTip , topbar } from "./tree.tsx" ; 6import { commitTime , plural } from "./util.ts" ; 7 8const TREE = 0o040000 ; 9const HISTORY_PREFETCH = 25 ; 10 11async function * commitHistory ( 12site :Site , 13start :string , 14scanned :( count :number ) => void , 15) :AsyncGenerator < PathChange > { 16for await ( const commit of log ( site . repo , [ start ], HISTORY_PREFETCH )) { 17scanned ( 1 ); 18yield { commit, object :{ oid :commit . tree , mode :TREE } }; 19} 20} 21 22const snapshotKind = ( object :LocatedObject ) :"tree" | "blob" => 23( object . mode & 0o170000 ) === TREE ?"tree" :"blob" ; 24 25export async function historyView ( site :Site , view :View ) :Promise < Node []> { 26const { oid, path} = view ; 27const commitList = path . length === 0 ; 28const atTip = await kindAtTip ( site , path ); 29const list = (< ol class = "log history-log" / >) as HTMLOListElement ; 30const status = (< p class = "meta history-status" / >) as HTMLElement ; 31const control = (< p class = "meta log-pagination" / >) as HTMLElement ; 32let scanned = 0 ; 33const scannedCommits = ( count :number ) => { 34scanned += count ; 35status . textContent = `scanned ${ plural ( scanned , "commit" )} ` ; 36}; 37paginate < PathChange >({ 38 list, 39 control, 40open :() => { 41scanned = 0 ; 42return commitList 43 ?commitHistory ( site , oid , scannedCommits ) 44 :pathHistory ( site . repo , [ oid ], path , scannedCommits ); 45}, 46key :change => change . commit . oid , 47render :change => { 48// rows of a file's history link to the file at that commit, not the commit's diff 49const target = ! commitList && change . object 50 ?canonical ( site . page , { kind :snapshotKind ( change . object ), oid :change . commit . oid , path}, atTip ) 51 :`#commit/ ${ change . commit . oid } ` ; 52const who = ( 53< span class = "who" > 54< span >{ change . commit . author . name }</ span > 55{ commitTime ( change . commit . author )} 56< / span> 57) as HTMLElement ; 58if ( ! change . object ) who . append (< span class = "deleted" > deleted < /span>); 59const item = (< li />) as HTMLLIElement ; 60if ( change . commit . changeId ) { 61item . append (< a class = "cid" href = { target }>{ change . commit . changeId . slice ( 0 , 8 )}</ a >, " " ); 62} 63item . append ( 64< a class = "sha" href = { target }>{ change . commit . oid . slice ( 0 , 7 )}</ a >, 65who , 66< span class = "msg" >{ change . commit . message . split ( "\n" )[ 0 ]}</ span >, 67); 68return item ; 69}, 70onShow : count => { 71status . replaceChildren (< span >{ plural ( count , commitList ? "commit" : "change" )}</ span >); 72}, 73onError : err => { 74status . textContent = `history failed: ${ String ( err )} ` ; 75}, 76}); 77 78return [ 79await topbar ( site , view , [ commitList ? "commit history" : "change history" ]), 80status , 81list , 82control , 83]; 84}