| Author | Dave Jarvis <email> |
|---|---|
| Date | 2026-03-01 23:56:29 GMT-0800 |
| Commit | be19c99ee25c6912d0c8d299bc87ff6651597722 |
| Parent | 09cc2d6 |
| <?php | ||
| require_once __DIR__ . '/Page.php'; | ||
| +require_once __DIR__ . '/Template.php'; | ||
| require_once __DIR__ . '/../model/UrlBuilder.php'; | ||
| $this->title = $title; | ||
| } | ||
| - | ||
| - protected function renderLayout( | ||
| - $contentCallback, | ||
| - array $currentRepo = [] | ||
| - ) { | ||
| - $siteTitle = Config::SITE_TITLE; | ||
| - $pageTitle = $this->title === '' | ||
| - ? '' | ||
| - : ' - ' . htmlspecialchars( $this->title ); | ||
| - | ||
| - ?> | ||
| -<!DOCTYPE html> | ||
| -<html lang="en"> | ||
| -<head> | ||
| - <meta charset="UTF-8"> | ||
| - <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| - <title><?php echo $siteTitle . $pageTitle; ?></title> | ||
| - <link rel="stylesheet" | ||
| - href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css"> | ||
| - <link rel="stylesheet" href="/styles/repo.css"> | ||
| -</head> | ||
| -<body> | ||
| -<div class="container"> | ||
| - <header> | ||
| - <h1><?php echo Config::SITE_TITLE; ?></h1> | ||
| - | ||
| - <?php if( $currentRepo ) { ?> | ||
| - <input type="checkbox" id="clone-toggle" class="clone-checkbox"> | ||
| - <?php } ?> | ||
| - | ||
| - <nav class="nav"> | ||
| - <?php if( $currentRepo ) { ?> | ||
| - <a href="<?php echo (new UrlBuilder())->build(); ?>">Home</a> | ||
| - <?php $safeName = $currentRepo['safe_name']; ?> | ||
| - <a href="<?php echo (new UrlBuilder()) | ||
| - ->withRepo( $safeName ) | ||
| - ->withAction( 'tree' ) | ||
| - ->build(); ?>">Files</a> | ||
| - <a href="<?php echo (new UrlBuilder()) | ||
| - ->withRepo( $safeName ) | ||
| - ->withAction( 'commits' ) | ||
| - ->build(); ?>">Commits</a> | ||
| - <a href="<?php echo (new UrlBuilder()) | ||
| - ->withRepo( $safeName ) | ||
| - ->withAction( 'tags' ) | ||
| - ->build(); ?>">Tags</a> | ||
| - | ||
| - <label for="clone-toggle" class="clone-link">Clone</label> | ||
| - <?php } ?> | ||
| - | ||
| - <?php if( $currentRepo ) { ?> | ||
| - <div class="repo-selector"> | ||
| - <label>Repository:</label> | ||
| - <select onchange="<?php echo (new UrlBuilder()) | ||
| - ->withSwitcher( 'this.value' ) | ||
| - ->build(); ?>"> | ||
| - <?php foreach( $this->repositories as $r ) { ?> | ||
| - <option | ||
| - value="<?php echo htmlspecialchars( $r['safe_name'] ); ?>" | ||
| - <?php | ||
| - echo $r['safe_name'] === $currentRepo['safe_name'] | ||
| - ? 'selected' | ||
| - : ''; | ||
| - ?>> | ||
| - <?php echo htmlspecialchars( $r['name'] ); ?> | ||
| - </option> | ||
| - <?php } ?> | ||
| - </select> | ||
| - </div> | ||
| - <?php } ?> | ||
| - </nav> | ||
| - <?php if( $currentRepo ) { ?> | ||
| - <div class="clone-region"> | ||
| - <div class="clone-wrapper"> | ||
| - <?php | ||
| - $cloneCmd = 'git clone https://repo.autonoma.ca/repo/' . | ||
| - $currentRepo['safe_name'] . '.git'; | ||
| - ?> | ||
| - <span class="clone-sizer"><?php echo htmlspecialchars( $cloneCmd ); ?></span> | ||
| - <input type="text" class="clone-input" readonly | ||
| - value="<?php echo htmlspecialchars( $cloneCmd ); ?>"> | ||
| - </div> | ||
| - </div> | ||
| - <?php } ?> | ||
| - </header> | ||
| + protected function getBaseTemplateVars( array $currentRepo = [] ): array { | ||
| + $suffix = $this->title === '' ? '' : ' - ' . \htmlspecialchars( $this->title ); | ||
| + $vars = [ | ||
| + 'site_title' => Config::SITE_TITLE, | ||
| + 'page_title' => $suffix, | ||
| + 'has_repo' => !empty( $currentRepo ), | ||
| + 'repositories' => $this->repositories, | ||
| + 'current_repo' => $currentRepo, | ||
| + 'url' => [ | ||
| + 'home' => (new UrlBuilder())->build() | ||
| + ] | ||
| + ]; | ||
| - <?php call_user_func( $contentCallback ); ?> | ||
| + if( !empty( $currentRepo ) ) { | ||
| + $safeName = $currentRepo['safe_name']; | ||
| + $vars['url']['files'] = (new UrlBuilder())->withRepo( $safeName )->withAction( 'tree' )->build(); | ||
| + $vars['url']['commits'] = (new UrlBuilder())->withRepo( $safeName )->withAction( 'commits' )->build(); | ||
| + $vars['url']['tags'] = (new UrlBuilder())->withRepo( $safeName )->withAction( 'tags' )->build(); | ||
| + $vars['url']['switcher'] = (new UrlBuilder())->withSwitcher( 'this.value' )->build(); | ||
| + $vars['clone_cmd'] = 'git clone ' . (new UrlBuilder())->withRepo( $safeName . '.git' )->build(); | ||
| + } | ||
| -</div> | ||
| -</body> | ||
| -</html> | ||
| - <?php | ||
| + return $vars; | ||
| } | ||
| - | ||
| - protected function renderBreadcrumbs( array $repo, array $trail = [] ) { | ||
| - $repoUrl = (new UrlBuilder())->withRepo( $repo['safe_name'] )->build(); | ||
| - $parts = array_merge( | ||
| + protected function buildBreadcrumbs( array $repo, array $trail = [] ): array { | ||
| + $parts = [ | ||
| [ | ||
| - '<a href="' . $repoUrl . '" class="repo-breadcrumb">' . | ||
| - htmlspecialchars( $repo['name'] ) . '</a>' | ||
| - ], | ||
| - $trail | ||
| - ); | ||
| + 'url' => (new UrlBuilder())->withRepo( $repo['safe_name'] )->build(), | ||
| + 'name' => $repo['name'] | ||
| + ] | ||
| + ]; | ||
| - echo '<div class="breadcrumb">' . implode( ' / ', $parts ) . '</div>'; | ||
| + return \array_merge( $parts, $trail ); | ||
| } | ||
| } | ||
| echo "Not Found"; | ||
| } | ||
| - | ||
| - exit; | ||
| } | ||
| private const PER_PAGE = 100; | ||
| - private array $currentRepo; | ||
| - private Git $git; | ||
| - private string $hash; | ||
| + private $currentRepo; | ||
| + private $git; | ||
| + private $hash; | ||
| public function __construct( | ||
| public function render(): void { | ||
| - $this->renderLayout( function() { | ||
| - $main = $this->git->getMainBranch(); | ||
| - | ||
| - if( !$main ) { | ||
| - echo '<div class="empty-state"><h3>No branches</h3>' . | ||
| - '<p>Empty repository.</p></div>'; | ||
| - } else { | ||
| - $this->renderBreadcrumbs( $this->currentRepo, ['Commits'] ); | ||
| - | ||
| - echo '<h2>Commit History <span class="branch-badge">' . | ||
| - htmlspecialchars( $main['name'] ) . '</span></h2>'; | ||
| - echo '<div class="commit-list">'; | ||
| - | ||
| - $start = $this->hash !== '' ? $this->hash : $main['hash']; | ||
| - $commits = []; | ||
| + $main = $this->git->getMainBranch(); | ||
| + $tpl = new Template( 'commits.tpl' ); | ||
| - $this->git->history( | ||
| - $start, | ||
| - self::PER_PAGE, | ||
| - function( Commit $commit ) use( &$commits ) { | ||
| - $commits[] = $commit; | ||
| - } | ||
| - ); | ||
| + foreach( $this->getBaseTemplateVars( $this->currentRepo ) as $k => $v ) { | ||
| + $tpl->assign( $k, $v ); | ||
| + } | ||
| - $nav = $this->buildPagination( $main['hash'], count( $commits ) ); | ||
| + if( !$main ) { | ||
| + $tpl->assign( 'has_branches', false ); | ||
| + } else { | ||
| + $trail = [ | ||
| + [ | ||
| + 'url' => '', | ||
| + 'name' => 'Commits' | ||
| + ] | ||
| + ]; | ||
| - $this->renderPagination( $nav ); | ||
| + $tpl->assign( 'has_branches', true ); | ||
| + $tpl->assign( 'main_name', $main['name'] ); | ||
| + $tpl->assign( 'breadcrumbs', $this->buildBreadcrumbs( $this->currentRepo, $trail ) ); | ||
| - $renderer = new HtmlCommitRenderer( | ||
| - $this->currentRepo['safe_name'] | ||
| - ); | ||
| + $start = $this->hash !== '' ? $this->hash : $main['hash']; | ||
| + $commits = []; | ||
| - foreach( $commits as $commit ) { | ||
| - $commit->render( $renderer ); | ||
| + $this->git->history( | ||
| + $start, | ||
| + self::PER_PAGE, | ||
| + function( Commit $commit ) use( &$commits ) { | ||
| + $commits[] = $commit; | ||
| } | ||
| - | ||
| - echo '</div>'; | ||
| - $this->renderPagination( $nav ); | ||
| - } | ||
| - }, $this->currentRepo ); | ||
| - } | ||
| - | ||
| - private function renderPagination( array $nav ): void { | ||
| - $pages = $nav['pages']; | ||
| - $current = $nav['current']; | ||
| - $hasNext = $nav['hasNext']; | ||
| - $hasPrev = $current > 1; | ||
| - $total = count( $pages ); | ||
| - | ||
| - if( $hasPrev || $hasNext ) { | ||
| - echo '<div class="pagination">'; | ||
| + ); | ||
| - if( $hasPrev ) { | ||
| - echo '<a href="' . $this->pageUrl( $pages[0] ) . | ||
| - '" class="page-link page-nav" aria-label="first">' . | ||
| - $this->svgArrow( 'first' ) . '</a>'; | ||
| + $nav = $this->buildPagination( $main['hash'], \count( $commits ) ); | ||
| + $commitsHtml = ''; | ||
| + $renderer = new HtmlCommitRenderer( $this->currentRepo['safe_name'] ); | ||
| - echo '<a href="' . $this->pageUrl( $pages[$current - 2] ) . | ||
| - '" class="page-link page-nav" aria-label="back">' . | ||
| - $this->svgArrow( 'back' ) . '</a>'; | ||
| - } else { | ||
| - echo '<span class="page-link page-nav page-nav-hidden" ' . | ||
| - 'aria-hidden="true">' . $this->svgArrow( 'first' ) . | ||
| - '</span>'; | ||
| + \ob_start(); | ||
| - echo '<span class="page-link page-nav page-nav-hidden" ' . | ||
| - 'aria-hidden="true">' . $this->svgArrow( 'back' ) . | ||
| - '</span>'; | ||
| + foreach( $commits as $commit ) { | ||
| + $commit->render( $renderer ); | ||
| } | ||
| - $this->renderPageNumbers( $pages, $current ); | ||
| + $commitsHtml = \ob_get_clean(); | ||
| - if( $hasNext ) { | ||
| - echo '<a href="' . $this->pageUrl( $pages[$current] ) . | ||
| - '" class="page-link page-nav" aria-label="next">' . | ||
| - $this->svgArrow( 'next' ) . '</a>'; | ||
| + $tpl->assign( 'commits_html', $commitsHtml ); | ||
| - echo '<a href="' . $this->pageUrl( $pages[$total - 1] ) . | ||
| - '" class="page-link page-nav" aria-label="last">' . | ||
| - $this->svgArrow( 'last' ) . '</a>'; | ||
| - } else { | ||
| - echo '<span class="page-link page-nav page-nav-hidden" ' . | ||
| - 'aria-hidden="true">' . $this->svgArrow( 'next' ) . | ||
| - '</span>'; | ||
| + $pagesData = []; | ||
| - echo '<span class="page-link page-nav page-nav-hidden" ' . | ||
| - 'aria-hidden="true">' . $this->svgArrow( 'last' ) . | ||
| - '</span>'; | ||
| + foreach( $nav['pages'] as $idx => $c ) { | ||
| + $pagesData[] = [ | ||
| + 'url' => $this->pageUrl( $c ), | ||
| + 'num' => $idx + 1 | ||
| + ]; | ||
| } | ||
| - | ||
| - echo '</div>'; | ||
| - } | ||
| - } | ||
| - | ||
| - private function svgArrow( string $type ): string { | ||
| - $icons = [ | ||
| - 'back' => '<path d="M14 17 L9 12 L14 7" />', | ||
| - 'next' => '<path d="M10 17 L15 12 L10 7" />', | ||
| - 'first' => '<path d="M13 17 L6 12 L13 7 M19 17 L12 12 L19 7" />', | ||
| - 'last' => '<path d="M11 17 L18 12 L11 7 M5 17 L12 12 L5 7" />', | ||
| - ]; | ||
| - | ||
| - $inner = $icons[$type] ?? ''; | ||
| - | ||
| - return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" ' . | ||
| - 'fill="none" stroke="currentColor" stroke-width="2" ' . | ||
| - 'stroke-linecap="round" stroke-linejoin="round" ' . | ||
| - 'aria-label="' . $type . '" role="img">' . | ||
| - '<title>' . $type . '</title>' . $inner . '</svg>'; | ||
| - } | ||
| - private function renderPageNumbers( array $pages, int $current ): void { | ||
| - $total = count( $pages ); | ||
| - $start = max( 1, $current - 4 ); | ||
| - $end = min( $total, $start + 9 ); | ||
| - $actual = 1; | ||
| + $tpl->assign( 'nav', [ | ||
| + 'has_prev' => $nav['current'] > 1, | ||
| + 'has_next' => $nav['hasNext'], | ||
| + 'current' => $nav['current'], | ||
| + 'total' => \count( $nav['pages'] ), | ||
| + 'pages' => $pagesData, | ||
| + 'first_url' => $this->pageUrl( $nav['pages'][0] ), | ||
| + 'back_url' => $nav['current'] > 1 ? $this->pageUrl( $nav['pages'][$nav['current'] - 2] ) : '', | ||
| + 'next_url' => $nav['hasNext'] ? $this->pageUrl( $nav['pages'][$nav['current']] ) : '', | ||
| + 'last_url' => $nav['hasNext'] ? $this->pageUrl( $nav['pages'][\count( $nav['pages'] ) - 1] ) : '' | ||
| + ] ); | ||
| - if( $end === $total ) { | ||
| - $start = max( 1, $end - 9 ); | ||
| - } | ||
| + $total = \count( $nav['pages'] ); | ||
| + $current = $nav['current']; | ||
| + $start = \max( 1, $current - 4 ); | ||
| + $end = \min( $total, $start + 9 ); | ||
| - while( $actual <= $total ) { | ||
| - if( $actual >= $start && $actual <= $end ) { | ||
| - if( $actual === $current ) { | ||
| - echo '<span class="page-badge">' . $actual . '</span>'; | ||
| - } else { | ||
| - echo '<a href="' . $this->pageUrl( $pages[$actual - 1] ) . | ||
| - '" class="page-link">' . $actual . '</a>'; | ||
| - } | ||
| + if( $end === $total ) { | ||
| + $start = \max( 1, $end - 9 ); | ||
| } | ||
| - $actual++; | ||
| + $tpl->assign( 'nav_start', $start ); | ||
| + $tpl->assign( 'nav_end', $end ); | ||
| } | ||
| + | ||
| + echo $tpl->render( 'commits.tpl' ); | ||
| } | ||
| if( $commit->isSha( $target ) ) { | ||
| - $currentPage = count( $pageCommits ); | ||
| + $currentPage = \count( $pageCommits ); | ||
| $found = true; | ||
| } | ||
| - if( $found && count( $pageCommits ) > $currentPage + 10 ) { | ||
| + if( $found && \count( $pageCommits ) > $currentPage + 10 ) { | ||
| $hitLimit = true; | ||
| $continue = false; | ||
| 'current' => $currentPage, | ||
| 'hasAll' => !$hitLimit, | ||
| - 'hasNext' => $count === self::PER_PAGE && | ||
| - isset( $pageCommits[$currentPage] ) | ||
| + 'hasNext' => $count === self::PER_PAGE && isset( $pageCommits[$currentPage] ) | ||
| ]; | ||
| } | ||
| string $oldSha | ||
| ) { | ||
| - $title = $currentRepo['name'] . ' - Compare'; | ||
| - parent::__construct( $repositories, $title ); | ||
| + parent::__construct( $repositories, $currentRepo['name'] . ' - Compare' ); | ||
| $this->currentRepo = $currentRepo; | ||
| $this->git = $git; | ||
| $this->newSha = $newSha; | ||
| $this->oldSha = $oldSha; | ||
| - } | ||
| - | ||
| - public function render() { | ||
| - $this->renderLayout( function() { | ||
| - $shortNew = substr( $this->newSha, 0, 7 ); | ||
| - $shortOld = substr( $this->oldSha, 0, 7 ); | ||
| - | ||
| - $this->renderBreadcrumbs( | ||
| - $this->currentRepo, | ||
| - ["Compare $shortNew...$shortOld"] | ||
| - ); | ||
| - | ||
| - $differ = new GitDiff( $this->git ); | ||
| - $changes = $differ->diff( $this->oldSha, $this->newSha ); | ||
| - | ||
| - if( empty( $changes ) ) { | ||
| - echo '<div class="empty-state"><h3>No changes</h3>' . | ||
| - '<p>No differences.</p></div>'; | ||
| - } else { | ||
| - foreach( $changes as $change ) { | ||
| - $this->renderDiffFile( $change ); | ||
| - } | ||
| - } | ||
| - }, $this->currentRepo ); | ||
| } | ||
| - private function renderDiffFile( array $change ) { | ||
| - $typeMap = [ | ||
| - 'A' => 'added', | ||
| - 'D' => 'deleted', | ||
| - 'M' => 'modified' | ||
| + public function render(): void { | ||
| + $shortNew = \substr( $this->newSha, 0, 7 ); | ||
| + $shortOld = \substr( $this->oldSha, 0, 7 ); | ||
| + $trail = [ | ||
| + [ | ||
| + 'url' => '', | ||
| + 'name' => "Compare $shortNew...$shortOld" | ||
| + ] | ||
| ]; | ||
| - | ||
| - $statusClass = $typeMap[$change['type']] ?? 'modified'; | ||
| - $path = htmlspecialchars( $change['path'] ); | ||
| - | ||
| - echo '<div class="diff-file">'; | ||
| - echo '<div class="diff-header ' . $statusClass . '">'; | ||
| - echo '<span class="diff-status-icon">' . $change['type'] . '</span> '; | ||
| - echo $path; | ||
| - echo '</div>'; | ||
| - | ||
| - if( $change['is_binary'] ) { | ||
| - echo '<div class="diff-content binary">Binary file</div>'; | ||
| - } elseif( !empty( $change['hunks'] ) ) { | ||
| - echo '<div class="diff-content">'; | ||
| - echo '<table class="diff-table"><tbody>'; | ||
| - | ||
| - foreach( $change['hunks'] as $line ) { | ||
| - $this->renderDiffLine( $line ); | ||
| - } | ||
| + $tpl = new Template( 'compare.tpl' ); | ||
| - echo '</tbody></table>'; | ||
| - echo '</div>'; | ||
| + foreach( $this->getBaseTemplateVars( $this->currentRepo ) as $k => $v ) { | ||
| + $tpl->assign( $k, $v ); | ||
| } | ||
| - echo '</div>'; | ||
| - } | ||
| + $tpl->assign( 'breadcrumbs', $this->buildBreadcrumbs( $this->currentRepo, $trail ) ); | ||
| - private function renderDiffLine( array $line ) { | ||
| - if( $line['t'] === 'gap' ) { | ||
| - echo '<tr class="diff-gap"><td colspan="3">...</td></tr>'; | ||
| - } else { | ||
| - $class = match( $line['t'] ) { | ||
| - '+' => 'diff-add', | ||
| - '-' => 'diff-del', | ||
| - default => '' | ||
| - }; | ||
| + $differ = new GitDiff( $this->git ); | ||
| + $changes = $differ->diff( $this->oldSha, $this->newSha ); | ||
| - echo '<tr class="' . $class . '">'; | ||
| - echo '<td class="diff-line-num">' . $line['no'] . '</td>'; | ||
| - echo '<td class="diff-line-num">' . $line['nn'] . '</td>'; | ||
| - echo '<td class="diff-code"><pre>' . | ||
| - htmlspecialchars( $line['l'] ) . '</pre></td>'; | ||
| - echo '</tr>'; | ||
| - } | ||
| + $tpl->assign( 'empty_changes', empty( $changes ) ); | ||
| + $tpl->assign( 'changes', $changes ); | ||
| + | ||
| + echo $tpl->render( 'compare.tpl' ); | ||
| } | ||
| } |
| string $hash | ||
| ) { | ||
| - parent::__construct( $repositories, substr( $hash, 0, 7 ) ); | ||
| + parent::__construct( $repositories, \substr( $hash, 0, 7 ) ); | ||
| $this->currentRepo = $currentRepo; | ||
| $this->git = $git; | ||
| $this->hash = $hash; | ||
| } | ||
| - | ||
| - public function render() { | ||
| - $this->renderLayout( function() { | ||
| - $commitData = $this->git->read( $this->hash ); | ||
| - $diffEngine = new GitDiff( $this->git ); | ||
| - $lines = explode( "\n", $commitData ); | ||
| - $msg = ''; | ||
| - $isMsg = false; | ||
| - $headers = []; | ||
| - | ||
| - foreach( $lines as $line ) { | ||
| - if( $line === '' ) { | ||
| - $isMsg = true; | ||
| - } elseif( $isMsg ) { | ||
| - $msg .= $line . "\n"; | ||
| - } else { | ||
| - if( preg_match( '/^(\w+) (.*)$/', $line, $m ) ) { | ||
| - $headers[$m[1]] = $m[2]; | ||
| - } | ||
| - } | ||
| - } | ||
| - | ||
| - $commitsUrl = (new UrlBuilder()) | ||
| - ->withRepo( $this->currentRepo['safe_name'] ) | ||
| - ->withAction( 'commits' ) | ||
| - ->build(); | ||
| - | ||
| - $this->renderBreadcrumbs( $this->currentRepo, [ | ||
| - '<a href="' . $commitsUrl . '">Commits</a>', | ||
| - substr( $this->hash, 0, 7 ) | ||
| - ] ); | ||
| - | ||
| - echo '<div style="display:flex;flex-direction:column">'; | ||
| - | ||
| - $this->renderCommitHeader( $headers, $msg ); | ||
| - | ||
| - echo '<div class="diff-container" style="order:3">'; | ||
| - | ||
| - $added = 0; | ||
| - $deleted = 0; | ||
| - $empty = true; | ||
| - foreach( $diffEngine->compare( $this->hash ) as $change ) { | ||
| - $empty = false; | ||
| + public function render(): void { | ||
| + $commitData = $this->git->read( $this->hash ); | ||
| + $diffEngine = new GitDiff( $this->git ); | ||
| + $lines = \explode( "\n", $commitData ); | ||
| + $msg = ''; | ||
| + $isMsg = false; | ||
| + $headers = []; | ||
| - foreach( $change['hunks'] as $hunkLine ) { | ||
| - if( $hunkLine['t'] === '+' ) { | ||
| - $added++; | ||
| - } elseif( $hunkLine['t'] === '-' ) { | ||
| - $deleted++; | ||
| - } | ||
| + foreach( $lines as $line ) { | ||
| + if( $line === '' ) { | ||
| + $isMsg = true; | ||
| + } elseif( $isMsg ) { | ||
| + $msg .= $line . "\n"; | ||
| + } else { | ||
| + if( \preg_match( '/^(\w+) (.*)$/', $line, $m ) ) { | ||
| + $headers[$m[1]] = $m[2]; | ||
| } | ||
| - | ||
| - $this->renderFileDiff( $change ); | ||
| - } | ||
| - | ||
| - if( $empty ) { | ||
| - echo '<div class="empty-state">' | ||
| - . '<p>No changes detected.</p></div>'; | ||
| } | ||
| + } | ||
| - echo '</div>'; | ||
| + $commitsUrl = (new UrlBuilder())->withRepo( $this->currentRepo['safe_name'] )->withAction( 'commits' )->build(); | ||
| + $trail = [ | ||
| + [ | ||
| + 'url' => $commitsUrl, | ||
| + 'name' => 'Commits' | ||
| + ], | ||
| + [ | ||
| + 'url' => '', | ||
| + 'name' => \substr( $this->hash, 0, 7 ) | ||
| + ] | ||
| + ]; | ||
| + $tpl = new Template( 'diff.tpl' ); | ||
| - $this->renderDelta( $added, $deleted ); | ||
| + foreach( $this->getBaseTemplateVars( $this->currentRepo ) as $k => $v ) { | ||
| + $tpl->assign( $k, $v ); | ||
| + } | ||
| - echo '</div>'; | ||
| - }, $this->currentRepo ); | ||
| - } | ||
| + $tpl->assign( 'breadcrumbs', $this->buildBreadcrumbs( $this->currentRepo, $trail ) ); | ||
| + $tpl->assign( 'hash', $this->hash ); | ||
| + $tpl->assign( 'msg', \trim( $msg ) ); | ||
| - private function renderCommitHeader( | ||
| - array $headers, | ||
| - string $msg | ||
| - ) { | ||
| $authorRaw = $headers['author'] ?? 'Unknown'; | ||
| - $authorName = preg_replace( | ||
| - '/<[^>]+>/', '<email>', $authorRaw | ||
| - ); | ||
| - $authorName = htmlspecialchars( $authorName ); | ||
| + $authorName = \preg_replace( '/<[^>]+>/', '<email>', $authorRaw ); | ||
| $commitDate = ''; | ||
| - if( preg_match( | ||
| - '/^(.*?) <.*?> (\d+) ([-+]\d{4})$/', | ||
| - $authorRaw, | ||
| - $m | ||
| - ) ) { | ||
| - $authorName = htmlspecialchars( $m[1] ) | ||
| - . ' <email>'; | ||
| + if( \preg_match( '/^(.*?) <.*?> (\d+) ([-+]\d{4})$/', $authorRaw, $m ) ) { | ||
| + $authorName = $m[1] . ' <email>'; | ||
| $timestamp = (int)$m[2]; | ||
| - $pattern = '/([-+])(\d{2})(\d{2})/'; | ||
| - $tzString = preg_replace( | ||
| - $pattern, '$1$2:$3', $m[3] | ||
| - ); | ||
| + $tzString = \preg_replace( '/([-+])(\d{2})(\d{2})/', '$1$2:$3', $m[3] ); | ||
| $dt = new DateTime( '@' . $timestamp ); | ||
| $dt->setTimezone( new DateTimeZone( $tzString ) ); | ||
| $commitDate = $dt->format( 'Y-m-d H:i:s \G\M\TO' ); | ||
| } | ||
| - | ||
| - echo '<div class="commit-details" style="order:1">'; | ||
| - echo '<div class="commit-header">'; | ||
| - echo '<h1 class="commit-title">' | ||
| - . htmlspecialchars( trim( $msg ) ) . '</h1>'; | ||
| - | ||
| - echo '<table class="commit-info-table"><tbody>'; | ||
| - | ||
| - echo '<tr>' | ||
| - . '<th class="commit-info-label">Author</th>' | ||
| - . '<td class="commit-info-value">' | ||
| - . $authorName . '</td></tr>'; | ||
| - if( $commitDate !== '' ) { | ||
| - echo '<tr>' | ||
| - . '<th class="commit-info-label">Date</th>' | ||
| - . '<td class="commit-info-value">' | ||
| - . $commitDate . '</td></tr>'; | ||
| - } | ||
| + $tpl->assign( 'author', $authorName ); | ||
| + $tpl->assign( 'date', $commitDate ); | ||
| - echo '<tr>' | ||
| - . '<th class="commit-info-label">Commit</th>' | ||
| - . '<td class="commit-info-value">' | ||
| - . $this->hash . '</td></tr>'; | ||
| + $hasParent = isset( $headers['parent'] ); | ||
| + $parentUrl = ''; | ||
| + $parentSha = ''; | ||
| - if( isset( $headers['parent'] ) ) { | ||
| - $url = (new UrlBuilder()) | ||
| + if( $hasParent ) { | ||
| + $parentSha = \substr( $headers['parent'], 0, 7 ); | ||
| + $parentUrl = (new UrlBuilder()) | ||
| ->withRepo( $this->currentRepo['safe_name'] ) | ||
| ->withAction( 'commit' ) | ||
| ->withHash( $headers['parent'] ) | ||
| ->build(); | ||
| - | ||
| - echo '<tr>' | ||
| - . '<th class="commit-info-label">Parent</th>' | ||
| - . '<td class="commit-info-value">'; | ||
| - echo '<a href="' . $url . '" class="parent-link">' | ||
| - . substr( $headers['parent'], 0, 7 ) . '</a>'; | ||
| - echo '</td></tr>'; | ||
| } | ||
| - echo '</tbody></table></div></div>'; | ||
| - } | ||
| + $tpl->assign( 'has_parent', $hasParent ); | ||
| + $tpl->assign( 'parent_url', $parentUrl ); | ||
| + $tpl->assign( 'parent_sha', $parentSha ); | ||
| - private function renderDelta( int $added, int $deleted ) { | ||
| - $pluralize = function( int $count ): string { | ||
| - return $count . ' line' . ($count !== 1 ? 's' : ''); | ||
| - }; | ||
| + $added = 0; | ||
| + $deleted = 0; | ||
| + $changes = []; | ||
| - $diffNet = $added - $deleted; | ||
| - $deltaMsg = $pluralize( $added ) . ' added, ' | ||
| - . $pluralize( $deleted ) . ' removed'; | ||
| + foreach( $diffEngine->compare( $this->hash ) as $change ) { | ||
| + foreach( $change['hunks'] as $hunkLine ) { | ||
| + if( $hunkLine['t'] === '+' ) { | ||
| + $added++; | ||
| + } elseif( $hunkLine['t'] === '-' ) { | ||
| + $deleted++; | ||
| + } | ||
| + } | ||
| - if( $diffNet !== 0 ) { | ||
| - $direction = $diffNet > 0 ? 'increase' : 'decrease'; | ||
| - $deltaMsg .= ', ' . abs( $diffNet ) | ||
| - . "-line $direction"; | ||
| + $changes[] = $change; | ||
| } | ||
| - | ||
| - echo '<div class="diff-delta" style="order:2">'; | ||
| - | ||
| - echo '<table class="commit-info-table"><tbody>'; | ||
| - echo '<tr>' | ||
| - . '<th class="commit-info-label">Delta</th>' | ||
| - . '<td class="commit-info-value">' | ||
| - . htmlspecialchars( $deltaMsg ) . '</td></tr>'; | ||
| - echo '</tbody></table>'; | ||
| - echo '</div>'; | ||
| - } | ||
| + $tpl->assign( 'empty_changes', empty( $changes ) ); | ||
| + $tpl->assign( 'changes', $changes ); | ||
| - private function renderFileDiff( array $change ) { | ||
| - $statusIcon = 'fa-file'; | ||
| - $statusClass = ''; | ||
| + $diffNet = $added - $deleted; | ||
| + $deltaMsg = $added . ' line' . ($added !== 1 ? 's' : '') . ' added, ' . | ||
| + $deleted . ' line' . ($deleted !== 1 ? 's' : '') . ' removed'; | ||
| - if( $change['type'] === 'A' ) { | ||
| - $statusIcon = 'fa-plus-circle'; | ||
| - $statusClass = 'status-add'; | ||
| - } elseif( $change['type'] === 'D' ) { | ||
| - $statusIcon = 'fa-minus-circle'; | ||
| - $statusClass = 'status-del'; | ||
| - } elseif( $change['type'] === 'M' ) { | ||
| - $statusIcon = 'fa-pencil-alt'; | ||
| - $statusClass = 'status-mod'; | ||
| + if( $diffNet !== 0 ) { | ||
| + $direction = $diffNet > 0 ? 'increase' : 'decrease'; | ||
| + $deltaMsg .= ', ' . \abs( $diffNet ) . "-line $direction"; | ||
| } | ||
| - | ||
| - echo '<div class="diff-file">'; | ||
| - echo '<div class="diff-header">'; | ||
| - echo '<span class="diff-status ' . $statusClass . '">' . | ||
| - '<i class="fa ' . $statusIcon . '"></i></span>'; | ||
| - echo '<span class="diff-path">' . | ||
| - htmlspecialchars( $change['path'] ) . '</span>'; | ||
| - echo '</div>'; | ||
| - | ||
| - if( $change['is_binary'] ) { | ||
| - echo '<div class="diff-binary">Binary files differ</div>'; | ||
| - } else { | ||
| - echo '<div class="diff-content">'; | ||
| - echo '<table><tbody>'; | ||
| - | ||
| - foreach( $change['hunks'] as $line ) { | ||
| - if( isset( $line['t'] ) && $line['t'] === 'gap' ) { | ||
| - echo '<tr class="diff-gap"><td colspan="3">'; | ||
| - echo '<img src="/images/diff-gap.svg" class="diff-gap-icon" />'; | ||
| - echo '</td></tr>'; | ||
| - } else { | ||
| - $class = 'diff-ctx'; | ||
| - $char = ' '; | ||
| - | ||
| - if( $line['t'] === '+' ) { | ||
| - $class = 'diff-add'; | ||
| - $char = '+'; | ||
| - } elseif( $line['t'] === '-' ) { | ||
| - $class = 'diff-del'; | ||
| - $char = '-'; | ||
| - } | ||
| - | ||
| - echo '<tr class="' . $class . '">'; | ||
| - echo '<td class="diff-num" data-num="' . $line['no'] . '"></td>'; | ||
| - echo '<td class="diff-num" data-num="' . $line['nn'] . '"></td>'; | ||
| - echo '<td class="diff-code"><span class="diff-marker">' . | ||
| - $char . '</span>' . htmlspecialchars( $line['l'] ) . '</td>'; | ||
| - echo '</tr>'; | ||
| - } | ||
| - } | ||
| - echo '</tbody></table>'; | ||
| - echo '</div>'; | ||
| - } | ||
| + $tpl->assign( 'delta_msg', $deltaMsg ); | ||
| - echo '</div>'; | ||
| + echo $tpl->render( 'diff.tpl' ); | ||
| } | ||
| } |
| } | ||
| - public function render() { | ||
| - $this->renderLayout( function() { | ||
| - $main = $this->git->getMainBranch(); | ||
| + public function render(): void { | ||
| + $main = $this->git->getMainBranch(); | ||
| + $tpl = new Template( 'file.tpl' ); | ||
| - if( !$main ) { | ||
| - echo '<div class="empty-state"><h3>No branches</h3></div>'; | ||
| - } else { | ||
| - $target = $this->hash; | ||
| - $entries = []; | ||
| + foreach( $this->getBaseTemplateVars( $this->currentRepo ) as $k => $v ) { | ||
| + $tpl->assign( $k, $v ); | ||
| + } | ||
| - $this->git->walk( $target, function( $file ) use ( &$entries ) { | ||
| - $entries[] = $file; | ||
| - }, $this->path ); | ||
| + if( !$main ) { | ||
| + $tpl->assign( 'has_branches', false ); | ||
| + } else { | ||
| + $tpl->assign( 'has_branches', true ); | ||
| - if( !empty( $entries ) && !$this->isExactFileMatch( $entries ) ) { | ||
| - $this->renderTree( $main, $target, $entries ); | ||
| - } else { | ||
| - $this->renderBlob( $target ); | ||
| + $target = $this->hash; | ||
| + $entries = []; | ||
| + | ||
| + $this->git->walk( $target, function( $file ) use ( &$entries ) { | ||
| + $entries[] = $file; | ||
| + }, $this->path ); | ||
| + | ||
| + $isExact = \count( $entries ) === 1 && $entries[0]->isName( \basename( $this->path ) ) && !$entries[0]->isDir; | ||
| + | ||
| + if( !empty( $entries ) && !$isExact ) { | ||
| + $tpl->assign( 'mode', 'tree' ); | ||
| + $tpl->assign( 'target_hash', $target ); | ||
| + $tpl->assign( 'repo_name', $this->currentRepo['name'] ); | ||
| + $tpl->assign( 'breadcrumbs', $this->buildBreadcrumbs( $this->currentRepo, $this->buildTrail( $target, 'Tree', $this->path ) ) ); | ||
| + | ||
| + \usort( $entries, function( $a, $b ) { | ||
| + return $a->compare( $b ); | ||
| + } ); | ||
| + | ||
| + $renderer = new HtmlFileRenderer( $this->currentRepo['safe_name'], $this->path, $target ); | ||
| + | ||
| + \ob_start(); | ||
| + | ||
| + foreach( $entries as $file ) { | ||
| + $file->renderListEntry( $renderer ); | ||
| } | ||
| - } | ||
| - }, $this->currentRepo ); | ||
| - } | ||
| - private function isExactFileMatch( $entries ) { | ||
| - return count( $entries ) === 1 && | ||
| - $entries[0]->isName( basename( $this->path ) ) && | ||
| - !$entries[0]->isDir; | ||
| - } | ||
| + $tpl->assign( 'entries_html', \ob_get_clean() ); | ||
| + } else { | ||
| + $tpl->assign( 'mode', 'blob' ); | ||
| + $tpl->assign( 'breadcrumbs', $this->buildBreadcrumbs( $this->currentRepo, $this->buildTrail( $target, 'File', $this->path ) ) ); | ||
| - private function renderTree( $main, $targetHash, $entries ) { | ||
| - $this->emitBreadcrumbs( $targetHash, 'Tree', $this->path ); | ||
| + $filename = $this->path; | ||
| + $file = $this->git->readFile( $target, $filename ); | ||
| + $size = $this->git->getObjectSize( $target, $filename ); | ||
| - echo '<h2>' . htmlspecialchars( $this->currentRepo['name'] ) . | ||
| - ' <span class="branch-badge">' . | ||
| - htmlspecialchars( $targetHash ) . '</span></h2>'; | ||
| + if( $size === 0 && !$file ) { | ||
| + $tpl->assign( 'file_found', false ); | ||
| + } else { | ||
| + $tpl->assign( 'file_found', true ); | ||
| - usort( $entries, function( $a, $b ) { | ||
| - return $a->compare( $b ); | ||
| - } ); | ||
| + $renderer = new HtmlFileRenderer( $this->currentRepo['safe_name'], \dirname( $filename ), $target ); | ||
| + $rawUrl = (new UrlBuilder())->withRepo( $this->currentRepo['safe_name'] )->withAction( 'raw' )->withHash( $target )->withName( $filename )->build(); | ||
| - echo '<table class="file-list-table">'; | ||
| - echo '<thead><tr><th></th><th>Name</th>' . | ||
| - '<th class="file-mode-cell">Mode</th>' . | ||
| - '<th class="file-size-cell">Size</th></tr></thead>'; | ||
| - echo '<tbody>'; | ||
| + \ob_start(); | ||
| - $renderer = new HtmlFileRenderer( | ||
| - $this->currentRepo['safe_name'], | ||
| - $this->path, | ||
| - $targetHash | ||
| - ); | ||
| + $handledMedia = $file->renderMedia( $renderer, $rawUrl ); | ||
| + $mediaHtml = \ob_get_clean(); | ||
| - foreach( $entries as $file ) { | ||
| - $file->renderListEntry( $renderer ); | ||
| - } | ||
| + $tpl->assign( 'handled_media', $handledMedia ); | ||
| + $tpl->assign( 'media_html', $mediaHtml ); | ||
| - echo '</tbody></table>'; | ||
| - } | ||
| + if( !$handledMedia ) { | ||
| + if( $file->isText() ) { | ||
| + $tpl->assign( 'is_text', true ); | ||
| - private function renderBlob( $targetHash ) { | ||
| - $filename = $this->path; | ||
| - $file = $this->git->readFile( $targetHash, $filename ); | ||
| - $size = $this->git->getObjectSize( $targetHash, $filename ); | ||
| + if( $size > self::MAX_DISPLAY_SIZE ) { | ||
| + $tpl->assign( 'is_large', true ); | ||
| - $renderer = new HtmlFileRenderer( | ||
| - $this->currentRepo['safe_name'], | ||
| - dirname( $filename ), | ||
| - $targetHash | ||
| - ); | ||
| + \ob_start(); | ||
| - $this->emitBreadcrumbs( $targetHash, 'File', $filename ); | ||
| + $file->renderSize( $renderer ); | ||
| - if( $size === 0 && !$file ) { | ||
| - echo '<div class="empty-state">File not found.</div>'; | ||
| - } else { | ||
| - $rawUrl = (new UrlBuilder()) | ||
| - ->withRepo( $this->currentRepo['safe_name'] ) | ||
| - ->withAction( 'raw' ) | ||
| - ->withHash( $targetHash ) | ||
| - ->withName( $filename ) | ||
| - ->build(); | ||
| + $tpl->assign( 'size_str', \ob_get_clean() ); | ||
| + $tpl->assign( 'download_url', $rawUrl ); | ||
| + } else { | ||
| + $tpl->assign( 'is_large', false ); | ||
| - if( !$file->renderMedia( $renderer, $rawUrl ) ) { | ||
| - if( $file->isText() ) { | ||
| - if( $size > self::MAX_DISPLAY_SIZE ) { | ||
| - ob_start(); | ||
| - $file->renderSize( $renderer ); | ||
| - $sizeStr = ob_get_clean(); | ||
| - $this->renderDownloadState( | ||
| - $targetHash, | ||
| - "File is too large to display ($sizeStr)." | ||
| - ); | ||
| - } else { | ||
| - $content = ''; | ||
| - $this->git->stream( | ||
| - $targetHash, | ||
| - function( $d ) use ( &$content ) { | ||
| - $content .= $d; | ||
| - }, | ||
| - $filename | ||
| - ); | ||
| + $content = ''; | ||
| - echo '<div class="blob-content"><pre class="blob-code">' . | ||
| - ($size > self::MAX_HIGHLIGHT_SIZE | ||
| - ? htmlspecialchars( $content ) | ||
| - : $file->highlight( $renderer, $content )) . | ||
| - '</pre></div>'; | ||
| + $this->git->stream( $target, function( $d ) use ( &$content ) { | ||
| + $content .= $d; | ||
| + }, $filename ); | ||
| + | ||
| + $highlighted = $size > self::MAX_HIGHLIGHT_SIZE ? \htmlspecialchars( $content ) : $file->highlight( $renderer, $content ); | ||
| + | ||
| + $tpl->assign( 'blob_content', $highlighted ); | ||
| + } | ||
| + } else { | ||
| + $tpl->assign( 'is_text', false ); | ||
| + $tpl->assign( 'download_url', $rawUrl ); | ||
| + } | ||
| } | ||
| - } else { | ||
| - $this->renderDownloadState( | ||
| - $targetHash, | ||
| - "This is a binary file." | ||
| - ); | ||
| } | ||
| } | ||
| } | ||
| - } | ||
| - | ||
| - private function renderDownloadState( $hash, $reason ) { | ||
| - $url = (new UrlBuilder()) | ||
| - ->withRepo( $this->currentRepo['safe_name'] ) | ||
| - ->withAction( 'raw' ) | ||
| - ->withHash( $hash ) | ||
| - ->withName( $this->path ) | ||
| - ->build(); | ||
| - echo '<div class="empty-state download-state">'; | ||
| - echo '<p>' . htmlspecialchars( $reason ) . '</p>'; | ||
| - echo '<a href="' . $url . '" class="btn-download">Download</a>'; | ||
| - echo '</div>'; | ||
| + echo $tpl->render( 'file.tpl' ); | ||
| } | ||
| - private function emitBreadcrumbs( $hash, $type, $path ) { | ||
| + private function buildTrail( string $hash, string $type, string $path ): array { | ||
| $trail = []; | ||
| - if( $path ) { | ||
| - $parts = explode( '/', trim( $path, '/' ) ); | ||
| + if( $path !== '' ) { | ||
| + $parts = \explode( '/', \trim( $path, '/' ) ); | ||
| $acc = ''; | ||
| foreach( $parts as $idx => $part ) { | ||
| $acc .= ($idx === 0 ? '' : '/') . $part; | ||
| - if( $idx === count( $parts ) - 1 ) { | ||
| - $trail[] = htmlspecialchars( $part ); | ||
| + if( $idx === \count( $parts ) - 1 ) { | ||
| + $trail[] = [ | ||
| + 'url' => '', | ||
| + 'name' => $part | ||
| + ]; | ||
| } else { | ||
| $url = (new UrlBuilder()) | ||
| ->withRepo( $this->currentRepo['safe_name'] ) | ||
| ->withAction( 'tree' ) | ||
| ->withHash( $hash ) | ||
| ->withName( $acc ) | ||
| ->build(); | ||
| - $trail[] = '<a href="' . $url . '">' . | ||
| - htmlspecialchars( $part ) . '</a>'; | ||
| + $trail[] = [ | ||
| + 'url' => $url, | ||
| + 'name' => $part | ||
| + ]; | ||
| } | ||
| } | ||
| - } elseif( $hash ) { | ||
| - $trail[] = $type . ' ' . substr( $hash, 0, 7 ); | ||
| + } elseif( $hash !== '' ) { | ||
| + $trail[] = [ | ||
| + 'url' => '', | ||
| + 'name' => $type . ' ' . \substr( $hash, 0, 7 ) | ||
| + ]; | ||
| } | ||
| - $this->renderBreadcrumbs( $this->currentRepo, $trail ); | ||
| + return $trail; | ||
| } | ||
| } |
| class HomePage extends BasePage { | ||
| - private array $repositories; | ||
| - private Git $git; | ||
| + private $repositories; | ||
| + private $git; | ||
| public function __construct( array $repositories, Git $git ) { | ||
| parent::__construct( $repositories ); | ||
| $this->repositories = $repositories; | ||
| $this->git = $git; | ||
| } | ||
| public function render(): void { | ||
| - $this->renderLayout( function() { | ||
| - if( empty( $this->repositories ) ) { | ||
| - echo '<div class="empty-state">No repositories found.</div>'; | ||
| - } else { | ||
| - echo '<div class="repo-grid">'; | ||
| - | ||
| - foreach( $this->repositories as $repo ) { | ||
| - $this->renderRepoCard( $repo ); | ||
| - } | ||
| - | ||
| - echo '</div>'; | ||
| - } | ||
| - } ); | ||
| - } | ||
| - | ||
| - private function renderRepoCard( array $repo ): void { | ||
| - $this->git->setRepository( $repo['path'] ); | ||
| + $reposData = []; | ||
| - $stats = [ 'branches' => 0, 'tags' => 0 ]; | ||
| + foreach( $this->repositories as $repo ) { | ||
| + $this->git->setRepository( $repo['path'] ); | ||
| - $this->git->eachBranch( function() use( &$stats ) { | ||
| - $stats['branches']++; | ||
| - } ); | ||
| + $branches = 0; | ||
| + $tags = 0; | ||
| - $this->git->eachTag( function() use( &$stats ) { | ||
| - $stats['tags']++; | ||
| - } ); | ||
| + $this->git->eachBranch( function() use( &$branches ) { | ||
| + $branches++; | ||
| + } ); | ||
| - $url = (new UrlBuilder())->withRepo( $repo['safe_name'] )->build(); | ||
| + $this->git->eachTag( function() use( &$tags ) { | ||
| + $tags++; | ||
| + } ); | ||
| - echo '<a href="' . $url . '" class="repo-card">'; | ||
| - echo '<h3>' . htmlspecialchars( $repo['name'] ) . '</h3>'; | ||
| - echo '<p class="repo-meta">'; | ||
| - echo $stats['branches'] . | ||
| - ($stats['branches'] === 1 ? ' branch, ' : ' branches, ') . | ||
| - $stats['tags'] . | ||
| - ($stats['tags'] === 1 ? ' tag' : ' tags'); | ||
| + $descPath = $repo['path'] . '/description'; | ||
| + $desc = \file_exists( $descPath ) ? \trim( \file_get_contents( $descPath ) ) : ''; | ||
| + $timeHtml = ''; | ||
| - if( $this->git->getMainBranch() ) { | ||
| - echo ', '; | ||
| + if( $this->git->getMainBranch() ) { | ||
| + $this->git->history( 'HEAD', 1, function( Commit $c ) use( &$timeHtml, $repo ) { | ||
| + $renderer = new HtmlCommitRenderer( $repo['safe_name'] ); | ||
| - $this->git->history( 'HEAD', 1, function( Commit $c ) use( $repo ) { | ||
| - $renderer = new HtmlCommitRenderer( $repo['safe_name'] ); | ||
| + \ob_start(); | ||
| + $c->renderTime( $renderer ); | ||
| + $timeHtml = \ob_get_clean(); | ||
| + } ); | ||
| + } | ||
| - $c->renderTime( $renderer ); | ||
| - } ); | ||
| + $reposData[] = [ | ||
| + 'name' => $repo['name'], | ||
| + 'url' => (new UrlBuilder())->withRepo( $repo['safe_name'] )->build(), | ||
| + 'branches' => $branches, | ||
| + 'tags' => $tags, | ||
| + 'desc' => $desc, | ||
| + 'time_html' => $timeHtml | ||
| + ]; | ||
| } | ||
| - | ||
| - echo '</p>'; | ||
| - | ||
| - $descPath = $repo['path'] . '/description'; | ||
| - if( file_exists( $descPath ) ) { | ||
| - $description = trim( file_get_contents( $descPath ) ); | ||
| + $tpl = new Template( 'home.tpl' ); | ||
| - if( $description !== '' ) { | ||
| - echo '<p style="margin-top: 1.5em;">' . | ||
| - htmlspecialchars( $description ) . '</p>'; | ||
| - } | ||
| + foreach( $this->getBaseTemplateVars() as $k => $v ) { | ||
| + $tpl->assign( $k, $v ); | ||
| } | ||
| - echo '</a>'; | ||
| + $tpl->assign( 'repos', $reposData ); | ||
| + | ||
| + echo $tpl->render( 'home.tpl' ); | ||
| } | ||
| } |
| <?php | ||
| interface Page { | ||
| - public function render(); | ||
| + public function render(): void; | ||
| } | ||
| class RawPage implements Page { | ||
| - private Git $git; | ||
| - private string $hash; | ||
| - private string $repo; | ||
| - private string $name; | ||
| + private $git; | ||
| + private $hash; | ||
| + private $repo; | ||
| + private $name; | ||
| public function __construct( | ||
| public function render(): void { | ||
| if( $this->name === '' ) { | ||
| - header( "Location: /{$this->repo}/" ); | ||
| + \header( "Location: /{$this->repo}/" ); | ||
| } else { | ||
| $file = $this->git->readFile( $this->hash, $this->name ); | ||
| - while( ob_get_level() > 0 ) { | ||
| - if( !ob_end_clean() ) { | ||
| + while( \ob_get_level() > 0 ) { | ||
| + if( !\ob_end_clean() ) { | ||
| break; | ||
| } | ||
| ); | ||
| } | ||
| - | ||
| - exit; | ||
| } | ||
| } | ||
| class TagsPage extends BasePage { | ||
| - private array $currentRepo; | ||
| - private Git $git; | ||
| + private $currentRepo; | ||
| + private $git; | ||
| public function __construct( | ||
| public function render(): void { | ||
| - $this->renderLayout( function() { | ||
| - $this->renderBreadcrumbs( $this->currentRepo, ['Tags'] ); | ||
| - echo '<h2>Tags</h2>'; | ||
| + $tpl = new Template( 'tags.tpl' ); | ||
| + $trail = [ | ||
| + [ | ||
| + 'url' => '', | ||
| + 'name' => 'Tags' | ||
| + ] | ||
| + ]; | ||
| - $tags = []; | ||
| + foreach( $this->getBaseTemplateVars( $this->currentRepo ) as $k => $v ) { | ||
| + $tpl->assign( $k, $v ); | ||
| + } | ||
| - $this->git->eachTag( function( Tag $tag ) use ( &$tags ) { | ||
| - $tags[] = $tag; | ||
| - } ); | ||
| + $tpl->assign( 'breadcrumbs', $this->buildBreadcrumbs( $this->currentRepo, $trail ) ); | ||
| - usort( $tags, function( Tag $a, Tag $b ) { | ||
| - return $a->compare( $b ); | ||
| - } ); | ||
| + $tags = []; | ||
| - if( empty( $tags ) ) { | ||
| - echo '<p>No tags found.</p>'; | ||
| - } else { | ||
| - $renderer = new HtmlTagRenderer( | ||
| - $this->currentRepo['safe_name'] | ||
| - ); | ||
| + $this->git->eachTag( function( Tag $tag ) use ( &$tags ) { | ||
| + $tags[] = $tag; | ||
| + } ); | ||
| - echo '<table class="tag-table">'; | ||
| - echo '<thead>'; | ||
| - echo '<tr>'; | ||
| - echo '<th>Name</th><th>Message</th><th>Author</th>'; | ||
| - echo '<th class="tag-age-header">Age</th>'; | ||
| - echo '<th class="tag-commit-header">Commit</th>'; | ||
| - echo '</tr>'; | ||
| - echo '</thead>'; | ||
| - echo '<tbody>'; | ||
| + \usort( $tags, function( Tag $a, Tag $b ) { | ||
| + return $a->compare( $b ); | ||
| + } ); | ||
| - $count = count( $tags ); | ||
| + $tpl->assign( 'has_tags', !empty( $tags ) ); | ||
| - for( $i = 0; $i < $count; $i++ ) { | ||
| - $tags[$i]->render( | ||
| - $renderer, | ||
| - isset( $tags[$i + 1] ) ? $tags[$i + 1] : new MissingTag() | ||
| - ); | ||
| - } | ||
| + if( !empty( $tags ) ) { | ||
| + $renderer = new HtmlTagRenderer( $this->currentRepo['safe_name'] ); | ||
| - echo '</tbody>'; | ||
| - echo '</table>'; | ||
| + \ob_start(); | ||
| + | ||
| + $count = \count( $tags ); | ||
| + | ||
| + for( $i = 0; $i < $count; $i++ ) { | ||
| + $tags[$i]->render( | ||
| + $renderer, | ||
| + isset( $tags[$i + 1] ) ? $tags[$i + 1] : new MissingTag() | ||
| + ); | ||
| } | ||
| - }, $this->currentRepo ); | ||
| + | ||
| + $tpl->assign( 'tags_html', \ob_get_clean() ); | ||
| + } | ||
| + | ||
| + echo $tpl->render( 'tags.tpl' ); | ||
| } | ||
| } | ||
| Delta | 365 lines added, 698 lines removed, 333-line decrease |
|---|