| … | … | @@ -8,6 +8,7 @@ |
| 8 | 8 | import { escapeHtml } from './response.js' |
| 9 | 9 | import { renderIdentityScript } from './identity-ui.js' |
| 10 | 10 | import { detectLanguage, getPrismAssets } from './syntax.js' |
| 11 | import { buildBreadcrumb } from './breadcrumb.js' | |
| 11 | 12 | import { getBreadcrumbStyles } from './breadcrumb.js' |
| 12 | 13 | import { parseDiff, getDiffStats, getStatusLabel } from './diff-parser.js' |
| 13 | 14 | |
| … | … | @@ -110,7 +111,7 @@ function renderTreePage(username, repoName, dirPath, files, breadcrumb, pathPref |
| 110 | 111 | |
| 111 | 112 | function renderFilePage(repoName, filePath, content, pathPrefix, options = {}) { |
| 112 | 113 | const { username = 'user', language = 'plaintext', lineCount = 0, isBinary = false } = options |
| 113 | const prismAssets = getPrismAssets(language, server.pathPrefix) | |
| 114 | const prismAssets = getPrismAssets(language, pathPrefix) | |
| 114 | 115 | const isMarkdown = filePath.toLowerCase().endsWith('.md') |
| 115 | 116 | |
| 116 | 117 | // Build breadcrumb |
| … | … | @@ -135,7 +136,7 @@ function renderFilePage(repoName, filePath, content, pathPrefix, options = {}) { |
| 135 | 136 | <head> |
| 136 | 137 | <meta charset="UTF-8"> |
| 137 | 138 | <title>${escapeHtml(filePath)} - ${escapeHtml(repoName)}</title> |
| 138 | ${isMarkdown ? '<script src="${server.pathPrefix}/vendor/marked.min.js"></script>' : ''} | |
| 139 | ${isMarkdown ? `<script src="${pathPrefix}/vendor/marked.min.js"></script>` : ''} | |
| 139 | 140 | <!-- Prism.js CSS --> |
| 140 | 141 | ${prismAssets.css.map(url => `<link rel="stylesheet" href="${url}">`).join('\n ')} |
| 141 | 142 | <style> |
| … | … | @@ -447,8 +447,42 @@ async function handleViewFile(req, res, server) { |
| 447 | 447 | const { backendManager } = server.repoData |
| 448 | 448 | const { pathPrefix } = server.config |
| 449 | 449 | try { |
| 450 | const { username, repoName, path: filePath } = req.params | |
| 451 | const content = await backendManager.readFile(repoName, filePath) | |
| 450 | const { username, repoName, path: fullPath } = req.params | |
| 451 | ||
| 452 | // fullPath is "branch/path/to/file" — split into ref and file path | |
| 453 | // Try common branch names first, fall back to treating first segment as ref | |
| 454 | const parts = fullPath.split('/') | |
| 455 | let ref = 'HEAD' | |
| 456 | let filePath = fullPath | |
| 457 | ||
| 458 | // Check if first segment is a known branch | |
| 459 | try { | |
| 460 | const branches = await backendManager.getBranches(repoName) | |
| 461 | const branchNames = branches.map(b => b.name || b) | |
| 462 | ||
| 463 | // Try matching longest branch name first (handles branches with slashes) | |
| 464 | let matched = false | |
| 465 | for (const branch of branchNames.sort((a, b) => b.length - a.length)) { | |
| 466 | if (fullPath.startsWith(branch + '/')) { | |
| 467 | ref = branch | |
| 468 | filePath = fullPath.slice(branch.length + 1) | |
| 469 | matched = true | |
| 470 | break | |
| 471 | } | |
| 472 | } | |
| 473 | if (!matched && parts.length > 1) { | |
| 474 | ref = parts[0] | |
| 475 | filePath = parts.slice(1).join('/') | |
| 476 | } | |
| 477 | } catch { | |
| 478 | // Can't get branches — use first segment as ref | |
| 479 | if (parts.length > 1) { | |
| 480 | ref = parts[0] | |
| 481 | filePath = parts.slice(1).join('/') | |
| 482 | } | |
| 483 | } | |
| 484 | ||
| 485 | const content = await backendManager.readFile(repoName, filePath, ref) | |
| 452 | 486 | |
| 453 | 487 | // Detect language from file extension |
| 454 | 488 | const language = detectLanguage(filePath) |