fix(editor): unify image upload flow for paste and button
Paste/drop and attachment button previously used separate upload paths. The button uploaded first then called insertFile (which replaced the current selection), while paste inserted a blob preview first. This caused the second image to overwrite the first when both were used. Now both paths share the same flow via uploadAndInsertFile(): blob preview with uploading animation → background upload → replace URL. - Extract shared uploadAndInsertFile() function - Replace insertFile ref method with uploadFile (inserts at doc end) - Simplify FileUploadButton to onSelect(file) — no more onUpload/onInsert - Wire onUploadFile in comment edit mode (was missing, upload was no-op) - Unify image border-radius CSS for both editing and readonly modes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
565afed447
commit
7cc4e63e0e
8 changed files with 88 additions and 109 deletions
|
|
@ -228,14 +228,14 @@ function CommentRow({
|
|||
defaultValue={entry.content ?? ""}
|
||||
placeholder="Edit comment..."
|
||||
onSubmit={saveEdit}
|
||||
onUploadFile={(file) => uploadWithToast(file, { issueId })}
|
||||
debounceMs={100}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-2">
|
||||
<FileUploadButton
|
||||
size="sm"
|
||||
onUpload={(file) => uploadWithToast(file, { issueId })}
|
||||
onInsert={(result, isImage) => editEditorRef.current?.insertFile(result.filename, result.link, isImage)}
|
||||
onSelect={(file) => editEditorRef.current?.uploadFile(file)}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm" variant="ghost" onClick={cancelEdit}>Cancel</Button>
|
||||
|
|
@ -441,8 +441,7 @@ function CommentCard({
|
|||
<div className="flex items-center justify-between mt-2">
|
||||
<FileUploadButton
|
||||
size="sm"
|
||||
onUpload={(file) => uploadWithToast(file, { issueId })}
|
||||
onInsert={(result, isImage) => editEditorRef.current?.insertFile(result.filename, result.link, isImage)}
|
||||
onSelect={(file) => editEditorRef.current?.uploadFile(file)}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm" variant="ghost" onClick={cancelEdit}>Cancel</Button>
|
||||
|
|
|
|||
|
|
@ -14,15 +14,12 @@ interface CommentInputProps {
|
|||
|
||||
function CommentInput({ issueId, onSubmit }: CommentInputProps) {
|
||||
const editorRef = useRef<RichTextEditorRef>(null);
|
||||
const attachmentIdsRef = useRef<string[]>([]);
|
||||
const [isEmpty, setIsEmpty] = useState(true);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const { uploadWithToast, uploading } = useFileUpload();
|
||||
const { uploadWithToast } = useFileUpload();
|
||||
|
||||
const handleUpload = async (file: File) => {
|
||||
const result = await uploadWithToast(file, { issueId });
|
||||
if (result) attachmentIdsRef.current.push(result.id);
|
||||
return result;
|
||||
return await uploadWithToast(file, { issueId });
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
|
@ -30,10 +27,8 @@ function CommentInput({ issueId, onSubmit }: CommentInputProps) {
|
|||
if (!content || submitting) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const ids = attachmentIdsRef.current.length > 0 ? [...attachmentIdsRef.current] : undefined;
|
||||
await onSubmit(content, ids);
|
||||
await onSubmit(content);
|
||||
editorRef.current?.clearContent();
|
||||
attachmentIdsRef.current = [];
|
||||
setIsEmpty(true);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
|
|
@ -55,11 +50,7 @@ function CommentInput({ issueId, onSubmit }: CommentInputProps) {
|
|||
<div className="absolute bottom-1 right-1.5 flex items-center gap-1">
|
||||
<FileUploadButton
|
||||
size="sm"
|
||||
onUpload={handleUpload}
|
||||
onInsert={(result, isImage) =>
|
||||
editorRef.current?.insertFile(result.filename, result.link, isImage)
|
||||
}
|
||||
disabled={uploading}
|
||||
onSelect={(file) => editorRef.current?.uploadFile(file)}
|
||||
/>
|
||||
<Button
|
||||
size="icon-xs"
|
||||
|
|
|
|||
|
|
@ -667,8 +667,7 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
|
|||
)}
|
||||
<FileUploadButton
|
||||
size="sm"
|
||||
onUpload={handleDescriptionUpload}
|
||||
onInsert={(result, isImage) => descEditorRef.current?.insertFile(result.filename, result.link, isImage)}
|
||||
onSelect={(file) => descEditorRef.current?.uploadFile(file)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -35,11 +35,10 @@ function ReplyInput({
|
|||
}: ReplyInputProps) {
|
||||
const editorRef = useRef<RichTextEditorRef>(null);
|
||||
const measureRef = useRef<HTMLDivElement>(null);
|
||||
const attachmentIdsRef = useRef<string[]>([]);
|
||||
const [isEmpty, setIsEmpty] = useState(true);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const { uploadWithToast, uploading } = useFileUpload();
|
||||
const { uploadWithToast } = useFileUpload();
|
||||
|
||||
useEffect(() => {
|
||||
const el = measureRef.current;
|
||||
|
|
@ -53,9 +52,7 @@ function ReplyInput({
|
|||
}, []);
|
||||
|
||||
const handleUpload = async (file: File) => {
|
||||
const result = await uploadWithToast(file, { issueId });
|
||||
if (result) attachmentIdsRef.current.push(result.id);
|
||||
return result;
|
||||
return await uploadWithToast(file, { issueId });
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
|
@ -63,10 +60,8 @@ function ReplyInput({
|
|||
if (!content || submitting) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const ids = attachmentIdsRef.current.length > 0 ? [...attachmentIdsRef.current] : undefined;
|
||||
await onSubmit(content, ids);
|
||||
await onSubmit(content);
|
||||
editorRef.current?.clearContent();
|
||||
attachmentIdsRef.current = [];
|
||||
setIsEmpty(true);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
|
|
@ -105,11 +100,7 @@ function ReplyInput({
|
|||
<div className="absolute bottom-0 right-0 flex items-center gap-1 text-muted-foreground transition-colors group-focus-within/editor:text-foreground">
|
||||
<FileUploadButton
|
||||
size="sm"
|
||||
onUpload={handleUpload}
|
||||
onInsert={(result, isImage) =>
|
||||
editorRef.current?.insertFile(result.filename, result.link, isImage)
|
||||
}
|
||||
disabled={uploading}
|
||||
onSelect={(file) => editorRef.current?.uploadFile(file)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue