diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0eae572..1ca38b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ importers: web: dependencies: + dayjs: + specifier: ^1.11.13 + version: 1.11.13 jotai: specifier: ^2.11.0 version: 2.11.0(@types/react@19.0.2)(react@19.0.0) @@ -1664,6 +1667,9 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -4728,6 +4734,8 @@ snapshots: date-fns@4.1.0: {} + dayjs@1.11.13: {} + debug@4.4.0: dependencies: ms: 2.1.3 diff --git a/src/index.ts b/src/index.ts index ec38006..b076d78 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,6 +84,15 @@ app.post("/api/upload", authWithPassword, async (c) => { return c.json({ record }); }); +app.get("/api/download/:slug/message", async (c) => { + const slug = c.req.param("slug"); + const record = await getUploadRecordBySlug(c.env.DB, slug); + if (!record) return c.status(404); + return c.text(record.message); +}) + + +const RE_ASSET_SUFFIX = /\.(jpg|png|gif|avif|mp4|mov|txt|html|js|css|json|ya?ml)/; app.get("/api/download/:slug/:index", async (c) => { const slug = c.req.param("slug"); const index = c.req.param("index"); @@ -92,17 +101,13 @@ app.get("/api/download/:slug/:index", async (c) => { return c.status(404); } - if (index === "message") { - return c.text(record.message); - } - const filePath = record.files[+index]?.path; if (!filePath) { return c.status(404); } const r = await c.env.MY_BUCKET.get(filePath, { - range: c.req.header("range"), + range: c.req.raw.headers }); if (!r) { @@ -112,16 +117,17 @@ app.get("/api/download/:slug/:index", async (c) => { const basename = filePath.split("/").pop()!.replace(/\?.*/, ""); const headers = new Headers(); - headers.set("accept-ranges", "bytes"); - - if ( - !/\.(jpg|png|gif|avif|mp4|mov|txt|html|js|css|json|ya?ml)/.test(basename) - ) { - headers.set("content-disposition", `attachment; filename="${basename}"`); - } r.writeHttpMetadata(headers); - return new Response(r.body, { headers }); + if (r.range && c.req.header('Range')) { + c.status(206); + headers.set("content-range", generateContentRangeHeader(r.range, r.size)); + } + headers.set("accept-ranges", "bytes"); + + if (!RE_ASSET_SUFFIX.test(basename)) headers.set("content-disposition", `attachment; filename="${basename}"`); + headers.forEach((value, key) => c.header(key, value)); + return c.body(r.body) }); app.post("/api/delete", authWithPassword, async (c) => { diff --git a/tsconfig.json b/tsconfig.json index 934f03c..2abd41b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "@cloudflare/workers-types/2023-07-01" ], "jsx": "react-jsx", - "jsxImportSource": "hono/jsx" + "jsxImportSource": "hono/jsx", + "isolatedModules": true, }, } \ No newline at end of file diff --git a/web/package.json b/web/package.json index 6eac4e4..b41a5ec 100644 --- a/web/package.json +++ b/web/package.json @@ -8,6 +8,7 @@ "preview": "rsbuild preview" }, "dependencies": { + "dayjs": "^1.11.13", "jotai": "^2.11.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/web/rspack.config.ts b/web/rspack.config.ts index b5965e2..a2c6442 100644 --- a/web/rspack.config.ts +++ b/web/rspack.config.ts @@ -7,20 +7,6 @@ export const configRsPack: ModifyRspackConfigFn = (config, ctx) => { config.experiments ??= {}; config.experiments.css = true; - ctx.addRules({ - test: /\.(sass|scss|css)$/, - exclude: /node_modules/, - use: [ - { - loader: 'postcss-loader', - options: { - postcssOptions: {}, - }, - }, - ], - type: 'css/auto', - }); - ctx.prependPlugins([ new InjectManifest({ swSrc: './src/sw.ts', diff --git a/web/src/components/UploadRecords.tsx b/web/src/components/UploadRecords.tsx index e46c147..11f5882 100644 --- a/web/src/components/UploadRecords.tsx +++ b/web/src/components/UploadRecords.tsx @@ -1,9 +1,14 @@ import { memo, useEffect, useMemo } from 'react'; import useSWRInfinite from 'swr/infinite'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + import type { UploadRecord } from '../../../src/database'; import { fetchAPI } from '../store/auth'; -interface Props {} +dayjs.extend(relativeTime); + +interface Props { } export const UploadRecords = memo((props) => { // all records. newest first @@ -51,12 +56,12 @@ const UploadRecordItem = memo((props: { record: UploadRecord }) => { {props.record.uploader} - + - {new Date(props.record.ctime).toLocaleString()} + {dayjs(props.record.ctime).fromNow()} {!!props.record.size && ( - + {toReadableSize(props.record.size)}