Compare commits
No commits in common. "dfb70c84f549946ee46448a0ff64457945cf0159" and "3cf26c7154c1167727d25506580b2b36cbf818fe" have entirely different histories.
dfb70c84f5
...
3cf26c7154
|
|
@ -88,9 +88,6 @@ const CodeHeader = define('CodeHeader', {
|
||||||
borderBottom: `1px solid ${theme('colors-border')}`,
|
borderBottom: `1px solid ${theme('colors-border')}`,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const ErrorBox = define('ErrorBox', {
|
const ErrorBox = define('ErrorBox', {
|
||||||
|
|
@ -193,46 +190,6 @@ const DownloadButton = define('DownloadButton', {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const EditButton = define('EditButton', {
|
|
||||||
base: 'button',
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
padding: '6px 12px',
|
|
||||||
backgroundColor: theme('colors-bgElement'),
|
|
||||||
color: theme('colors-text'),
|
|
||||||
border: `1px solid ${theme('colors-border')}`,
|
|
||||||
borderRadius: theme('radius-md'),
|
|
||||||
fontSize: '13px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
states: {
|
|
||||||
':hover': {
|
|
||||||
backgroundColor: theme('colors-bgHover'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const EditLink = define('EditLink', {
|
|
||||||
base: 'a',
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
padding: '6px 12px',
|
|
||||||
backgroundColor: theme('colors-bgElement'),
|
|
||||||
color: theme('colors-text'),
|
|
||||||
border: `1px solid ${theme('colors-border')}`,
|
|
||||||
borderRadius: theme('radius-md'),
|
|
||||||
fontSize: '13px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
textDecoration: 'none',
|
|
||||||
states: {
|
|
||||||
':hover': {
|
|
||||||
backgroundColor: theme('colors-bgHover'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const FolderIcon = () => (
|
const FolderIcon = () => (
|
||||||
<FileIcon viewBox="0 0 24 24">
|
<FileIcon viewBox="0 0 24 24">
|
||||||
<path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" />
|
<path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" />
|
||||||
|
|
@ -249,7 +206,6 @@ interface LayoutProps {
|
||||||
title: string
|
title: string
|
||||||
children: Child
|
children: Child
|
||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
editable?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileMemoryScript = `
|
const fileMemoryScript = `
|
||||||
|
|
@ -277,7 +233,7 @@ const fileMemoryScript = `
|
||||||
})();
|
})();
|
||||||
`
|
`
|
||||||
|
|
||||||
function Layout({ title, children, highlight, editable }: LayoutProps) {
|
function Layout({ title, children, highlight }: LayoutProps) {
|
||||||
return (
|
return (
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -285,27 +241,13 @@ function Layout({ title, children, highlight, editable }: LayoutProps) {
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
{highlight && !editable && (
|
{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.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)" />
|
<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>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{editable && (
|
|
||||||
<>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" media="(prefers-color-scheme: light)" />
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" media="(prefers-color-scheme: dark)" />
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-jsx.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-tsx.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markdown.min.js"></script>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script dangerouslySetInnerHTML={{ __html: fileMemoryScript }} />
|
<script dangerouslySetInnerHTML={{ __html: fileMemoryScript }} />
|
||||||
|
|
@ -313,7 +255,7 @@ function Layout({ title, children, highlight, editable }: LayoutProps) {
|
||||||
<Container>
|
<Container>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
{highlight && !editable && <script dangerouslySetInnerHTML={{ __html: 'hljs.highlightAll();' }} />}
|
{highlight && <script dangerouslySetInnerHTML={{ __html: 'hljs.highlightAll();' }} />}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|
@ -342,26 +284,6 @@ app.get('/raw', async c => {
|
||||||
return new Response(file)
|
return new Response(file)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post('/save', async c => {
|
|
||||||
const appName = c.req.query('app')
|
|
||||||
const version = c.req.query('version') || 'current'
|
|
||||||
const filePath = c.req.query('file')
|
|
||||||
|
|
||||||
if (!appName || !filePath) {
|
|
||||||
return c.text('Missing app or file parameter', 400)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullPath = join(APPS_DIR, appName, version, filePath)
|
|
||||||
const content = await c.req.text()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Bun.write(fullPath, content)
|
|
||||||
return c.text('OK')
|
|
||||||
} catch (err) {
|
|
||||||
return c.text(`Failed to save: ${err}`, 500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function listFiles(appPath: string, subPath: string = '') {
|
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 })
|
||||||
|
|
@ -451,29 +373,6 @@ function getLanguage(filename: string): string {
|
||||||
return langMap[ext] || 'plaintext'
|
return langMap[ext] || 'plaintext'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrismLanguage(filename: string): string {
|
|
||||||
const ext = extname(filename).toLowerCase()
|
|
||||||
const langMap: Record<string, string> = {
|
|
||||||
'.js': 'javascript',
|
|
||||||
'.jsx': 'javascript',
|
|
||||||
'.ts': 'typescript',
|
|
||||||
'.tsx': 'typescript',
|
|
||||||
'.json': 'json',
|
|
||||||
'.css': 'css',
|
|
||||||
'.html': 'html',
|
|
||||||
'.md': 'markdown',
|
|
||||||
'.sh': 'bash',
|
|
||||||
'.yml': 'yaml',
|
|
||||||
'.yaml': 'yaml',
|
|
||||||
'.py': 'python',
|
|
||||||
'.rb': 'ruby',
|
|
||||||
'.go': 'go',
|
|
||||||
'.rs': 'rust',
|
|
||||||
'.sql': 'sql',
|
|
||||||
}
|
|
||||||
return langMap[ext] || 'plaintext'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
app.get('/', async c => {
|
app.get('/', async c => {
|
||||||
const appName = c.req.query('app')
|
const appName = c.req.query('app')
|
||||||
|
|
@ -579,91 +478,12 @@ app.get('/', async c => {
|
||||||
// Text file - show with syntax highlighting
|
// Text file - show with syntax highlighting
|
||||||
const content = readFileSync(fullPath, 'utf-8')
|
const content = readFileSync(fullPath, 'utf-8')
|
||||||
const language = getLanguage(filename)
|
const language = getLanguage(filename)
|
||||||
const prismLang = getPrismLanguage(filename)
|
|
||||||
const edit = c.req.query('edit') === '1'
|
|
||||||
|
|
||||||
if (edit) {
|
|
||||||
const editorScript = `
|
|
||||||
import { CodeJar } from 'https://cdn.jsdelivr.net/npm/codejar@4.2.0/dist/codejar.js';
|
|
||||||
|
|
||||||
const editor = document.getElementById('editor');
|
|
||||||
const saveBtn = document.getElementById('save-btn');
|
|
||||||
const status = document.getElementById('save-status');
|
|
||||||
let dirty = false;
|
|
||||||
|
|
||||||
const highlight = (el) => {
|
|
||||||
Prism.highlightElement(el);
|
|
||||||
};
|
|
||||||
|
|
||||||
const jar = CodeJar(editor, highlight, { tab: ' ', addClosing: false });
|
|
||||||
|
|
||||||
// Initial highlight
|
|
||||||
highlight(editor);
|
|
||||||
|
|
||||||
jar.onUpdate(() => {
|
|
||||||
if (!dirty) {
|
|
||||||
dirty = true;
|
|
||||||
saveBtn.textContent = 'Save *';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
saveBtn.onclick = async () => {
|
|
||||||
if (!dirty) return;
|
|
||||||
saveBtn.disabled = true;
|
|
||||||
status.textContent = 'Saving...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch('/save?app=${appName}${versionParam}&file=${filePath}', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
|
||||||
body: jar.toString()
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Save failed');
|
|
||||||
dirty = false;
|
|
||||||
saveBtn.textContent = 'Save';
|
|
||||||
saveBtn.disabled = false;
|
|
||||||
status.textContent = 'Saved!';
|
|
||||||
setTimeout(() => { status.textContent = ''; }, 2000);
|
|
||||||
} catch (err) {
|
|
||||||
saveBtn.disabled = false;
|
|
||||||
status.textContent = 'Error: ' + err.message;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
||||||
e.preventDefault();
|
|
||||||
saveBtn.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
`
|
|
||||||
return c.html(
|
|
||||||
<Layout title={`${appName}/${filePath}`} editable>
|
|
||||||
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
|
||||||
<CodeBlock>
|
|
||||||
<CodeHeader>
|
|
||||||
<span>{filename}</span>
|
|
||||||
<div style="display:flex;align-items:center;gap:8px">
|
|
||||||
<span id="save-status" style="font-size:12px;font-weight:normal;color:var(--colors-textMuted)"></span>
|
|
||||||
<EditButton id="save-btn">Save</EditButton>
|
|
||||||
<EditLink href={`/?app=${appName}${versionParam}&file=${filePath}`}>Done</EditLink>
|
|
||||||
</div>
|
|
||||||
</CodeHeader>
|
|
||||||
<pre id="editor" class={`language-${prismLang}`} contenteditable style="margin:0;padding:15px;min-height:300px;outline:none">{content}</pre>
|
|
||||||
</CodeBlock>
|
|
||||||
<script type="module" dangerouslySetInnerHTML={{ __html: editorScript }} />
|
|
||||||
</Layout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.html(
|
return c.html(
|
||||||
<Layout title={`${appName}/${filePath}`} highlight>
|
<Layout title={`${appName}/${filePath}`} highlight>
|
||||||
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
<PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
|
||||||
<CodeBlock>
|
<CodeBlock>
|
||||||
<CodeHeader>
|
<CodeHeader>{filename}</CodeHeader>
|
||||||
<span>{filename}</span>
|
|
||||||
<EditLink href={`/?app=${appName}${versionParam}&file=${filePath}&edit=1`}>Edit</EditLink>
|
|
||||||
</CodeHeader>
|
|
||||||
<pre><code class={`language-${language}`}>{content}</code></pre>
|
<pre><code class={`language-${language}`}>{content}</code></pre>
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user