fix code stuff
This commit is contained in:
parent
1c51427034
commit
4c1701a06d
|
|
@ -3,6 +3,7 @@ import { createThemes, define, stylesToCSS } from '@because/forge'
|
||||||
import { readdir, stat } from 'fs/promises'
|
import { readdir, stat } from 'fs/promises'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { join, extname, basename } from 'path'
|
import { join, extname, basename } from 'path'
|
||||||
|
import type { Child } from 'hono/jsx'
|
||||||
|
|
||||||
const APPS_DIR = process.env.APPS_DIR!
|
const APPS_DIR = process.env.APPS_DIR!
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ const theme = createThemes({
|
||||||
})
|
})
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
const Container = define('CodeBrowserContainer', {
|
const Container = define('Container', {
|
||||||
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
maxWidth: '1200px',
|
maxWidth: '1200px',
|
||||||
|
|
@ -63,7 +64,7 @@ const Title = define('Title', {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
})
|
})
|
||||||
|
|
||||||
const AppName = define('AppName', {
|
const Subtitle = define('Subtitle', {
|
||||||
color: theme('textMuted'),
|
color: theme('textMuted'),
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
marginTop: '5px',
|
marginTop: '5px',
|
||||||
|
|
@ -141,7 +142,7 @@ const CodeHeader = define('CodeHeader', {
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
})
|
})
|
||||||
|
|
||||||
const Error = define('Error', {
|
const ErrorBox = define('ErrorBox', {
|
||||||
color: theme('error'),
|
color: theme('error'),
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
backgroundColor: theme('errorBg'),
|
backgroundColor: theme('errorBg'),
|
||||||
|
|
@ -176,14 +177,20 @@ const FileIconSvg = () => (
|
||||||
</FileIcon>
|
</FileIcon>
|
||||||
)
|
)
|
||||||
|
|
||||||
const themeScript = `
|
const initScript = `
|
||||||
(function() {
|
(function() {
|
||||||
var theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
var theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
document.body.setAttribute('data-theme', theme);
|
document.body.setAttribute('data-theme', theme);
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
|
||||||
document.body.setAttribute('data-theme', e.matches ? 'dark' : 'light');
|
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 = `
|
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, {
|
app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
|
||||||
'Content-Type': 'text/css; charset=utf-8',
|
'Content-Type': 'text/css; charset=utf-8',
|
||||||
}))
|
}))
|
||||||
|
|
@ -201,17 +246,11 @@ async function listFiles(appPath: string, subPath: string = '') {
|
||||||
const fullPath = join(appPath, subPath)
|
const fullPath = join(appPath, subPath)
|
||||||
const entries = await readdir(fullPath, { withFileTypes: true })
|
const entries = await readdir(fullPath, { withFileTypes: true })
|
||||||
|
|
||||||
const items = await Promise.all(
|
const items = entries.map(entry => ({
|
||||||
entries.map(async entry => {
|
|
||||||
const itemPath = join(fullPath, entry.name)
|
|
||||||
const stats = await stat(itemPath)
|
|
||||||
return {
|
|
||||||
name: entry.name,
|
name: entry.name,
|
||||||
isDirectory: entry.isDirectory(),
|
isDirectory: entry.isDirectory(),
|
||||||
path: subPath ? `${subPath}/${entry.name}` : entry.name,
|
path: subPath ? `${subPath}/${entry.name}` : entry.name,
|
||||||
}
|
}))
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return items.sort((a, b) => {
|
return items.sort((a, b) => {
|
||||||
if (a.isDirectory !== b.isDirectory) {
|
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, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLanguage(filename: string): string {
|
function getLanguage(filename: string): string {
|
||||||
const ext = extname(filename).toLowerCase()
|
const ext = extname(filename).toLowerCase()
|
||||||
const langMap: Record<string, string> = {
|
const langMap: Record<string, string> = {
|
||||||
|
|
@ -254,23 +284,9 @@ app.get('/', async c => {
|
||||||
|
|
||||||
if (!appName) {
|
if (!appName) {
|
||||||
return c.html(
|
return c.html(
|
||||||
<html>
|
<Layout title="Code Browser">
|
||||||
<head>
|
<ErrorBox>Please specify an app name with ?app=<name></ErrorBox>
|
||||||
<meta charset="UTF-8" />
|
</Layout>
|
||||||
<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=<name></Error>
|
|
||||||
</Container>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,113 +296,48 @@ app.get('/', async c => {
|
||||||
await stat(appPath)
|
await stat(appPath)
|
||||||
} catch {
|
} catch {
|
||||||
return c.html(
|
return c.html(
|
||||||
<html>
|
<Layout title="Code Browser">
|
||||||
<head>
|
<ErrorBox>App "{appName}" not found</ErrorBox>
|
||||||
<meta charset="UTF-8" />
|
</Layout>
|
||||||
<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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullPath = join(appPath, filePath)
|
const fullPath = join(appPath, filePath)
|
||||||
let stats
|
let fileStats
|
||||||
|
|
||||||
try {
|
try {
|
||||||
stats = await stat(fullPath)
|
fileStats = await stat(fullPath)
|
||||||
} catch {
|
} catch {
|
||||||
return c.html(
|
return c.html(
|
||||||
<html>
|
<Layout title="Code Browser" subtitle={appName}>
|
||||||
<head>
|
<ErrorBox>Path "{filePath}" not found</ErrorBox>
|
||||||
<meta charset="UTF-8" />
|
</Layout>
|
||||||
<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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 content = readFileSync(fullPath, 'utf-8')
|
||||||
const language = getLanguage(basename(fullPath))
|
const language = getLanguage(basename(fullPath))
|
||||||
const parentPath = filePath.split('/').slice(0, -1).join('/')
|
|
||||||
|
|
||||||
return c.html(
|
return c.html(
|
||||||
<html>
|
<Layout title={`${appName}/${filePath}`} subtitle={`${appName}/${filePath}`} highlight>
|
||||||
<head>
|
<BackLink href={backUrl}>← Back</BackLink>
|
||||||
<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>
|
<CodeBlock>
|
||||||
<CodeHeader>{basename(fullPath)}</CodeHeader>
|
<CodeHeader>{basename(fullPath)}</CodeHeader>
|
||||||
<pre><code class={`language-${language}`}>{content}</code></pre>
|
<pre><code class={`language-${language}`}>{content}</code></pre>
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</Container>
|
</Layout>
|
||||||
<script dangerouslySetInnerHTML={{ __html: 'hljs.highlightAll();' }} />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = await listFiles(appPath, filePath)
|
const files = await listFiles(appPath, filePath)
|
||||||
const parentPath = filePath.split('/').slice(0, -1).join('/')
|
|
||||||
|
|
||||||
return c.html(
|
return c.html(
|
||||||
<html>
|
<Layout title={`${appName}${filePath ? `/${filePath}` : ''}`} subtitle={`${appName}${filePath ? `/${filePath}` : ''}`}>
|
||||||
<head>
|
{filePath && <BackLink href={backUrl}>← Back</BackLink>}
|
||||||
<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>
|
<FileList>
|
||||||
{files.map(file => (
|
{files.map(file => (
|
||||||
<FileItem>
|
<FileItem>
|
||||||
|
|
@ -397,9 +348,7 @@ app.get('/', async c => {
|
||||||
</FileItem>
|
</FileItem>
|
||||||
))}
|
))}
|
||||||
</FileList>
|
</FileList>
|
||||||
</Container>
|
</Layout>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user