Mercurial
diff hg-web/src/index.html @ 104:2301aeb7503b
[Hg Web] Super simple mercurial server.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Sat, 03 Jan 2026 10:20:45 -0800 |
| parents | |
| children | ffb764d2fcc5 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hg-web/src/index.html Sat Jan 03 10:20:45 2026 -0800 @@ -0,0 +1,175 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Zenbu Repository</title> + <link rel="stylesheet" href="/base.css"> + <link rel="stylesheet" href="/index.css"> +</head> +<body> + <main> + <div class="header"> + <h1>Zenbu Repository</h1> + <p class="description">Browse and clone this mercurial repository</p> + </div> + + <div class="clone-info"> + <strong>Clone this repository:</strong><br> + <code>hg clone http://zenbu.babocoder.com</code> + </div> + + <div class="breadcrumb" id="breadcrumb"></div> + + <div class="file-list" id="fileList"></div> + + <div class="readme-section" id="readmeSection" style="display: none;"> + <h2>README</h2> + <div class="readme-content" id="readmeContent"></div> + </div> + + <div class="empty-state" id="emptyState" style="display: none;"> + <p>No files found in this directory</p> + </div> + </main> + + <script src="/markdown_to_html.js"></script> + <script> + const API_BASE = '/api/repo'; + + // Get current path from URL + function getCurrentPath() { + const params = new URLSearchParams(window.location.search); + return params.get('path') || ''; + } + + // Update URL without reloading + function updateURL(path) { + const url = path ? `?path=${encodeURIComponent(path)}` : '/'; + window.history.pushState({path}, '', url); + } + + // Render breadcrumb + function renderBreadcrumb(path) { + const breadcrumb = document.getElementById('breadcrumb'); + if (!path) { + breadcrumb.innerHTML = '<a href="/">Root</a>'; + return; + } + + const parts = path.split('/').filter(p => p); + let currentPath = ''; + let html = '<a href="/">Root</a>'; + + parts.forEach((part, index) => { + currentPath += (currentPath ? '/' : '') + part; + html += ` <span>/</span> `; + if (index === parts.length - 1) { + html += `<span>${part}</span>`; + } else { + html += `<a href="?path=${encodeURIComponent(currentPath)}">${part}</a>`; + } + }); + + breadcrumb.innerHTML = html; + } + + // Render file list + function renderFiles(files) { + const fileList = document.getElementById('fileList'); + const emptyState = document.getElementById('emptyState'); + + if (!files || files.length === 0) { + fileList.style.display = 'none'; + emptyState.style.display = 'block'; + return; + } + + emptyState.style.display = 'none'; + fileList.style.display = 'block'; + + // Sort: directories first, then files, alphabetically + files.sort((a, b) => { + if (a.type !== b.type) { + return a.type === 'directory' ? -1 : 1; + } + return a.name.localeCompare(b.name); + }); + + let html = ''; + files.forEach(file => { + const icon = file.type === 'directory' ? '📁' : '📄'; + const className = file.type; + const href = file.type === 'directory' + ? `?path=${encodeURIComponent(file.path)}` + : `/api/repo/file?path=${encodeURIComponent(file.path)}`; + const target = file.type === 'directory' ? '' : 'target="_blank"'; + + html += ` + <div class="file-item ${className}"> + <span class="icon">${icon}</span> + <span class="name"> + <a href="${href}" ${target}>${file.name}</a> + </span> + </div> + `; + }); + + fileList.innerHTML = html; + } + + // Load and render README + async function loadReadme(path) { + const readmeSection = document.getElementById('readmeSection'); + const readmeContent = document.getElementById('readmeContent'); + + try { + const readmePath = path ? `${path}/README.md` : 'README.md'; + const response = await fetch(`/api/repo/readme?path=${encodeURIComponent(readmePath)}`); + + if (response.ok) { + const markdown = await response.text(); + readmeSection.style.display = 'block'; + renderMarkdown(readmeContent, markdown); + } else { + readmeSection.style.display = 'none'; + } + } catch (error) { + readmeSection.style.display = 'none'; + } + } + + // Load directory contents + async function loadDirectory(path) { + try { + const url = path ? `${API_BASE}/list?path=${encodeURIComponent(path)}` : `${API_BASE}/list`; + const response = await fetch(url); + const data = await response.json(); + + if (data.error) { + throw new Error(data.error); + } + + renderBreadcrumb(path); + renderFiles(data.files); + loadReadme(path); + } catch (error) { + console.error('Error loading directory:', error); + document.getElementById('fileList').innerHTML = ` + <div class="error-message">Error loading directory: ${error.message}</div> + `; + } + } + + // Handle browser back/forward + window.addEventListener('popstate', (event) => { + const path = event.state?.path || ''; + loadDirectory(path); + }); + + // Initial load + const currentPath = getCurrentPath(); + loadDirectory(currentPath); + </script> +</body> +</html>