diff options
author | 木杉 <zhmushan@qq.com> | 2019-12-03 08:14:25 +0800 |
---|---|---|
committer | Ry Dahl <ry@tinyclouds.org> | 2019-12-02 16:14:25 -0800 |
commit | cfa4f540baac5beaf168de3e818c882ccbd95136 (patch) | |
tree | 5a685c28a9783f5abe3337dd2ab9679c81022419 /std/http/file_server.ts | |
parent | 136b5e3da2c689b54b34c46fa41973e0ccca66ab (diff) |
better html for file_server (#3423)
Diffstat (limited to 'std/http/file_server.ts')
-rwxr-xr-x | std/http/file_server.ts | 195 |
1 files changed, 122 insertions, 73 deletions
diff --git a/std/http/file_server.ts b/std/http/file_server.ts index 8dc2ee87a..fb8f28081 100755 --- a/std/http/file_server.ts +++ b/std/http/file_server.ts @@ -7,8 +7,7 @@ // https://github.com/indexzero/http-server/blob/master/test/http-server-test.js const { ErrorKind, cwd, args, stat, readDir, open } = Deno; -import { contentType } from "../media_types/mod.ts"; -import { extname, posix } from "../path/mod.ts"; +import { posix } from "../path/mod.ts"; import { listenAndServe, ServerRequest, @@ -16,33 +15,14 @@ import { Response } from "./server.ts"; -const dirViewerTemplate = ` -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta http-equiv="X-UA-Compatible" content="ie=edge"> - <title>Deno File Server</title> - <style> - td { - padding: 0 1rem; - } - td.mode { - font-family: Courier; - } - </style> -</head> -<body> - <h1>Index of <%DIRNAME%></h1> - <table> - <tr><th>Mode</th><th>Size</th><th>Name</th></tr> - <%CONTENTS%> - </table> -</body> -</html> -`; +interface EntryInfo { + mode: string; + size: string; + url: string; + name: string; +} +const encoder = new TextEncoder(); const serverArgs = args.slice(); let CORSEnabled = false; // TODO: switch to flags if we later want to add more options @@ -58,7 +38,6 @@ const target = posix.isAbsolute(targetArg) ? posix.normalize(targetArg) : posix.join(cwd(), targetArg); const addr = `0.0.0.0:${serverArgs[2] || 4500}`; -const encoder = new TextEncoder(); function modeToString(isDir: boolean, maybeMode: number | null): string { const modeMap = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"]; @@ -99,25 +78,6 @@ function fileLenToString(len: number): string { return `${(len / base).toFixed(2)}${suffix[suffixIndex]}`; } -function createDirEntryDisplay( - name: string, - url: string, - size: number | null, - mode: number | null, - isDir: boolean -): string { - const sizeStr = size === null ? "" : "" + fileLenToString(size!); - return ` - <tr><td class="mode">${modeToString( - isDir, - mode - )}</td><td>${sizeStr}</td><td><a href="${url}">${name}${ - isDir ? "/" : "" - }</a></td> - </tr> - `; -} - async function serveFile( req: ServerRequest, filePath: string @@ -126,7 +86,7 @@ async function serveFile( const fileInfo = await stat(filePath); const headers = new Headers(); headers.set("content-length", fileInfo.len.toString()); - headers.set("content-type", contentType(extname(filePath)) || "text/plain"); + headers.set("content-type", "text/plain"); const res = { status: 200, @@ -141,12 +101,8 @@ async function serveDir( req: ServerRequest, dirPath: string ): Promise<Response> { - interface ListItem { - name: string; - template: string; - } const dirUrl = `/${posix.relative(target, dirPath)}`; - const listEntry: ListItem[] = []; + const listEntry: EntryInfo[] = []; const fileInfos = await readDir(dirPath); for (const fileInfo of fileInfos) { const filePath = posix.join(dirPath, fileInfo.name); @@ -161,29 +117,17 @@ async function serveDir( mode = (await stat(filePath)).mode; } catch (e) {} listEntry.push({ + mode: modeToString(fileInfo.isDirectory(), mode), + size: fileInfo.isFile() ? fileLenToString(fileInfo.len) : "", name: fileInfo.name, - template: createDirEntryDisplay( - fileInfo.name, - fileUrl, - fileInfo.isFile() ? fileInfo.len : null, - mode, - fileInfo.isDirectory() - ) + url: fileUrl }); } - - const formattedDirUrl = `${dirUrl.replace(/\/$/, "")}/`; - const page = new TextEncoder().encode( - dirViewerTemplate.replace("<%DIRNAME%>", formattedDirUrl).replace( - "<%CONTENTS%>", - listEntry - .sort((a, b): number => - a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1 - ) - .map((v): string => v.template) - .join("") - ) + listEntry.sort((a, b) => + a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1 ); + const formattedDirUrl = `${dirUrl.replace(/\/$/, "")}/`; + const page = encoder.encode(dirViewerTemplate(formattedDirUrl, listEntry)); const headers = new Headers(); headers.set("content-type", "text/html"); @@ -232,6 +176,111 @@ function setCORS(res: Response): void { ); } +function dirViewerTemplate(dirname: string, entries: EntryInfo[]): string { + return html` + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="X-UA-Compatible" content="ie=edge" /> + <title>Deno File Server</title> + <style> + :root { + --background-color: #fafafa; + --color: rgba(0, 0, 0, 0.87); + } + @media (prefers-color-scheme: dark) { + :root { + --background-color: #303030; + --color: #fff; + } + } + @media (min-width: 960px) { + main { + max-width: 960px; + } + body { + padding-left: 32px; + padding-right: 32px; + } + } + @media (min-width: 600px) { + main { + padding-left: 24px; + padding-right: 24px; + } + } + body { + background: var(--background-color); + color: var(--color); + font-family: "Roboto", "Helvetica", "Arial", sans-serif; + font-weight: 400; + line-height: 1.43; + font-size: 0.875rem; + } + a { + color: #2196f3; + text-decoration: none; + } + a:hover { + text-decoration: underline; + } + table th { + text-align: left; + } + table td { + padding: 12px 24px 0 0; + } + </style> + </head> + <body> + <main> + <h1>Index of ${dirname}</h1> + <table> + <tr> + <th>Mode</th> + <th>Size</th> + <th>Name</th> + </tr> + ${entries.map( + entry => html` + <tr> + <td class="mode"> + ${entry.mode} + </td> + <td> + ${entry.size} + </td> + <td> + <a href="${entry.url}">${entry.name}</a> + </td> + </tr> + ` + )} + </table> + </main> + </body> + </html> + `; +} + +function html(strings: TemplateStringsArray, ...values: unknown[]): string { + const l = strings.length - 1; + let html = ""; + + for (let i = 0; i < l; i++) { + let v = values[i]; + if (v instanceof Array) { + v = v.join(""); + } + const s = strings[i] + v; + html += s; + } + html += strings[l]; + return html; +} + listenAndServe( addr, async (req): Promise<void> => { |