Skip to main content

FileExtension

Summary

Adds a file node to the editor

Beta

Note this extension is in beta, so its API may change without a bump to the major semver version.

Usage

Installation

This extension is still in beta so is not included in the main remirror package.

import { FileExtension } from '@remirror/extension-file';

The extension is provided by the @remirror/extension-file package.

Examples

Source code
import 'remirror/styles/extension-file.css';

import { useCallback } from 'react';
import { DropCursorExtension } from 'remirror/extensions';
import { FileExtension } from '@remirror/extension-file';
import { Remirror, ThemeProvider, useRemirror } from '@remirror/react';

const Basic = (): JSX.Element => {
const extensions = useCallback(() => [new FileExtension({}), new DropCursorExtension()], []);
const { manager, state } = useRemirror({ extensions, content });

return (
<>
<p>
Default Implementation. Uses <code>FileReader.readAsDataURL</code> under the hood.
</p>
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender />
</ThemeProvider>
</>
);
};

const content = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Drag and drop one or multiple non-image files into the editor.',
},
],
},
{
type: 'file',
attrs: {
id: null,
url: 'data:text/plain;base64,SGVsbG8gd29ybGQhCgo=',
fileName: 'hello.txt',
fileType: 'text/plain',
fileSize: 14,
error: null,
},
},
],
};

export default Basic;
Source code
import 'remirror/styles/extension-file.css';

import { useCallback } from 'react';
import { DropCursorExtension } from 'remirror/extensions';
import { createObjectUrlFileUploader, FileExtension } from '@remirror/extension-file';
import { Remirror, ThemeProvider, useRemirror } from '@remirror/react';

const WithObjectUrl = (): JSX.Element => {
const extensions = useCallback(
() => [
new FileExtension({ uploadFileHandler: createObjectUrlFileUploader }),
new DropCursorExtension(),
],
[],
);
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<>
<p>
Uses <code>URL.createObjectUrl</code> under the hood.
</p>
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender />
</ThemeProvider>
</>
);
};

const content = `<p>Drag and drop one or multiple non-image files into the editor.</p>`;

export default WithObjectUrl;
Source code
import 'remirror/styles/extension-file.css';

import { useCallback } from 'react';
import { DropCursorExtension } from 'remirror/extensions';
import { createBaseuploadFileUploader, FileExtension } from '@remirror/extension-file';
import { Remirror, ThemeProvider, useRemirror } from '@remirror/react';

const WithBashupload = (): JSX.Element => {
const extensions = useCallback(
() => [
new FileExtension({ uploadFileHandler: createBaseuploadFileUploader }),
new DropCursorExtension(),
],
[],
);
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<>
<p>
Actually upload the file to <a href='https://bashupload.com/'>https://bashupload.com/</a>.{' '}
bashupload is an open-source project created by IO-Technologies. Star it on{' '}
<a href='https://github.com/IO-Technologies/bashupload'>GitHub</a>.
</p>
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender />
</ThemeProvider>
</>
);
};

const content = `<p>Drag and drop one or multiple non-image files into the editor.</p>`;

export default WithBashupload;
Source code
import 'remirror/styles/extension-file.css';

import { useCallback } from 'react';
import { DropCursorExtension } from 'remirror/extensions';
import { createSlowFileUploader, FileExtension } from '@remirror/extension-file';
import { Remirror, ThemeProvider, useRemirror } from '@remirror/react';

const WithUploadProgress = (): JSX.Element => {
const extensions = useCallback(
() => [
new FileExtension({ uploadFileHandler: createSlowFileUploader }),
new DropCursorExtension(),
],
[],
);
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<>
<p>
An example with slow uploading speed. You can see the upload progress and an abort button in
this example.
</p>
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender />
</ThemeProvider>
</>
);
};

const content = `<p>Drag and drop one or multiple non-image files into the editor.</p>`;

export default WithUploadProgress;
Source code
import 'remirror/styles/extension-file.css';

import { css } from '@emotion/css';
import { useCallback, useState } from 'react';
import { DropCursorExtension } from 'remirror/extensions';
import { hasUploadingFile } from '@remirror/core';
import { createSlowFileUploader, FileExtension } from '@remirror/extension-file';
import {
Button,
ControlledDialogComponent,
Remirror,
ThemeProvider,
useHelpers,
useRemirror,
useRemirrorContext,
} from '@remirror/react';

const WithUploadIncompleteWarning = (): JSX.Element => {
const extensions = useCallback(
() => [
new FileExtension({ uploadFileHandler: createSlowFileUploader }),
new DropCursorExtension(),
],
[],
);
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<>
<p>
Click the save button below <strong>while a file is uploading</strong>. You will see a
warning dialog.
</p>
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender='end'>
<SaveButton />
</Remirror>
</ThemeProvider>
</>
);
};

const content = `<p>Drag and drop one or multiple non-image files into the editor.</p>`;

const SaveButton: React.FC = () => {
const [showWarning, setShowWarning] = useState<boolean>(false);
const [showSaved, setShowSaved] = useState<boolean>(false);
const { getState } = useRemirrorContext();
const { getJSON } = useHelpers();

const onSave = useCallback(
(forceSave = false) => {
const state = getState();

if (!forceSave) {
const isUploading = hasUploadingFile(state);

if (isUploading) {
setShowWarning(true);
return;
}
}

console.log('JSON state', getJSON(state));
setShowSaved(true);
},
[getState, getJSON],
);

return (
<>
<Button onClick={() => onSave()}>Save</Button>
<ControlledDialogComponent
visible={showWarning}
onUpdate={(v) => setShowWarning(v)}
backdrop={false}
>
<p>Warning, you have incomplete file uploads</p>
<div
className={css`
display: flex;
justify-content: space-between;
`}
>
<Button onClick={() => setShowWarning(false)}>Cancel</Button>
<Button onClick={() => onSave(true)}>Save anyway</Button>
</div>
</ControlledDialogComponent>
<ControlledDialogComponent
visible={showSaved}
onUpdate={(v) => setShowSaved(v)}
backdrop={false}
>
<p>Content saved. (See console)</p>
<div
className={css`
display: flex;
justify-content: center;
`}
>
<Button onClick={() => setShowSaved(false)}>Ok</Button>
</div>
</ControlledDialogComponent>
</>
);
};

export default WithUploadIncompleteWarning;
Source code
import 'remirror/styles/extension-file.css';

import React, { useCallback } from 'react';
import { DropCursorExtension } from 'remirror/extensions';
import { FileExtension } from '@remirror/extension-file';
import { Remirror, ThemeProvider, useCommands, useRemirror } from '@remirror/react';

const WithUploadFileButton = (): JSX.Element => {
const extensions = useCallback(() => [new FileExtension({}), new DropCursorExtension()], []);
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<>
<p>
Default Implementation. Uses <code>FileReader.readAsDataURL</code> under the hood.
</p>
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender>
<UploadFileButton />
</Remirror>
</ThemeProvider>
</>
);
};

function useFileDialog(
uploadFiles: (files: File[]) => void,
accept?: string,
): {
openFileDialog: () => void;
} {
const openFileDialog = () => {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;

if (accept) {
input.accept = accept;
}

input.addEventListener('change', (event: Event) => {
const { files } = event.target as HTMLInputElement;

if (files) {
const fileArray: File[] = [];

for (const file of files) {
fileArray.push(file);
}

uploadFiles(fileArray);
}
});

input.click();
};

return { openFileDialog };
}

const UploadFileButton: React.FC = () => {
const { uploadFiles } = useCommands();
const { openFileDialog } = useFileDialog(uploadFiles);
return <button onClick={openFileDialog}>Upload file</button>;
};

const content = `<p>Drag and drop one or multiple non-image files into the editor or click the button at the bottom.</p>`;

export default WithUploadFileButton;

API