81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
import { escapeHTML } from "bun"
|
|
import { readdirSync } from "fs"
|
|
import { join, extname } from "path"
|
|
|
|
import type { CommandOutput } from "app/src/shared/types"
|
|
import { NOSE_WWW } from "app/src/config"
|
|
import { getState } from "@/session"
|
|
import { appDir } from "app/src/webapp"
|
|
import { isBinaryFile } from "app/src/utils"
|
|
import { highlight } from "../lib/highlight"
|
|
|
|
export default async function (path: string) {
|
|
const state = getState()
|
|
if (!state) return { error: "no state" }
|
|
|
|
const project = state.project
|
|
if (!project) return { error: "no project loaded" }
|
|
|
|
const root = appDir(project)
|
|
if (!root) return { error: "error loading project" }
|
|
|
|
let files: string[] = []
|
|
|
|
for (const file of readdirSync(root, { withFileTypes: true })) {
|
|
files.push(file.name)
|
|
}
|
|
|
|
if (root === NOSE_WWW) {
|
|
files = files.filter(file => file.endsWith(`${project}.ts`) || file.endsWith(`${project}.tsx`))
|
|
}
|
|
|
|
if (!files.includes(path))
|
|
return { error: `file not found: ${path}` }
|
|
|
|
return await readFile(join(root, path))
|
|
}
|
|
|
|
async function readFile(path: string): Promise<CommandOutput> {
|
|
const ext = extname(path).slice(1)
|
|
const file = Bun.file(path)
|
|
|
|
switch (ext) {
|
|
case "jpeg": case "jpg": case "png": case "gif": case "webp":
|
|
const img = await file.arrayBuffer()
|
|
return { html: `<img src="data:image/${ext};base64,${Buffer.from(img).toString('base64')}" />` }
|
|
case "mp3": case "wav":
|
|
case "mp4": case "mov": case "avi": case "mkv": case "webm":
|
|
return "Not implemented"
|
|
case "js": case "ts": case "tsx": case "json":
|
|
return {
|
|
html: `<div style='white-space: pre;'>${highlight(convertIndent(await file.text()))}</div>`
|
|
}
|
|
default:
|
|
if (await isBinaryFile(path))
|
|
throw "Cannot display binary file"
|
|
|
|
return {
|
|
html: `<div style="white-space: pre;">${escapeHTML(await file.text())}</div>`
|
|
}
|
|
}
|
|
}
|
|
|
|
// cut down 4space and 2space indents into 1space, for readability on a small screen
|
|
function convertIndent(str: string) {
|
|
const lines = str.split("\n")
|
|
|
|
// find the smallest non-zero indent
|
|
const minIndent = Math.min(
|
|
...lines
|
|
.map(l => (l.match(/^ +/) || [""])[0].length)
|
|
.filter(len => len > 0)
|
|
)
|
|
|
|
return lines
|
|
.map(line =>
|
|
line.replace(/^( +)/, (_, spaces) => " ".repeat(spaces.length / minIndent))
|
|
)
|
|
.join("\n")
|
|
}
|
|
|