diff --git a/apps/code/20260130-000000/src/server/index.tsx b/apps/code/20260130-000000/src/server/index.tsx
index 74936bb..4b2d3d7 100644
--- a/apps/code/20260130-000000/src/server/index.tsx
+++ b/apps/code/20260130-000000/src/server/index.tsx
@@ -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 = () => (
)
-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 (
+
+
+
+
+ {title}
+
+ {highlight && (
+ <>
+
+
+
+ >
+ )}
+
+
+
+
+
+ Code Browser
+ {subtitle && {subtitle}}
+
+ {children}
+
+ {highlight && }
+
+
+ )
+}
+
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, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
-}
-
function getLanguage(filename: string): string {
const ext = extname(filename).toLowerCase()
const langMap: Record = {
@@ -254,23 +284,9 @@ app.get('/', async c => {
if (!appName) {
return c.html(
-
-
-
-
- Code Browser
-
-
-
-
-
-
- Please specify an app name with ?app=<name>
-
-
-
+
+ Please specify an app name with ?app=<name>
+
)
}
@@ -280,126 +296,59 @@ app.get('/', async c => {
await stat(appPath)
} catch {
return c.html(
-
-
-
-
- Code Browser
-
-
-
-
-
-
- App "{appName}" not found
-
-
-
+
+ App "{appName}" not found
+
)
}
const fullPath = join(appPath, filePath)
- let stats
+ let fileStats
try {
- stats = await stat(fullPath)
+ fileStats = await stat(fullPath)
} catch {
return c.html(
-
-
-
-
- Code Browser
-
-
-
-
-
-
- Path "{filePath}" not found in app "{appName}"
-
-
-
+
+ Path "{filePath}" not found
+
)
}
- 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(
-
-
-
-
- {`${appName}/${filePath}`}
-
-
-
-
-
-
-
-
-
- Code Browser
- {appName}/{filePath}
-
-
- ← Back
-
-
- {basename(fullPath)}
- {content}
-
-
-
-
-
+
+ ← Back
+
+ {basename(fullPath)}
+ {content}
+
+
)
}
const files = await listFiles(appPath, filePath)
- const parentPath = filePath.split('/').slice(0, -1).join('/')
return c.html(
-
-
-
-
- {`${appName}${filePath ? `/${filePath}` : ''}`}
-
-
-
-
-
-
- Code Browser
- {appName}{filePath ? `/${filePath}` : ''}
-
- {filePath && (
-
- ← Back
-
- )}
-
- {files.map(file => (
-
-
- {file.isDirectory ? : }
- {file.name}
-
-
- ))}
-
-
-
-
+
+ {filePath && ← Back}
+
+ {files.map(file => (
+
+
+ {file.isDirectory ? : }
+ {file.name}
+
+
+ ))}
+
+
)
})