fix code stuff

This commit is contained in:
Chris Wanstrath 2026-01-30 18:29:42 -08:00
parent 1c51427034
commit 4c1701a06d

View File

@ -3,6 +3,7 @@ import { createThemes, define, stylesToCSS } from '@because/forge'
import { readdir, stat } from 'fs/promises'
import { readFileSync } from 'fs'
import { join, extname, basename } from 'path'
import type { Child } from 'hono/jsx'
const APPS_DIR = process.env.APPS_DIR!
@ -43,7 +44,7 @@ const theme = createThemes({
})
// Styles
const Container = define('CodeBrowserContainer', {
const Container = define('Container', {
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
padding: '20px',
maxWidth: '1200px',
@ -63,7 +64,7 @@ const Title = define('Title', {
fontWeight: 'bold',
})
const AppName = define('AppName', {
const Subtitle = define('Subtitle', {
color: theme('textMuted'),
fontSize: '18px',
marginTop: '5px',
@ -141,7 +142,7 @@ const CodeHeader = define('CodeHeader', {
fontSize: '14px',
})
const Error = define('Error', {
const ErrorBox = define('ErrorBox', {
color: theme('error'),
padding: '20px',
backgroundColor: theme('errorBg'),
@ -176,14 +177,20 @@ const FileIconSvg = () => (
</FileIcon>
)
const themeScript = `
(function() {
var theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
document.body.setAttribute('data-theme', theme);
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
document.body.setAttribute('data-theme', e.matches ? 'dark' : 'light');
});
})();
const initScript = `
(function() {
var theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
document.body.setAttribute('data-theme', theme);
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
document.body.setAttribute('data-theme', e.matches ? 'dark' : 'light');
});
function sendHeight() {
window.parent.postMessage({ type: 'resize-iframe', height: document.documentElement.scrollHeight }, '*');
}
sendHeight();
new ResizeObserver(sendHeight).observe(document.body);
window.addEventListener('load', sendHeight);
})();
`
const baseStyles = `
@ -193,6 +200,44 @@ body {
}
`
interface LayoutProps {
title: string
subtitle?: string
children: Child
highlight?: boolean
}
function Layout({ title, subtitle, children, highlight }: LayoutProps) {
return (
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<link rel="stylesheet" href="/styles.css" />
{highlight && (
<>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" media="(prefers-color-scheme: light)" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" media="(prefers-color-scheme: dark)" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
</>
)}
</head>
<body>
<script dangerouslySetInnerHTML={{ __html: initScript }} />
<Container>
<Header>
<Title>Code Browser</Title>
{subtitle && <Subtitle>{subtitle}</Subtitle>}
</Header>
{children}
</Container>
{highlight && <script dangerouslySetInnerHTML={{ __html: 'hljs.highlightAll();' }} />}
</body>
</html>
)
}
app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
'Content-Type': 'text/css; charset=utf-8',
}))
@ -201,17 +246,11 @@ async function listFiles(appPath: string, subPath: string = '') {
const fullPath = join(appPath, subPath)
const entries = await readdir(fullPath, { withFileTypes: true })
const items = await Promise.all(
entries.map(async entry => {
const itemPath = join(fullPath, entry.name)
const stats = await stat(itemPath)
return {
name: entry.name,
isDirectory: entry.isDirectory(),
path: subPath ? `${subPath}/${entry.name}` : entry.name,
}
})
)
const items = entries.map(entry => ({
name: entry.name,
isDirectory: entry.isDirectory(),
path: subPath ? `${subPath}/${entry.name}` : entry.name,
}))
return items.sort((a, b) => {
if (a.isDirectory !== b.isDirectory) {
@ -221,15 +260,6 @@ async function listFiles(appPath: string, subPath: string = '') {
})
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
function getLanguage(filename: string): string {
const ext = extname(filename).toLowerCase()
const langMap: Record<string, string> = {
@ -254,23 +284,9 @@ app.get('/', async c => {
if (!appName) {
return c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Browser</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<Container>
<Header>
<Title>Code Browser</Title>
</Header>
<Error>Please specify an app name with ?app=&lt;name&gt;</Error>
</Container>
</body>
</html>
<Layout title="Code Browser">
<ErrorBox>Please specify an app name with ?app=&lt;name&gt;</ErrorBox>
</Layout>
)
}
@ -280,126 +296,59 @@ app.get('/', async c => {
await stat(appPath)
} catch {
return c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Browser</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<Container>
<Header>
<Title>Code Browser</Title>
</Header>
<Error>App "{appName}" not found</Error>
</Container>
</body>
</html>
<Layout title="Code Browser">
<ErrorBox>App "{appName}" not found</ErrorBox>
</Layout>
)
}
const fullPath = join(appPath, filePath)
let stats
let fileStats
try {
stats = await stat(fullPath)
fileStats = await stat(fullPath)
} catch {
return c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Browser</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<Container>
<Header>
<Title>Code Browser</Title>
</Header>
<Error>Path "{filePath}" not found in app "{appName}"</Error>
</Container>
</body>
</html>
<Layout title="Code Browser" subtitle={appName}>
<ErrorBox>Path "{filePath}" not found</ErrorBox>
</Layout>
)
}
if (stats.isFile()) {
const parentPath = filePath.split('/').slice(0, -1).join('/')
const backUrl = `/?app=${appName}${parentPath ? `&file=${parentPath}` : ''}`
if (fileStats.isFile()) {
const content = readFileSync(fullPath, 'utf-8')
const language = getLanguage(basename(fullPath))
const parentPath = filePath.split('/').slice(0, -1).join('/')
return c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{`${appName}/${filePath}`}</title>
<link rel="stylesheet" href="/styles.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" media="(prefers-color-scheme: light)" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" media="(prefers-color-scheme: dark)" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
</head>
<body>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<Container>
<Header>
<Title>Code Browser</Title>
<AppName>{appName}/{filePath}</AppName>
</Header>
<BackLink href={`/?app=${appName}${parentPath ? `&file=${parentPath}` : ''}`}>
Back
</BackLink>
<CodeBlock>
<CodeHeader>{basename(fullPath)}</CodeHeader>
<pre><code class={`language-${language}`}>{content}</code></pre>
</CodeBlock>
</Container>
<script dangerouslySetInnerHTML={{ __html: 'hljs.highlightAll();' }} />
</body>
</html>
<Layout title={`${appName}/${filePath}`} subtitle={`${appName}/${filePath}`} highlight>
<BackLink href={backUrl}> Back</BackLink>
<CodeBlock>
<CodeHeader>{basename(fullPath)}</CodeHeader>
<pre><code class={`language-${language}`}>{content}</code></pre>
</CodeBlock>
</Layout>
)
}
const files = await listFiles(appPath, filePath)
const parentPath = filePath.split('/').slice(0, -1).join('/')
return c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{`${appName}${filePath ? `/${filePath}` : ''}`}</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<Container>
<Header>
<Title>Code Browser</Title>
<AppName>{appName}{filePath ? `/${filePath}` : ''}</AppName>
</Header>
{filePath && (
<BackLink href={`/?app=${appName}${parentPath ? `&file=${parentPath}` : ''}`}>
Back
</BackLink>
)}
<FileList>
{files.map(file => (
<FileItem>
<FileLink href={`/?app=${appName}&file=${file.path}`}>
{file.isDirectory ? <FolderIcon /> : <FileIconSvg />}
<span>{file.name}</span>
</FileLink>
</FileItem>
))}
</FileList>
</Container>
</body>
</html>
<Layout title={`${appName}${filePath ? `/${filePath}` : ''}`} subtitle={`${appName}${filePath ? `/${filePath}` : ''}`}>
{filePath && <BackLink href={backUrl}> Back</BackLink>}
<FileList>
{files.map(file => (
<FileItem>
<FileLink href={`/?app=${appName}&file=${file.path}`}>
{file.isDirectory ? <FolderIcon /> : <FileIconSvg />}
<span>{file.name}</span>
</FileLink>
</FileItem>
))}
</FileList>
</Layout>
)
})