diff --git a/app/(pages)/explorer/viewer/page.tsx b/app/(pages)/explorer/viewer/page.tsx index a5b5364..10b5fd9 100644 --- a/app/(pages)/explorer/viewer/page.tsx +++ b/app/(pages)/explorer/viewer/page.tsx @@ -2,6 +2,7 @@ "use client"; import React, { useEffect } from "react"; +import dynamic from "next/dynamic"; import { toast } from "react-toastify"; import { parseStringPath, useExplorer } from "@/hooks/useExplorer"; @@ -15,7 +16,8 @@ import TextViewer from "@/components/viewers/text-viewer"; import ImageViewer from "@/components/viewers/image-viewer"; import VideoViewer from "@/components/viewers/video-viewer"; import AudioViewer from "@/components/viewers/audio-viewer"; -import PDFViewer from "@/components/viewers/pdf-viewer"; +/** @see https://github.com/wojtekmaj/react-pdf/issues/1811#issuecomment-2151416080 */ +const PDFViewer = dynamic(() => import("@/components/viewers/pdf-viewer"), { ssr: false }) as typeof React.Component; export function getViewer(type: string): typeof React.Component | null { switch(type) { diff --git a/app/api/fs/file/route.ts b/app/api/fs/file/route.ts index 1f10095..47d20f7 100644 --- a/app/api/fs/file/route.ts +++ b/app/api/fs/file/route.ts @@ -53,3 +53,33 @@ export async function GET(req: NextRequest) { return error(500); } } + +export async function PATCH(req: NextRequest) { + const token = req.cookies.get(tokenStorageKey)?.value; + + if(!token) return error(401); + if(!validateToken(token)) return error(403); + + const { searchParams } = new URL(req.url); + const targetPath = searchParams.get("path") ?? "/"; + const content = (await req.formData()).get("content"); + + if(!content) error(400); + + try { + if(!targetPath || !fs.existsSync(targetPath)) return error(404); + + const stat = fs.statSync(targetPath); + + if(!stat.isFile()) return error(400); + + fs.writeFileSync(targetPath, content?.toString() ?? ""); + + return packet({}); + } catch (err) { + // eslint-disable-next-line no-console + console.log("[Server: /api/fs/file] "+ err); + + return error(500); + } +} diff --git a/components/viewers/index.ts b/components/viewers/index.ts index bfbed4f..3bb8463 100644 --- a/components/viewers/index.ts +++ b/components/viewers/index.ts @@ -60,8 +60,42 @@ export default abstract class Viewer

extends Reac } } - /** @todo */ - protected async saveFile() {} + protected async saveFile(data: string) { + try { + const { status } = await axios.patch( + `/api/fs/file?path=${this.props.path}`, + { content: data }, + { + headers: { + "Content-Type": "multipart/form-data" + } + } + ); + + if(status === 200) toast.success("保存成功"); + } catch (_err) { + const err = _err as AxiosError; + const status = err.response?.status ?? 0; + + switch(status) { + case 400: + toast.error(`该路径或请求无效 (${status})`); + break; + case 401: + toast.error(`未登录 (${status})`); + break; + case 403: + toast.error(`无效的访问token (${status})`); + break; + case 404: + toast.error(`找不到该文件 (${status})`); + break; + case 500: + toast.error(`服务器内部错误 (${status})`); + break; + } + } + } public componentDidMount() { document.title = this.name +" - "+ this.props.path; diff --git a/components/viewers/text-viewer.tsx b/components/viewers/text-viewer.tsx index 8d2a846..7ba88cc 100644 --- a/components/viewers/text-viewer.tsx +++ b/components/viewers/text-viewer.tsx @@ -31,7 +31,7 @@ export default class TextViewer extends Viewer } private async handleSave() { - await this.saveFile(); + await this.saveFile(this.state.value); this.setState({ edited: false }); } diff --git a/components/viewers/video-viewer.tsx b/components/viewers/video-viewer.tsx index ceee505..8483048 100644 --- a/components/viewers/video-viewer.tsx +++ b/components/viewers/video-viewer.tsx @@ -26,7 +26,6 @@ interface VideoViewerState { } export default class VideoViewer extends Viewer { - private readonly isFlv: boolean; private blob: Blob = new Blob(); private videoRef = React.createRef(); @@ -42,8 +41,6 @@ export default class VideoViewer extends Viewer true) === "flv"; } private handlePlayButtonClick() {