<?php require_once __DIR__ . '/FileRenderer.php'; require_once __DIR__ . '/Highlighter.php'; require_once __DIR__ . '/../UrlBuilder.php'; class HtmlFileRenderer implements FileRenderer { private string $repoSafeName; private string $currentPath; private string $currentRef; public function __construct( string $repoSafeName, string $currentPath = '', string $currentRef = 'HEAD' ) { $this->repoSafeName = $repoSafeName; $this->currentPath = trim( $currentPath, '/' ); $this->currentRef = $currentRef; } public function renderListEntry( string $name, string $sha, string $mode, string $iconClass, int $timestamp, int $size ): void { $fullPath = ($this->currentPath === '' ? '' : $this->currentPath . '/') . $name; $isDir = ($mode === '40000' || $mode === '040000'); $action = $isDir ? 'tree' : 'blob'; $url = (new UrlBuilder()) ->withRepo( $this->repoSafeName ) ->withAction( $action ) ->withHash( $this->currentRef ) ->withName( $fullPath ) ->build(); echo '<tr>'; echo '<td class="file-icon-cell"><i class="fas ' . $iconClass . '"></i></td>'; echo '<td class="file-name-cell"><a href="' . $url . '">' . htmlspecialchars( $name ) . '</a></td>'; echo '<td class="file-mode-cell">' . $this->formatMode( $mode ) . '</td>'; echo '<td class="file-size-cell">' . ($size > 0 ? $this->formatSize( $size ) : '') . '</td>'; echo '</tr>'; } public function renderMedia( File $file, string $url, string $mediaType ): bool { if( $file->isImage() ) { echo '<div class="blob-content blob-content-image"><img src="' . $url . '"></div>'; return true; } elseif( $file->isVideo() ) { echo '<div class="blob-content blob-content-video"><video controls><source src="' . $url . '" type="' . $mediaType . '"></video></div>'; return true; } elseif( $file->isAudio() ) { echo '<div class="blob-content blob-content-audio"><audio controls><source src="' . $url . '" type="' . $mediaType . '"></audio></div>'; return true; } return false; } public function renderSize( int $bytes ): void { echo $this->formatSize( $bytes ); } public function highlight( string $filename, string $content, string $mediaType ): string { return (new Highlighter($filename, $content, $mediaType))->render(); } public function renderTime( int $timestamp ): void { $tokens = [ 31536000 => 'year', 2592000 => 'month', 604800 => 'week', 86400 => 'day', 3600 => 'hour', 60 => 'minute', 1 => 'second' ]; $diff = $timestamp ? time() - $timestamp : null; $result = 'never'; if( $diff && $diff >= 5 ) { foreach( $tokens as $unit => $text ) { if( $diff < $unit ) continue; $num = floor( $diff / $unit ); $result = $num . ' ' . $text . ($num > 1 ? 's' : '') . ' ago'; break; } } elseif( $diff ) { $result = 'just now'; } echo $result; } private function formatSize( int $bytes ): string { $units = [ 'B', 'KB', 'MB', 'GB', 'TB' ]; $i = 0; while( $bytes >= 1024 && $i < count( $units ) - 1 ) { $bytes /= 1024; $i++; } return ($bytes === 0 ? 0 : round( $bytes )) . ' ' . $units[$i]; } private function formatMode( string $mode ): string { switch( $mode ) { case '100644': return 'w'; case '100755': return 'x'; case '040000': return 'd'; case '120000': return 'l'; case '160000': return 'm'; default: return '?'; } } }