import { HandlerContext, Handlers, PageProps, RouteConfig } from "$fresh/server.ts"; import { asset, Head } from "$fresh/runtime.ts"; import { decodePath, encodePath, removePrefixFromPathname, } from "../../util/util.ts"; import { join } from "path/posix.ts"; import DirList, { EntryInfo } from "../../islands/DirList.tsx"; import FileViewer from "../../islands/FileViewer.tsx"; import RenderView from "../../islands/ContentRenderer.tsx"; import { serveFile } from "http/file_server.ts"; import { Status } from "http/http_status.ts"; type DirProps = { type: "dir"; path: string; stat: Deno.FileInfo; files: EntryInfo[]; }; type FileProps = { type: "file"; path: string; stat: Deno.FileInfo; }; export type DirOrFileProps = DirProps | FileProps; type RenderOption = { fileInfo?: Deno.FileInfo; }; async function renderFile( req: Request, path: string, { fileInfo }: RenderOption = {}, ) { try { if (!fileInfo) { fileInfo = await Deno.stat(path); } if (fileInfo.isDirectory) { // if index.html exists, serve it. // otherwise, serve a directory listing. const indexPath = join(path, "/index.html"); try { await Deno.stat(indexPath); const res = await serveFile(req, indexPath); return res; } catch (e) { if (e instanceof Deno.errors.NotFound) { const list: Deno.DirEntry[] = []; for await (const entry of Deno.readDir(path)) { list.push(entry); } return new Response( JSON.stringify( list, ), { headers: { "content-type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE", "Access-Control-Allow-Headers": "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With", }, status: 200, }, ); } } } const res = await serveFile(req, path, { fileInfo, }); return res; } catch (e) { if (e instanceof Deno.errors.NotFound) { return new Response("Not Found", { status: 404, }); } throw e; } } async function renderPage( _req: Request, path: string, ctx: HandlerContext, { fileInfo }: RenderOption = {}, ) { try { if (!fileInfo) { fileInfo = await Deno.stat(path); } if (fileInfo.isDirectory) { const filesIter = await Deno.readDir(path); const files: EntryInfo[] = []; for await (const file of filesIter) { const fileStat = await Deno.stat(join(path, file.name)); files.push({ ...file, lastModified: fileStat.mtime ? new Date(fileStat.mtime) : undefined, size: fileStat.size, }); } return await ctx.render({ type: "dir", fileInfo, files, path, }); } else { return await ctx.render({ type: "file", fileInfo, path, }); } } catch (e) { if (e instanceof Deno.errors.NotFound) { return await ctx.renderNotFound(); } throw e; } } async function GET(req: Request, ctx: HandlerContext): Promise { const url = new URL(req.url); const path = removePrefixFromPathname(decodePath(url.pathname), "/dir"); const fileInfo = await Deno.stat(path); if (fileInfo.isFile && url.pathname.endsWith("/")) { url.pathname = url.pathname.slice(0, -1); return Response.redirect(url, Status.TemporaryRedirect); } if ((!fileInfo.isFile) && (!url.pathname.endsWith("/"))) { url.pathname += "/"; return Response.redirect(url, Status.TemporaryRedirect); } if (url.searchParams.has("pretty")) { return await renderPage(req, path, ctx, { fileInfo }); } else { return await renderFile(req, path, { fileInfo }); } } export const handler: Handlers = { GET, }; function isImageFile(path: string) { return /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico|tiff)$/i.test(path); } function searchFiles( path: EntryInfo[], fn: (path: EntryInfo) => boolean, opt: { order?: (a: EntryInfo, b: EntryInfo) => number } = {}, ) { const candiate = path.filter(fn); if (candiate.length > 0) { if (opt.order) { candiate.sort(opt.order); } return candiate[0]; } return null; } export default function DirLists(props: PageProps) { const data = props.data; let cover = null, index = null, content = null; if (data.type === "dir") { cover = searchFiles(data.files, (f) => isImageFile(f.name)); index = searchFiles(data.files, (f) => f.name === "index.html"); const contentFilenameCandidate = [ "README.md", "readme.md", "README.txt", "readme.txt", ]; const contentFilenameCandidateSet = new Set(contentFilenameCandidate); content = searchFiles( data.files, (f) => contentFilenameCandidateSet.has(f.name), { order: (a, b) => { const aIndex = contentFilenameCandidate.indexOf(a.name); const bIndex = contentFilenameCandidate.indexOf(b.name); return (aIndex - bIndex); }, }, ); } return ( <> Simple file server : {data.path}
{data.type === "dir" ? : } {index ? ( {cover ? ( ) : ( Index )} ) : null} {content ? (
) : null}
); } export const config : RouteConfig = { routeOverride: "/dir/**{/}?" }