From ed6fde550f63ec2bb0b83ecc1e0a20f804c6aa13 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Thu, 10 Jul 2025 11:58:00 +0800 Subject: [PATCH] fix(core): some comment editor ux enhancements (#13126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix AF-2726, AF-2729 #### PR Dependency Tree * **PR #13126** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit * **New Features** * Added drag-and-drop support for file attachments in the comment editor. * Improved user feedback with notifications and toasts when downloading attachments. * **Bug Fixes** * Enhanced error handling and reporting for attachment downloads. * **Improvements** * Optimized file download process for same-origin resources to improve performance. * Updated default comment filter to show all comments, not just those for the current mode. * **Documentation** * Updated English localization to provide clearer instructions when no comments are present. #### PR Dependency Tree * **PR #13126** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) --- .../comment/comment-editor/index.tsx | 55 +++++++++++++------ .../src/components/comment/sidebar/index.tsx | 2 +- packages/frontend/core/src/utils/resource.ts | 15 +++++ packages/frontend/i18n/src/i18n.gen.ts | 2 +- packages/frontend/i18n/src/resources/en.json | 2 +- 5 files changed, 55 insertions(+), 21 deletions(-) diff --git a/packages/frontend/core/src/components/comment/comment-editor/index.tsx b/packages/frontend/core/src/components/comment/comment-editor/index.tsx index 44a4ee133..f948ea4a2 100644 --- a/packages/frontend/core/src/components/comment/comment-editor/index.tsx +++ b/packages/frontend/core/src/components/comment/comment-editor/index.tsx @@ -1,8 +1,9 @@ -import { IconButton, notify } from '@affine/component'; +import { IconButton, notify, toast } from '@affine/component'; import { LitDocEditor, type PageEditor } from '@affine/core/blocksuite/editors'; import { SnapshotHelper } from '@affine/core/modules/comment/services/snapshot-helper'; import type { CommentAttachment } from '@affine/core/modules/comment/types'; import { PeekViewService } from '@affine/core/modules/peek-view'; +import { downloadResourceWithUrl } from '@affine/core/utils/resource'; import { DebugLogger } from '@affine/debug'; import { getAttachmentFileIconRC } from '@blocksuite/affine/components/icons'; import { type RichText, selectTextModel } from '@blocksuite/affine/rich-text'; @@ -80,16 +81,6 @@ export interface CommentEditorRef { focus: () => void; } -const download = (url: string, name: string) => { - const element = document.createElement('a'); - element.setAttribute('download', name); - element.setAttribute('href', url); - element.style.display = 'none'; - document.body.append(element); - element.click(); - element.remove(); -}; - // todo: get rid of circular data changes const useSnapshotDoc = ( defaultSnapshotOrDoc: DocSnapshot | Store, @@ -313,6 +304,28 @@ export const CommentEditor = forwardRef( [addAttachments] ); + const handleDragOver = useCallback( + (e: React.DragEvent) => { + if (readonly) return; + // Prevent default to allow drop + e.preventDefault(); + }, + [readonly] + ); + + const handleDrop = useCallback( + (e: React.DragEvent) => { + if (readonly) return; + e.preventDefault(); + e.stopPropagation(); + const files = Array.from(e.dataTransfer?.files ?? []); + if (files.length) { + addAttachments(files); + } + }, + [addAttachments, readonly] + ); + const openFilePicker = useAsyncCallback(async () => { if (isUploadDisabled) return; const files = await openFilesWith('Any'); @@ -382,8 +395,6 @@ export const CommentEditor = forwardRef( if (!attachments) return; const att = attachments[index]; if (!att) return; - const url = att.url || att.localUrl; - if (!url) return; if (isImageAttachment(att)) { // translate attachment index to image index const imageAttachments = attachments.filter(isImageAttachment); @@ -391,13 +402,19 @@ export const CommentEditor = forwardRef( if (imageIndex >= 0) { handleImagePreview(imageIndex); } - } else if (att.url || att.localUrl) { + } else if (att.url) { // todo: open attachment preview. for now, just download it - download(url, att.filename ?? att.file?.name ?? 'attachment'); - notify({ - title: 'Downloading attachment', - message: 'The attachment is being downloaded to your computer.', + downloadResourceWithUrl( + att.url, + att.filename ?? att.file?.name ?? 'attachment' + ).catch(e => { + console.error('Failed to download attachment', e); + notify.error({ + title: 'Failed to download attachment', + message: e.message, + }); }); + toast('The attachment is being downloaded to your computer.'); } }, [attachments, handleImagePreview] @@ -538,6 +555,8 @@ export const CommentEditor = forwardRef( onClick={readonly ? undefined : handleClickEditor} onKeyDown={handleKeyDown} onPaste={handlePaste} + onDragOver={handleDragOver} + onDrop={handleDrop} data-readonly={!!readonly} className={clsx(styles.container, 'comment-editor-viewport')} > diff --git a/packages/frontend/core/src/components/comment/sidebar/index.tsx b/packages/frontend/core/src/components/comment/sidebar/index.tsx index 3c5ccd1cb..395bb935a 100644 --- a/packages/frontend/core/src/components/comment/sidebar/index.tsx +++ b/packages/frontend/core/src/components/comment/sidebar/index.tsx @@ -596,7 +596,7 @@ const CommentList = ({ entity }: { entity: DocCommentEntity }) => { const [filterState, setFilterState] = useState({ showResolvedComments: false, onlyMyReplies: false, - onlyCurrentMode: true, + onlyCurrentMode: false, }); const onFilterChange = useCallback( diff --git a/packages/frontend/core/src/utils/resource.ts b/packages/frontend/core/src/utils/resource.ts index 773200176..06ac38b27 100644 --- a/packages/frontend/core/src/utils/resource.ts +++ b/packages/frontend/core/src/utils/resource.ts @@ -33,7 +33,22 @@ export async function downloadBlob(blob: Blob, filename: string) { URL.revokeObjectURL(blobUrl); } +export function downloadFile(url: string, filename: string) { + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.target = '_blank'; + a.click(); +} + export async function downloadResourceWithUrl(url: string, filename: string) { + // 1. if url is not same origin, fetch it to blob and download it + // 2. otherwise, download it directly + const sameOrigin = new URL(url).origin === window.location.origin; + if (sameOrigin) { + downloadFile(url, filename); + return; + } // given input url may not have correct mime type const blob = await resourceUrlToBlob(url); if (!blob) return; diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts index e18bcbc91..e97a94dec 100644 --- a/packages/frontend/i18n/src/i18n.gen.ts +++ b/packages/frontend/i18n/src/i18n.gen.ts @@ -8239,7 +8239,7 @@ export function useAFFiNEI18N(): { */ ["com.affine.comment.comments"](): string; /** - * `No comments yet` + * `No comments yet, select content to add comment to` */ ["com.affine.comment.no-comments"](): string; /** diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 31a56aab8..060ce2145 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -2067,7 +2067,7 @@ "com.affine.migration-all-docs-notification.error": "Migration failed: {{errorMessage}}", "com.affine.migration-all-docs-notification.button": "Migrate data", "com.affine.comment.comments": "Comments", - "com.affine.comment.no-comments": "No comments yet", + "com.affine.comment.no-comments": "No comments yet, select content to add comment to", "com.affine.comment.delete.confirm.title": "Delete the thread?", "com.affine.comment.delete.confirm.description": "All comments will also be deleted, and this action cannot be undone.", "com.affine.comment.reply.delete.confirm.title": "Delete this reply?",