@ecp.eth/react-editor
A React-based rich text editor for the Ethereum Comments Protocol.
The ECP React Editor provides a powerful, customizable rich text editor built on top of TipTap that supports mentions, file uploads, and seamless integration with the Ethereum Comments Protocol. For comprehensive documentation and guides, visit our documentation website.
See an example of the React Editor in action at share.ethcomments.xyz.
Installation
npm install @ecp.eth/react-editor
# or
yarn add @ecp.eth/react-editor
# or
pnpm add @ecp.eth/react-editorFeatures
- Rich Text Editing: Built on TipTap with support for paragraphs, links, and formatting
- Mentions: Support for ENS, Farcaster, and ERC-20 token mentions with autocomplete
- File Uploads: Drag-and-drop file uploads with support for images, videos, and documents
- Reference Extraction: Extract structured references from editor content
- Content Parsing: Parse plain text with references back into rich content
- Customizable Components: Fully customizable media components and themes
- TypeScript Support: Full TypeScript support with comprehensive type definitions
Quick Start (Web)
import { Editor } from "@ecp.eth/react-editor";
import { useIndexerSuggestions } from "@ecp.eth/react-editor/hooks";
import { usePinataUploadFiles } from "@ecp.eth/react-editor/hooks";
function CommentEditor() {
const suggestions = useIndexerSuggestions();
const uploads = usePinataUploadFiles();
return (
<Editor
placeholder="Write your comment..."
suggestions={suggestions}
uploads={uploads}
onBlur={() => console.log("Editor lost focus")}
/>
);
}React Native Usage
The editor also supports React Native! Import the native version and wrap your app with PortalProvider:
import { Editor, type EditorRef } from "@ecp.eth/react-editor/editor.native";
import { PortalProvider } from "@gorhom/portal";
import { useIndexerSuggestions } from "@ecp.eth/react-editor/hooks";
import { usePinataUploadFiles } from "@ecp.eth/react-editor/hooks";
function App() {
return (
<PortalProvider>
<CommentEditor />
</PortalProvider>
);
}
function CommentEditor() {
const editorRef = useRef<EditorRef>(null);
const suggestions = useIndexerSuggestions();
const uploads = usePinataUploadFiles();
return (
<Editor
ref={editorRef}
placeholder="Write your comment..."
suggestions={suggestions}
uploads={uploads}
onBlur={() => console.log("Editor lost focus")}
/>
);
}Important: The PortalProvider from @gorhom/portal is required for React Native to ensure the suggestion UI displays correctly. Make sure to wrap your app (or at least the component tree containing the editor) with PortalProvider.
Core Components
Editor
The main editor component with full rich text editing capabilities.
import { Editor, type EditorRef } from "@ecp.eth/react-editor";
const editorRef = useRef<EditorRef>(null);
<Editor
ref={editorRef}
placeholder="Write your comment..."
suggestions={suggestions}
uploads={uploads}
autoFocus={true}
onBlur={() => console.log("Editor lost focus")}
onEscapePress={() => console.log("Escape pressed")}
/>;EditorRef Methods
The editor ref provides several useful methods:
// Focus the editor
editorRef.current?.focus();
// Clear editor content
editorRef.current?.clear();
// Add files programmatically
editorRef.current?.addFiles([file1, file2]);
// Get uploaded files
const uploadedFiles = await editorRef.current?.getUploadedFiles();
// Get files pending upload
const pendingFiles = await editorRef.current?.getFilesForUpload();
// Mark file as uploaded
editorRef.current?.setFileAsUploaded(uploadedFile);
// Mark file upload as failed
editorRef.current?.setFileUploadAsFailed(fileId);Hooks
useIndexerSuggestions
Provides suggestions for ENS, Farcaster, and ERC-20 mentions using the ECP Indexer.
import { useIndexerSuggestions } from "@ecp.eth/react-editor/hooks";
const suggestions = useIndexerSuggestions({
indexerUrl: "https://api.ethcomments.xyz",
});usePinataUploadFiles
Provides file upload functionality using Pinata IPFS service.
import { usePinataUploadFiles } from "@ecp.eth/react-editor/hooks";
const uploads = usePinataUploadFiles({
pinataApiKey: "your-pinata-api-key",
pinataSecretApiKey: "your-pinata-secret-key",
});useHandleDefaultEditorValue
Handles setting default editor values with content and references.
import { useHandleDefaultEditorValue } from "@ecp.eth/react-editor/hooks";
const content = useHandleDefaultEditorValue(
defaultValue?.content,
defaultValue?.references,
);Utilities
extractReferences
Extract structured references from editor content.
import { extractReferences } from "@ecp.eth/react-editor/extract-references";
const editorContent = editorRef.current?.editor?.getJSON();
const references = extractReferences(editorContent);parse
Parse plain text with references back into rich content.
import { parse } from "@ecp.eth/react-editor/parser";
const richContent = parse(plainText, references);Configuration
File Upload Limits
// Default limits
const ALLOWED_UPLOAD_MIME_TYPES = [
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
"video/mp4",
"video/webm",
"video/avi",
"video/quicktime",
];
const MAX_UPLOAD_FILE_SIZE = 1024 * 1024 * 10; // 10MBCustom Media Components
You can customize how media files are displayed:
import { CustomImageComponent } from "./CustomImageComponent";
import { CustomVideoComponent } from "./CustomVideoComponent";
import { CustomFileComponent } from "./CustomFileComponent";
<Editor
imageComponent={CustomImageComponent}
videoComponent={CustomVideoComponent}
fileComponent={CustomFileComponent}
// ... other props
/>;Types
The package exports comprehensive TypeScript types:
import type {
EditorRef,
EditorProps,
EditorSuggestionsService,
UploadFilesService,
MentionItem,
LinkAttributes,
MentionsExtensionTheme,
} from "@ecp.eth/react-editor/types";Advanced Usage
Custom Suggestions Service
const customSuggestions: EditorSuggestionsService = {
search: async (query: string) => {
// Implement your own search logic
return [
{ type: "ens", address: "0x...", name: "example.eth" },
{ type: "farcaster", address: "0x...", fname: "example" },
];
},
};Custom Upload Service
const customUploads: UploadFilesService = {
allowedMimeTypes: ["image/png", "image/jpeg"],
maxFileSize: 5 * 1024 * 1024, // 5MB
uploadFile: async (file, callbacks) => {
// Implement your own upload logic
const response = await uploadToYourService(file);
callbacks?.onSuccess?.(uploadedFile, response);
return response;
},
uploadFiles: async (files, callbacks) => {
// Implement batch upload logic
return Promise.all(
files.map((file) => customUploads.uploadFile(file, callbacks)),
);
},
};Peer Dependencies
Web (React)
@tanstack/react-query>= 5.0.0pinata^2.5.0react18 || 19react-dom18 || 19viem^2.29.2
React Native
All web dependencies plus:
@gorhom/portal>= 1.0.14 (required for suggestion UI)react-native>= 0.81.0react-native-safe-area-context>= 5.6.0react-native-reanimated>= 4.1.1react-native-webview>= 13.16.0react-native-worklets>= 0.7.1
License
MIT