Last active
July 8, 2025 07:26
-
-
Save Tsukina-7mochi/b71af2a4f3ec81d2bc6f9df670a646e3 to your computer and use it in GitHub Desktop.
Preview GitHub-Flavored Markdown with Deno
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // deno serve -R https://gist.githubusercontent.com/Tsukina-7mochi/b71af2a4f3ec81d2bc6f9df670a646e3/raw/21206f905e2ef7d83a1022686d10af56b6bc6d25/preview-gfm.ts | |
| // deno serve --port [port] to specify port | |
| // deno serve --open to open in browser | |
| import { parseArgs } from "jsr:@std/cli/parse-args"; | |
| import * as path from "jsr:@std/path"; | |
| import { marked } from "npm:marked"; | |
| import { gfmHeadingId } from "npm:marked-gfm-heading-id"; | |
| const args = parseArgs(Deno.args); | |
| const argpath = path.resolve(`${args._[0] ?? "."}`); | |
| const stat = await Deno.stat(argpath); | |
| const basedir = stat.isDirectory ? argpath : path.dirname(argpath); | |
| const hasIndexFile = stat.isFile; | |
| console.log(basedir); | |
| marked.use({ | |
| async: true, | |
| gfm: true, | |
| }, gfmHeadingId()); | |
| async function renderToHTML(filepath: string): Promise<string> { | |
| const content = await Deno.readTextFile(filepath); | |
| const parsed = await marked.parse(content); | |
| return `<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <link href="https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet/dist/gfm.min.css" rel="stylesheet" /> | |
| <style> | |
| body { | |
| font-size: 16px; | |
| max-width: 894px; | |
| margin: 0 auto; | |
| border-left: 1px solid #d1d9e0; | |
| border-right: 1px solid #d1d9e0; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| ${parsed} | |
| </body> | |
| </html>`; | |
| } | |
| function extToContentType(ext: string): string { | |
| if (ext === "html") { | |
| return "text/html"; | |
| } else if (ext === "css") { | |
| return "text/css"; | |
| } else if (ext === "js" || ext === "mjs") { | |
| return "application/javascript"; | |
| } else if (ext === "json") { | |
| return "application/json"; | |
| } else if (ext === "png") { | |
| return "image/png"; | |
| } else if (ext === "jpg" || ext === "jpeg") { | |
| return "image/jpeg"; | |
| } else if (ext === "gif") { | |
| return "image/gif"; | |
| } else if (ext === "svg") { | |
| return "image/svg+xml"; | |
| } else if (ext === "webp") { | |
| return "image/webp"; | |
| } else if (ext === "txt") { | |
| return "text/plain"; | |
| } | |
| return "application/octet-stream"; | |
| } | |
| export default { | |
| async fetch(req: Request): Promise<Response> { | |
| try { | |
| const url = new URL(req.url); | |
| const pathname = url.pathname; | |
| if (pathname === "/" && hasIndexFile) { | |
| return new Response( | |
| await renderToHTML(argpath), | |
| { | |
| headers: { "Content-Type": "text/html" }, | |
| status: 200, | |
| }, | |
| ); | |
| } | |
| const filepath = path.join(basedir, `.${pathname}`); | |
| const ext = path.extname(filepath).toLowerCase(); | |
| if (ext === ".md") { | |
| return new Response( | |
| await renderToHTML(filepath), | |
| { | |
| headers: { "Content-Type": "text/html" }, | |
| status: 200, | |
| }, | |
| ); | |
| } | |
| const content = await Deno.readFile(filepath); | |
| return new Response(content, { | |
| headers: { "Content-Type": extToContentType(ext) }, | |
| status: 200, | |
| }); | |
| } catch (error) { | |
| if (error instanceof Deno.errors.NotFound) { | |
| console.warn(error.message); | |
| return new Response("File not found", { status: 404 }); | |
| } | |
| console.error(error); | |
| return new Response("Internal server error: see console for details.", { | |
| status: 500, | |
| }); | |
| } | |
| }, | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment