annotate hg-web/src/components/repo-browser.tsx @ 193:9f4429c49733 hg-web

[HgWeb] Making progress....
author MrJuneJune <me@mrjunejune.com>
date Sun, 25 Jan 2026 20:04:55 -0800
parents
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
193
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1 import React, { useState, useEffect, useRef, useCallback } from 'react';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2 import createMarkdownModule from 'markdown_converter/markdown_to_html_wasm/markdown_to_html_bin.js';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3 import hljs from 'third_party/highlight/highlight.min.js';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
4 import { Header } from "hg-web/src/components/header";
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
5 import { Footer } from "hg-web/src/components/footer";
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
6 import { ThemeProvider, useTheme } from "hg-web/src/components/theme";
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
7
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
8 // --- ICONS (served as static files) ---
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
9 const ICONS = {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
10 folder: "/icons/folder.png",
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
11 file: "/icons/file.svg",
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
12 close: "/icons/close.png"
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
13 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
14
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
15 const API_BASE = '/api/repo';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
16
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
17 // File extensions that should be displayed as code
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
18 const CODE_EXTENSIONS = new Set([
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
19 'js', 'jsx', 'ts', 'tsx', 'py', 'rb', 'java', 'c', 'cpp', 'h', 'hpp',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
20 'cs', 'go', 'rs', 'swift', 'kt', 'scala', 'php', 'pl', 'sh', 'bash',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
21 'zsh', 'fish', 'ps1', 'bat', 'cmd', 'html', 'htm', 'css', 'scss',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
22 'sass', 'less', 'json', 'xml', 'yaml', 'yml', 'toml', 'ini', 'cfg',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
23 'conf', 'md', 'markdown', 'txt', 'log', 'sql', 'graphql', 'vue',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
24 'svelte', 'astro', 'prisma', 'dockerfile', 'makefile', 'cmake',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
25 'gradle', 'pom', 'lock', 'gitignore', 'env', 'example', 'sample'
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
26 ]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
27
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
28 // Prefetch cache
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
29 const prefetchCache = new Map<string, Promise<any>>();
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
30
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
31 function isCodeFile(filename: string): boolean {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
32 const ext = filename.split('.').pop()?.toLowerCase() || '';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
33 const basename = filename.toLowerCase();
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
34 return CODE_EXTENSIONS.has(ext) ||
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
35 CODE_EXTENSIONS.has(basename) ||
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
36 basename === 'dockerfile' ||
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
37 basename === 'makefile' ||
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
38 basename.startsWith('.');
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
39 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
40
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
41 function isMarkdownFile(filename: string): boolean {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
42 const ext = filename.split('.').pop()?.toLowerCase() || '';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
43 return ext === 'md' || ext === 'markdown';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
44 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
45
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
46 function prefetchDirectory(path: string): void {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
47 const cacheKey = `dir:${path}`;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
48 if (prefetchCache.has(cacheKey)) return;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
49
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
50 const url = path
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
51 ? `${API_BASE}/list?path=${encodeURIComponent(path)}`
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
52 : `${API_BASE}/list`;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
53
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
54 prefetchCache.set(cacheKey, fetch(url).then(r => r.json()).catch(() => null));
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
55 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
56
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
57 function prefetchFile(path: string): void {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
58 const cacheKey = `file:${path}`;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
59 if (prefetchCache.has(cacheKey)) return;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
60
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
61 prefetchCache.set(cacheKey,
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
62 fetch(`${API_BASE}/file?path=${encodeURIComponent(path)}`)
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
63 .then(r => r.ok ? r.text() : null)
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
64 .catch(() => null)
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
65 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
66 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
67
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
68 async function getCachedFile(path: string): Promise<string | null> {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
69 const cacheKey = `file:${path}`;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
70 if (prefetchCache.has(cacheKey)) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
71 return prefetchCache.get(cacheKey);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
72 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
73 prefetchFile(path);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
74 return prefetchCache.get(cacheKey)!;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
75 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
76
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
77 /**
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
78 * Component: Breadcrumb
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
79 */
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
80 function Breadcrumb({ currentPath, onNavigate }: { currentPath: string; onNavigate: (path: string) => void }) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
81 if (!currentPath) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
82 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
83 <nav className="breadcrumb">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
84 <span className="nav-item active">root</span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
85 </nav>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
86 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
87 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
88
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
89 const parts = currentPath.split('/').filter(p => p);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
90 const crumbs = parts.map((part, index) => ({
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
91 name: part,
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
92 fullPath: parts.slice(0, index + 1).join('/')
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
93 }));
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
94
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
95 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
96 <nav className="breadcrumb">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
97 <a
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
98 href="/"
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
99 onClick={(e) => { e.preventDefault(); onNavigate(''); }}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
100 title="Go to Root"
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
101 >
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
102 root
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
103 </a>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
104 {crumbs.map((crumb, index) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
105 const isLast = index === crumbs.length - 1;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
106 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
107 <React.Fragment key={crumb.fullPath}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
108 <span className="separator">/</span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
109 {isLast ? (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
110 <span className="nav-item active">{crumb.name}</span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
111 ) : (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
112 <a
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
113 href={`?path=${encodeURIComponent(crumb.fullPath)}`}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
114 onClick={(e) => { e.preventDefault(); onNavigate(crumb.fullPath); }}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
115 >
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
116 {crumb.name}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
117 </a>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
118 )}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
119 </React.Fragment>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
120 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
121 })}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
122 </nav>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
123 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
124 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
125
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
126 /**
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
127 * Component: FileViewer
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
128 * Shows file content inline with syntax highlighting
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
129 */
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
130 function FileViewer({ filePath, onClose }: { filePath: string; onClose: () => void }) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
131 const [content, setContent] = useState<string | null>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
132 const [loading, setLoading] = useState(true);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
133 const codeRef = useRef<HTMLElement>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
134
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
135 const filename = filePath.split('/').pop() || filePath;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
136
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
137 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
138 setLoading(true);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
139 getCachedFile(filePath).then((text) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
140 setContent(text);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
141 setLoading(false);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
142 });
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
143 }, [filePath]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
144
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
145 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
146 if (content && codeRef.current) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
147 hljs.highlightElement(codeRef.current);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
148 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
149 }, [content]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
150
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
151 // Close on escape key
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
152 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
153 const handleKeyDown = (e: KeyboardEvent) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
154 if (e.key === 'Escape') onClose();
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
155 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
156 window.addEventListener('keydown', handleKeyDown);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
157 return () => window.removeEventListener('keydown', handleKeyDown);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
158 }, [onClose]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
159
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
160 // Get language from file extension for highlight.js
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
161 const getLanguage = () => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
162 const ext = filename.split('.').pop()?.toLowerCase() || '';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
163 const langMap: Record<string, string> = {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
164 js: 'javascript', jsx: 'javascript', ts: 'typescript', tsx: 'typescript',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
165 py: 'python', rb: 'ruby', rs: 'rust', go: 'go', java: 'java',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
166 c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp', cs: 'csharp',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
167 sh: 'bash', bash: 'bash', zsh: 'bash', fish: 'bash',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
168 json: 'json', yaml: 'yaml', yml: 'yaml', toml: 'toml',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
169 html: 'html', htm: 'html', css: 'css', scss: 'scss', sass: 'scss',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
170 sql: 'sql', md: 'markdown', markdown: 'markdown', xml: 'xml',
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
171 dockerfile: 'dockerfile', makefile: 'makefile'
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
172 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
173 return langMap[ext] || 'plaintext';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
174 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
175
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
176 const addLineNumbers = (text: string) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
177 const lines = text.split('\n');
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
178 return lines.map((_, i) => i + 1).join('\n');
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
179 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
180
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
181 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
182 <div className="file-viewer-overlay" onClick={onClose}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
183 <div className="file-viewer" onClick={(e) => e.stopPropagation()}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
184 <div className="file-viewer-header">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
185 <span className="file-viewer-title">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
186 <img src={ICONS.file} alt="" style={{ width: 16, height: 16 }} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
187 {filename}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
188 </span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
189 <button className="file-viewer-close" onClick={onClose} title="Close (Esc)">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
190 <img className="icon-invert" src={ICONS.close} alt="Close" />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
191 </button>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
192 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
193 <div className="file-viewer-content">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
194 {loading ? (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
195 <div className="file-viewer-loading">Loading...</div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
196 ) : content ? (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
197 <pre style={{ display: 'flex' }}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
198 <span className="file-viewer-line-numbers">{addLineNumbers(content)}</span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
199 <code ref={codeRef} className={`language-${getLanguage()}`}>{content}</code>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
200 </pre>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
201 ) : (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
202 <div className="file-viewer-loading">Unable to load file</div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
203 )}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
204 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
205 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
206 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
207 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
208 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
209
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
210 /**
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
211 * Component: MarkdownViewerModal
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
212 * Shows markdown content rendered in a modal
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
213 */
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
214 function MarkdownViewerModal({ filePath, onClose }: { filePath: string; onClose: () => void }) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
215 const [content, setContent] = useState<string | null>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
216 const [loading, setLoading] = useState(true);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
217 const contentRef = useRef<HTMLDivElement>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
218 const moduleRef = useRef<any>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
219 const [wasmReady, setWasmReady] = useState(false);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
220
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
221 const filename = filePath.split('/').pop() || filePath;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
222
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
223 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
224 createMarkdownModule().then((Module: any) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
225 moduleRef.current = Module;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
226 setWasmReady(true);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
227 });
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
228 }, []);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
229
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
230 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
231 setLoading(true);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
232 getCachedFile(filePath).then((text) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
233 setContent(text);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
234 setLoading(false);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
235 });
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
236 }, [filePath]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
237
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
238 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
239 if (!content || !wasmReady || !contentRef.current || !moduleRef.current) return;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
240
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
241 const Module = moduleRef.current;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
242 const markdownToHtmlPtr = Module.cwrap('markdown_to_html', 'number', ['string']);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
243 const markdownFree = Module.cwrap('markdown_free', null, ['number']);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
244
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
245 const ptr = markdownToHtmlPtr(content);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
246 const html = Module.UTF8ToString(ptr);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
247 markdownFree(ptr);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
248 contentRef.current.innerHTML = html;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
249 }, [content, wasmReady]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
250
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
251 // Close on escape key
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
252 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
253 const handleKeyDown = (e: KeyboardEvent) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
254 if (e.key === 'Escape') onClose();
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
255 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
256 window.addEventListener('keydown', handleKeyDown);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
257 return () => window.removeEventListener('keydown', handleKeyDown);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
258 }, [onClose]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
259
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
260 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
261 <div className="file-viewer-overlay" onClick={onClose}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
262 <div className="file-viewer" onClick={(e) => e.stopPropagation()}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
263 <div className="file-viewer-header">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
264 <span className="file-viewer-title">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
265 <img src={ICONS.file} alt="" style={{ width: 16, height: 16 }} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
266 {filename}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
267 </span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
268 <button className="file-viewer-close" onClick={onClose} title="Close (Esc)">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
269 <img className="icon-invert" src={ICONS.close} alt="Close" />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
270 </button>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
271 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
272 <div className="file-viewer-content">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
273 {loading || !wasmReady ? (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
274 <div className="file-viewer-loading">Loading...</div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
275 ) : content ? (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
276 <div className="readme-content" ref={contentRef} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
277 ) : (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
278 <div className="file-viewer-loading">Unable to load file</div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
279 )}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
280 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
281 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
282 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
283 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
284 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
285
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
286 /**
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
287 * Component: FileList
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
288 */
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
289 function FileList({ directories, files, onNavigate, onOpenFile }: {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
290 directories: any[];
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
291 files: any[];
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
292 onNavigate: (path: string) => void;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
293 onOpenFile: (path: string) => void;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
294 }) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
295 const isEmpty = directories.length === 0 && files.length === 0;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
296
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
297 if (isEmpty) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
298 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
299 <div className="file-list-container">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
300 <div className="empty-state">This directory is empty.</div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
301 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
302 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
303 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
304
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
305 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
306 <div className="file-list-container">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
307 <div className="file-header">Files</div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
308
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
309 <div id="fileListBody">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
310 {directories.map((dir) => (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
311 <FileRow
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
312 key={dir.abspath}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
313 item={dir}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
314 iconUrl={ICONS.folder}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
315 isDir={true}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
316 onNavigate={onNavigate}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
317 onOpenFile={onOpenFile}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
318 />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
319 ))}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
320
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
321 {files.map((file) => (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
322 <FileRow
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
323 key={file.abspath}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
324 item={file}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
325 iconUrl={ICONS.file}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
326 isDir={false}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
327 onNavigate={onNavigate}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
328 onOpenFile={onOpenFile}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
329 />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
330 ))}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
331 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
332 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
333 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
334 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
335
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
336 /**
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
337 * Component: FileRow
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
338 */
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
339 function FileRow({ item, iconUrl, isDir, onNavigate, onOpenFile }: {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
340 item: { abspath: string; basename: string };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
341 iconUrl: string;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
342 isDir: boolean;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
343 onNavigate: (path: string) => void;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
344 onOpenFile: (path: string) => void;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
345 }) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
346 const handleClick = (e: React.MouseEvent) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
347 e.preventDefault();
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
348 if (isDir) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
349 onNavigate(item.abspath);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
350 } else if (isCodeFile(item.basename)) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
351 onOpenFile(item.abspath);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
352 } else {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
353 window.open(`/api/repo/file?path=${encodeURIComponent(item.abspath)}`, '_blank');
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
354 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
355 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
356
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
357 const handleMouseEnter = () => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
358 if (isDir) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
359 prefetchDirectory(item.abspath);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
360 } else if (isCodeFile(item.basename)) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
361 prefetchFile(item.abspath);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
362 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
363 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
364
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
365 const href = isDir
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
366 ? `?path=${encodeURIComponent(item.abspath)}`
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
367 : `/api/repo/file?path=${encodeURIComponent(item.abspath)}`;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
368
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
369 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
370 <div className="file-row" onMouseEnter={handleMouseEnter}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
371 <span className="icon">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
372 <img className="icon-invert" src={iconUrl} alt={isDir ? "Directory" : "File"} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
373 </span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
374 <span className="name">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
375 <a href={href} onClick={handleClick}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
376 {item.basename}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
377 </a>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
378 </span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
379 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
380 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
381 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
382
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
383 /**
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
384 * Component: ReadmeViewer
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
385 */
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
386 function ReadmeViewer({ content }: { content: string | null }) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
387 const contentRef = useRef<HTMLDivElement>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
388 const moduleRef = useRef<any>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
389 const [wasmReady, setWasmReady] = useState(false);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
390
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
391 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
392 createMarkdownModule().then((Module: any) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
393 moduleRef.current = Module;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
394 setWasmReady(true);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
395 });
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
396 }, []);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
397
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
398 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
399 if (!content || !wasmReady || !contentRef.current || !moduleRef.current) return;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
400
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
401 const Module = moduleRef.current;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
402 const markdownToHtmlPtr = Module.cwrap('markdown_to_html', 'number', ['string']);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
403 const markdownFree = Module.cwrap('markdown_free', null, ['number']);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
404
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
405 const ptr = markdownToHtmlPtr(content);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
406 const html = Module.UTF8ToString(ptr);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
407 markdownFree(ptr);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
408 contentRef.current.innerHTML = html;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
409 }, [content, wasmReady]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
410
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
411 if (!content) return null;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
412
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
413 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
414 <div className="readme-section">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
415 <div className="readme-header">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
416 <img className="icon-invert" src={ICONS.file} width="16" alt="" style={{ opacity: 0.5 }} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
417 README.md
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
418 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
419 <div className="readme-content" ref={contentRef}>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
420 {!wasmReady && 'Loading...'}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
421 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
422 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
423 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
424 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
425
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
426 /**
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
427 * Repository Browser Content (uses theme context)
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
428 */
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
429 function RepoBrowserContent() {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
430 const [currentPath, setCurrentPath] = useState(getCurrentPath());
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
431 const [content, setContent] = useState<{ files: any[]; directories: any[] }>({ files: [], directories: [] });
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
432 const [readme, setReadme] = useState<string | null>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
433 const [error, setError] = useState<string | null>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
434 const [loading, setLoading] = useState(false);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
435 const [viewingFile, setViewingFile] = useState<string | null>(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
436
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
437 const { isDark, toggleTheme } = useTheme();
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
438
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
439 function getCurrentPath() {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
440 const params = new URLSearchParams(window.location.search);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
441 return params.get('path') || '';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
442 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
443
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
444 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
445 const handlePopState = () => setCurrentPath(getCurrentPath());
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
446 window.addEventListener('popstate', handlePopState);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
447 return () => window.removeEventListener('popstate', handlePopState);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
448 }, []);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
449
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
450 useEffect(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
451 fetchDirectory(currentPath);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
452 fetchReadme(currentPath);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
453 }, [currentPath]);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
454
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
455 const navigate = (path: string) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
456 const newUrl = path ? `?path=${encodeURIComponent(path)}` : '/';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
457 window.history.pushState({ path }, '', newUrl);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
458 setCurrentPath(path);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
459 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
460
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
461 const fetchDirectory = async (path: string) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
462 setLoading(true);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
463 setError(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
464 try {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
465 const cacheKey = `dir:${path}`;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
466 let data;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
467 if (prefetchCache.has(cacheKey)) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
468 data = await prefetchCache.get(cacheKey);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
469 prefetchCache.delete(cacheKey);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
470 } else {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
471 const url = path
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
472 ? `${API_BASE}/list?path=${encodeURIComponent(path)}`
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
473 : `${API_BASE}/list`;
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
474 const response = await fetch(url);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
475 if (response.ok) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
476 data = await response.json();
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
477 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
478 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
479
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
480 if (data?.error) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
481 throw new Error(data.error);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
482 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
483
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
484 setContent({
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
485 files: data?.files || [],
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
486 directories: data?.directories || []
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
487 });
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
488 } catch (err: any) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
489 console.error('Error loading directory:', err);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
490 setError(err.message);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
491 } finally {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
492 setLoading(false);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
493 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
494 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
495
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
496 const fetchReadme = async (path: string) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
497 setReadme(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
498 const readmePath = path ? `${path}/README.md` : 'README.md';
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
499 try {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
500 const response = await fetch(`${API_BASE}/file?path=${encodeURIComponent(readmePath)}`);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
501 if (response.ok) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
502 const text = await response.text();
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
503 setReadme(text);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
504 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
505 } catch (err) {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
506 // Readme is optional, ignore errors
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
507 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
508 };
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
509
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
510 const handleOpenFile = useCallback((path: string) => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
511 setViewingFile(path);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
512 }, []);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
513
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
514 const handleCloseFile = useCallback(() => {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
515 setViewingFile(null);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
516 }, []);
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
517
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
518 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
519 <>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
520 <div className="repo-container">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
521 <Header
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
522 title="Zenbu Repository"
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
523 subtitle="Browse and manage the mercurial codebase"
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
524 showThemeToggle={true}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
525 isDark={isDark}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
526 onToggleTheme={toggleTheme}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
527 />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
528
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
529 {/* Clone Bar */}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
530 <div className="clone-box">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
531 <div className="clone-box-inner">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
532 <span className="clone-label">Clone HTTPS</span>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
533 <code className="clone-url">hg clone http://zenbu.babocoder.com/repo</code>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
534 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
535 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
536
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
537 {/* Navigation & Content */}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
538 <Breadcrumb currentPath={currentPath} onNavigate={navigate} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
539
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
540 {error && <div className="error-message">Error: {error}</div>}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
541
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
542 {loading ? (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
543 <div className="file-list-container">
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
544 <div className="loading-state">Loading files...</div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
545 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
546 ) : (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
547 <>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
548 <FileList
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
549 directories={content.directories}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
550 files={content.files}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
551 onNavigate={navigate}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
552 onOpenFile={handleOpenFile}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
553 />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
554 <ReadmeViewer content={readme} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
555 </>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
556 )}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
557
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
558 <Footer />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
559 </div>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
560
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
561 {/* File Viewer Modal */}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
562 {viewingFile && (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
563 isMarkdownFile(viewingFile) ? (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
564 <MarkdownViewerModal filePath={viewingFile} onClose={handleCloseFile} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
565 ) : (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
566 <FileViewer filePath={viewingFile} onClose={handleCloseFile} />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
567 )
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
568 )}
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
569 </>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
570 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
571 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
572
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
573 /**
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
574 * Main Application Component with ThemeProvider
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
575 */
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
576 function RepoBrowser() {
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
577 return (
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
578 <ThemeProvider>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
579 <RepoBrowserContent />
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
580 </ThemeProvider>
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
581 );
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
582 }
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
583
9f4429c49733 [HgWeb] Making progress....
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
584 export { RepoBrowser };