feat(file-upload): implement selecting and uploading file via button on the editor toolbar (#15036)

Co-authored-by: Chris Collins <chriscollins3456@gmail.com>
This commit is contained in:
purnimagarg1 2025-10-22 19:43:28 +05:30 committed by GitHub
parent afdf18e370
commit efae5559fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 122 additions and 1 deletions

View File

@ -147,7 +147,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
}
}
private updateNodeWithUrl(view: EditorView, nodeId: string, url: string): void {
public updateNodeWithUrl(view: EditorView, nodeId: string, url: string): void {
const { nodePos, nodeToUpdate } = this.findNodeById(view.state, nodeId);
if (!nodePos || !nodeToUpdate) return;

View File

@ -0,0 +1,119 @@
import { Button, Dropdown, Text, Tooltip, colors } from '@components';
import { useRemirrorContext } from '@remirror/react';
import { FileArrowUp } from 'phosphor-react';
import React, { useRef, useState } from 'react';
import styled from 'styled-components';
import {
FileDragDropExtension,
SUPPORTED_FILE_TYPES,
createFileNodeAttributes,
validateFile,
} from '@components/components/Editor/extensions/fileDragDrop';
import { CommandButton } from '@components/components/Editor/toolbar/CommandButton';
const DropdownContainer = styled.div`
box-shadow: 0 4px 12px 0 rgba(9, 1, 61, 0.12);
display: flex;
flex-direction: column;
padding: 8px;
gap: 8px;
border-radius: 12px;
width: 192px;
background: ${colors.white};
`;
const StyledText = styled(Text)`
text-align: center;
`;
const StyledButton = styled(Button)`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
`;
const FileInput = styled.input`
display: none;
`;
export const FileUploadButton = () => {
const { commands } = useRemirrorContext();
const fileInputRef = useRef<HTMLInputElement>(null);
const remirrorContext = useRemirrorContext();
const fileExtension = remirrorContext.getExtension(FileDragDropExtension);
const [showDropdown, setShowDropdown] = useState(false);
const handlebuttonClick = () => {
fileInputRef.current?.click();
};
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const input = event.target as HTMLInputElement;
const files = input.files ? Array.from(input.files) : [];
if (files.length === 0) return;
const supportedTypes = SUPPORTED_FILE_TYPES;
const { onFileUpload } = fileExtension.options;
try {
// Process files concurrently
await Promise.all(
files.map(async (file) => {
const validation = validateFile(file, { allowedTypes: supportedTypes });
if (!validation.isValid) {
// TODO: Handle validation errors
return;
}
// Create placeholder node
const attrs = createFileNodeAttributes(file);
commands.insertFileNode({ ...attrs, url: '' });
// Upload file if handler exists
if (onFileUpload) {
try {
const finalUrl = await onFileUpload(file);
fileExtension.updateNodeWithUrl(remirrorContext.view, attrs.id, finalUrl);
} catch (uploadError) {
// TODO: Handle upload errors
}
}
}),
);
} catch (error) {
// Error processing file - skip silently
} finally {
input.value = '';
setShowDropdown(false);
}
};
const dropdownContent = () => (
<DropdownContainer>
<StyledButton size="sm" onClick={handlebuttonClick}>
Choose File
</StyledButton>
<FileInput ref={fileInputRef} type="file" onChange={handleFileChange} />
<StyledText color="gray" size="sm" lineHeight="normal">
Max size: 2GB
</StyledText>
</DropdownContainer>
);
return (
<Dropdown open={showDropdown} onOpenChange={(open) => setShowDropdown(open)} dropdownRender={dropdownContent}>
<Tooltip title="Upload File">
<CommandButton
icon={<FileArrowUp size={20} color={colors.gray[1800]} />}
onClick={() => setShowDropdown(true)}
commandName="uploadFile"
/>
</Tooltip>
</Dropdown>
);
};

View File

@ -17,6 +17,7 @@ import styled from 'styled-components';
import { AddImageButton } from '@components/components/Editor/toolbar/AddImageButton';
import { AddLinkButton } from '@components/components/Editor/toolbar/AddLinkButton';
import { CommandButton } from '@components/components/Editor/toolbar/CommandButton';
import { FileUploadButton } from '@components/components/Editor/toolbar/FileUploadButton';
import { FontSizeSelect } from '@components/components/Editor/toolbar/FontSizeSelect';
import { HeadingMenu } from '@components/components/Editor/toolbar/HeadingMenu';
@ -127,6 +128,7 @@ export const Toolbar = ({ styles }: Props) => {
onClick={() => commands.createTable()}
disabled={active.table()} /* Disables nested tables */
/>
<FileUploadButton />
</InnerContainer>
</Container>
);