mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 00:28:33 +00:00
fix(core): some comment editor ux enhancements (#13126)
fix AF-2726, AF-2729 #### PR Dependency Tree * **PR #13126** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai --> #### PR Dependency Tree * **PR #13126** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal)
This commit is contained in:
@@ -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<CommentEditorRef, CommentEditorProps>(
|
||||
[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<CommentEditorRef, CommentEditorProps>(
|
||||
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<CommentEditorRef, CommentEditorProps>(
|
||||
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<CommentEditorRef, CommentEditorProps>(
|
||||
onClick={readonly ? undefined : handleClickEditor}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
data-readonly={!!readonly}
|
||||
className={clsx(styles.container, 'comment-editor-viewport')}
|
||||
>
|
||||
|
||||
@@ -596,7 +596,7 @@ const CommentList = ({ entity }: { entity: DocCommentEntity }) => {
|
||||
const [filterState, setFilterState] = useState<CommentFilterState>({
|
||||
showResolvedComments: false,
|
||||
onlyMyReplies: false,
|
||||
onlyCurrentMode: true,
|
||||
onlyCurrentMode: false,
|
||||
});
|
||||
|
||||
const onFilterChange = useCallback(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
|
||||
@@ -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?",
|
||||
|
||||
Reference in New Issue
Block a user