File Uploads
Galley does not upload files itself. It detects paste and drop operations, gives your app the files and editor selection, and inserts the Markdown returned by your handler.
Handle Files
Section titled “Handle Files”<GalleyEditor onFiles={async (input) => { input.report({ phase: 'progress', progress: 0.2, message: 'Uploading' });
const markdown = await Promise.all( input.files.map(async (file) => { const url = await uploadFile(file); return ``; }), );
input.report({ phase: 'progress', progress: 0.9, message: 'Finishing' }); return markdown; }}/>Return values:
| Return | Behavior |
|---|---|
string | Insert the string at the original paste/drop selection. |
string[] | Join with newlines and insert. |
null or false | Do not insert anything. Use this when your app handled the files separately. |
Input Shape
Section titled “Input Shape”interface GalleyFileInput { id: string; files: File[]; source: 'paste' | 'drop'; event: ClipboardEvent | DragEvent; view: EditorView; selection: { from: number; to: number; anchor: number; head: number }; report(update: GalleyFileStatusUpdate): void;}selection is captured when the paste or drop happens, so async uploads can finish after the user moves the cursor.
Progress and Errors
Section titled “Progress and Errors”Galley emits start before calling onFiles, complete after successful insertion, and error when the handler rejects.
<GalleyEditor onFileStatus={(status) => { console.log(status.id, status.phase, status.progress, status.message); }} onFileError={(error, input) => { reportUploadError(error, input.files); }}/>Use input.report() inside your handler for progress updates. Galley forwards those updates to onFileStatus and active upload renderers.
For app-level progress UI, keep a small upload log or status summary outside the editor:
const [uploads, setUploads] = useState<GalleyFileStatus[]>([]);
<GalleyEditor onFiles={uploadFiles} onFileStatus={(status) => { setUploads((current) => [ ...current.filter((upload) => upload.id !== status.id), status, ]); }} footer={{ after: () => { const active = uploads.filter((upload) => upload.phase !== 'complete'); return active.length ? <span>{active.length} upload active</span> : null; }, }}/>;Upload UI Modes
Section titled “Upload UI Modes”<GalleyEditor uploadInteraction="inline" /><GalleyEditor uploadInteraction="overlay" /><GalleyEditor uploadInteraction="locked" />| Mode | Behavior |
|---|---|
inline | Shows editor-resident placeholders and drop indicators. |
overlay | Adds an aggregate overlay while uploads are active. |
locked | Shows the overlay and blocks document edits until uploads finish. |
Custom Upload Renderers
Section titled “Custom Upload Renderers”Renderers return an HTMLElement or null.
<GalleyEditor uploadPlaceholderRenderer={(upload) => { const node = document.createElement('span'); node.textContent = upload.message ?? 'Uploading...'; return node; }} uploadOverlayRenderer={(uploads) => { const node = document.createElement('div'); node.textContent = `${uploads.length} upload in progress`; return node; }}/>Use a custom placeholder for inline progress when uploads are slow:
<GalleyEditor onFiles={async (input) => { for (let step = 1; step <= 4; step += 1) { await wait(250); input.report({ phase: 'progress', progress: step / 4, message: `Uploading ${input.files[0]?.name ?? 'file'}`, }); }
return input.files.map((file) => `})`); }} uploadPlaceholderRenderer={(upload) => { const node = document.createElement('span'); const percent = Math.round((upload.progress ?? 0) * 100); node.textContent = `${upload.message ?? 'Uploading'} - ${percent}%`; return node; }}/>Use uploadInteraction="locked" when partial edits during upload would confuse the workflow, such as inserting a batch of dropped photos into a fixed section:
<GalleyEditor onFiles={uploadFiles} uploadInteraction="locked" uploadOverlayRenderer={(uploads) => { const node = document.createElement('div'); const done = uploads.filter((upload) => upload.phase === 'complete').length; node.textContent = `${done}/${uploads.length} files uploaded`; return node; }}/>Keep custom upload UI concise and non-blocking unless you intentionally use locked mode. If uploads can fail, show a recovery path in your surrounding application UI through onFileError or app-owned status controls.