From c890fa9cf5e9ad965988562bc66c414484c10fe4 Mon Sep 17 00:00:00 2001 From: SURESH CHOUKSEY Date: Fri, 29 May 2026 00:19:58 +0530 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E9=9C=80=E6=B1=82=E8=AF=84?= =?UTF-8?q?=E4=BC=B0=E9=A1=B5=E9=9D=A2=E6=94=AF=E6=8C=81=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E3=80=81=E8=A7=A3=E6=9E=90=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/File.ts | 30 +++++++++++++++++++++ pages/dashboard/project/[id].tsx | 45 ++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 models/File.ts diff --git a/models/File.ts b/models/File.ts new file mode 100644 index 0000000..896e814 --- /dev/null +++ b/models/File.ts @@ -0,0 +1,30 @@ +import { toggle } from 'mobx-restful'; +import { blobOf, uniqueID } from 'web-utility'; + +import userStore from './User'; + +export interface SignedLink { + getLink: string; + putLink: string; +} + +export class FileModel { + client = userStore.client; + + @toggle('uploading') + async upload(file: string | Blob) { + if (typeof file === 'string') { + const name = file.split('/').pop()!; + + file = new File([await blobOf(file)], name); + } + const { body } = await this.client.post( + `file/signed-link/${file instanceof File ? file.name : uniqueID()}`, + ); + await this.client.put(body!.putLink, file, { 'Content-Type': file.type }); + + return body!.getLink; + } +} + +export default new FileModel(); diff --git a/pages/dashboard/project/[id].tsx b/pages/dashboard/project/[id].tsx index 42b0e11..c1fa132 100644 --- a/pages/dashboard/project/[id].tsx +++ b/pages/dashboard/project/[id].tsx @@ -4,7 +4,7 @@ import { marked } from 'marked'; import { observer } from 'mobx-react'; import { ObservedComponent, reaction } from 'mobx-react-helper'; import { compose, JWTProps, jwtVerifier, RouteProps, router } from 'next-ssr-middleware'; -import { FormEvent, KeyboardEventHandler } from 'react'; +import { ChangeEvent, ClipboardEvent, DragEvent, FormEvent, KeyboardEventHandler } from 'react'; import { formToJSON, scrollTo, sleep } from 'web-utility'; import { PageHead } from '../../../components/PageHead'; @@ -12,6 +12,7 @@ import { EvaluationDisplay } from '../../../components/Project/EvaluationDisplay import { ScrollList } from '../../../components/ScrollList'; import { SessionBox } from '../../../components/User/SessionBox'; import { ConsultMessageModel, ProjectModel } from '../../../models/ProjectEvaluation'; +import fileStore from '../../../models/File'; import { i18n, I18nContext } from '../../../models/Translation'; type ProjectEvaluationPageProps = JWTProps & RouteProps<{ id: string }>; @@ -76,6 +77,40 @@ export default class ProjectEvaluationPage extends ObservedComponent< ); }; + handleFiles = async (files: File[]) => { + for (const file of files) { + const URI = await fileStore.upload(file); + const content = file.type.startsWith('image/') + ? `![${file.name}](${URI})` + : `[${file.name}](${URI})`; + await this.messageStore.updateOne({ content }); + } + }; + + handleFileSelect = (event: ChangeEvent) => { + const files = Array.from(event.target.files || []); + if (files.length) { + this.handleFiles(files); + event.target.value = ''; + } + }; + + handlePasteDrop = (event: ClipboardEvent | DragEvent) => { + const list = + event.type === 'paste' + ? [...(event as ClipboardEvent).clipboardData.items] + : [...(event as DragEvent).dataTransfer.items]; + + const files = list + .map(item => item.getAsFile()) + .filter((file): file is File => file !== null); + + if (files.length) { + event.preventDefault(); + this.handleFiles(files); + } + }; + renderChatMessage = ( { id, content, evaluation, prototypes, createdAt, createdBy }: ConsultMessage, index = 0, @@ -175,6 +210,10 @@ export default class ProjectEvaluationPage extends ObservedComponent< className="sticky bottom-0 mx-1 mt-auto mb-1 flex items-end gap-2 p-1.5 sm:mx-0 sm:mb-0 sm:p-2" onSubmit={this.handleMessageSubmit} > + From 040b0434794b35359f6c38d2505be711b095175b Mon Sep 17 00:00:00 2001 From: SURESH CHOUKSEY Date: Sun, 31 May 2026 13:39:42 +0530 Subject: [PATCH 2/2] style: format project file upload handler --- pages/dashboard/project/[id].tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pages/dashboard/project/[id].tsx b/pages/dashboard/project/[id].tsx index c1fa132..5484c8e 100644 --- a/pages/dashboard/project/[id].tsx +++ b/pages/dashboard/project/[id].tsx @@ -101,9 +101,7 @@ export default class ProjectEvaluationPage extends ObservedComponent< ? [...(event as ClipboardEvent).clipboardData.items] : [...(event as DragEvent).dataTransfer.items]; - const files = list - .map(item => item.getAsFile()) - .filter((file): file is File => file !== null); + const files = list.map(item => item.getAsFile()).filter((file): file is File => file !== null); if (files.length) { event.preventDefault();