eddyter
v1.3.44
Published
A configurable rich text editor component with AI-powered features and API key authentication.
Readme
Eddyter
A configurable rich text editor component with AI-powered features and API key authentication.
Installation
npm install eddyter
# or
yarn add eddyterFeatures
- Rich text editor with extensive formatting options
- API key authentication
- Configurable UI components (toolbar, floating menu)
- HTML view option
- Support for tables, images, links, and more
- AI chat integration (for premium plans)
- Environment-based API configuration
React Native Integration
You can use Eddyter in React Native applications via WebView by loading a deployed version of the editor.
Prerequisites
npm install react-native-webview
# or
yarn add react-native-webviewRichTextEditor Component
Create a reusable RichTextEditor component that wraps the WebView:
import React, { useRef, useState, useCallback } from 'react';
import { View, ActivityIndicator, Text, StyleSheet } from 'react-native';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
interface RichTextEditorProps {
editorBaseUrl: string;
apiKey: string;
initialContent?: string;
theme?: 'light' | 'dark';
style?: object;
onChange?: (content: string) => void;
onReady?: () => void;
onAuthSuccess?: () => void;
onAuthError?: (error: string) => void;
}
interface WebViewMessage {
type: string;
payload?: Record<string, unknown>;
}
export const RichTextEditor: React.FC<RichTextEditorProps> = ({
editorBaseUrl,
apiKey,
initialContent,
theme = 'light',
style,
onChange,
onReady,
onAuthSuccess,
onAuthError,
}) => {
const webViewRef = useRef<WebView>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const buildEditorUrl = () => {
const baseUrl = editorBaseUrl.replace(/\/$/, '');
const params = new URLSearchParams();
if (apiKey) params.append('apiKey', apiKey);
if (theme) params.append('theme', theme);
return `${baseUrl}?${params.toString()}`;
};
const handleMessage = useCallback((event: WebViewMessageEvent) => {
try {
const message: WebViewMessage = JSON.parse(event.nativeEvent.data);
switch (message.type) {
case 'EDITOR_READY':
setIsLoading(false);
onReady?.();
// Send initial content after editor is ready
if (initialContent && webViewRef.current) {
webViewRef.current.postMessage(
JSON.stringify({
type: 'SET_CONTENT',
payload: { content: initialContent },
})
);
}
break;
case 'CONTENT_CHANGE':
onChange?.(message.payload?.content as string || '');
break;
case 'AUTH_SUCCESS':
onAuthSuccess?.();
break;
case 'AUTH_ERROR':
onAuthError?.(message.payload?.error as string);
break;
}
} catch (e) {
console.warn('[RichTextEditor] Failed to parse message:', e);
}
}, [onChange, onReady, onAuthSuccess, onAuthError, initialContent]);
const handleError = useCallback((syntheticEvent: any) => {
const { nativeEvent } = syntheticEvent;
setError(nativeEvent.description || 'Failed to load editor');
setIsLoading(false);
}, []);
if (error) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Failed to load editor</Text>
<Text style={styles.errorDetail}>{error}</Text>
</View>
);
}
return (
<View style={[styles.container, style]}>
<WebView
ref={webViewRef}
source={{ uri: buildEditorUrl() }}
style={styles.webview}
onMessage={handleMessage}
onError={handleError}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={false}
scalesPageToFit={true}
allowsInlineMediaPlayback={true}
keyboardDisplayRequiresUserAction={false}
/>
{isLoading && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color="#007AFF" />
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1 },
webview: { flex: 1, backgroundColor: 'transparent' },
loadingOverlay: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
},
errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
errorText: { fontSize: 18, fontWeight: 'bold', color: '#FF3B30' },
errorDetail: { fontSize: 14, color: '#666', marginTop: 8, textAlign: 'center' },
});Usage Example
import React, { useState } from 'react';
import { View, KeyboardAvoidingView, Platform } from 'react-native';
import { RichTextEditor } from './components/RichTextEditor';
const EDITOR_CONFIG = {
editorBaseUrl: 'https://your-deployed-editor-url.com',
apiKey: 'your-api-key',
};
function NoteEditorScreen() {
const [content, setContent] = useState('');
return (
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<RichTextEditor
editorBaseUrl={EDITOR_CONFIG.editorBaseUrl}
apiKey={EDITOR_CONFIG.apiKey}
theme="light"
initialContent="<p>Start writing...</p>"
onChange={setContent}
onReady={() => console.log('Editor ready')}
onAuthSuccess={() => console.log('Authenticated')}
onAuthError={(error) => console.error('Auth failed:', error)}
/>
</KeyboardAvoidingView>
);
}Message Protocol
The editor and React Native communicate via postMessage. Here are the supported message types:
| Message Type | Direction | Description |
|--------------|-----------|-------------|
| EDITOR_READY | Editor → RN | Editor has finished loading |
| CONTENT_CHANGE | Editor → RN | Content was modified (payload: { content: string }) |
| AUTH_SUCCESS | Editor → RN | API key authentication succeeded |
| AUTH_ERROR | Editor → RN | Authentication failed (payload: { error: string }) |
| SET_CONTENT | RN → Editor | Set editor content (payload: { content: string }) |
Sending Content to Editor
After receiving the EDITOR_READY message, you can programmatically set content:
webViewRef.current?.postMessage(
JSON.stringify({
type: 'SET_CONTENT',
payload: { content: '<p>New content here</p>' },
})
);Usage
Important: Importing Styles
To ensure proper styling of the editor components including tables, you must import the package's CSS:
// Import the styles in your application
import 'eddyter/style.css';Basic Setup
import React from 'react';
import {
ConfigurableEditorWithAuth,
EditorProvider,
defaultEditorConfig
} from 'eddyter';
// Import required styles
import 'eddyter/style.css';
function App() {
const apiKey = 'your-api-key'; // Replace with your actual API key
// Current logged-in user for comments
const currentUser = {
id: 'user-123',
name: 'John Doe',
email: '[email protected]',
avatar: 'https://example.com/avatar.jpg' // optional
};
const handleContentChange = (html) => {
console.log('Editor HTML content:', html);
// Handle the HTML content (save to state, send to server, etc.)
};
return (
<EditorProvider
defaultFontFamilies={defaultEditorConfig.defaultFontFamilies}
currentUser={currentUser}
>
<ConfigurableEditorWithAuth
apiKey={apiKey}
onChange={handleContentChange}
initialContent="<p>Welcome to the editor!</p>"
mentionUserList={["Alice", "Bob", "Charlie"]}
onAuthSuccess={() => console.log('Authentication successful')}
onAuthError={(error) => console.error('Authentication error:', error)}
/>
</EditorProvider>
);
}API Reference
EditorProvider
Provides authentication and configuration context for the editor.
Props
children: React nodes to renderdefaultFontFamilies: Array of font names (optional)currentUser: Current logged-in user for comments (optional) - Object withid,name,email, and optionalavatarenableLinkPreview: Enable automatic link preview on hover (optional, default:true)apiKey: API key for authentication (optional) - Required only if you need link preview to work immediately without opening the editor first
ConfigurableEditorWithAuth
The main editor component with authentication.
Props
apiKey: Your API key for authentication (required)initialContent: Initial HTML content for the editor (optional) - stringonChange: Callback function when editor content changes (optional) - receives HTML stringdefaultFontFamilies: Array of font names for the font selector (optional)mentionUserList: Array of usernames for mention functionality (optional) - Array of strings like["Alice", "Bob", "Charlie"]onAuthSuccess: Callback function when authentication is successful (optional)onAuthError: Callback function when authentication fails (optional)customVerifyKey: Custom function to verify API key (optional)
Examples
Basic Editor with Authentication
import React from 'react';
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
import 'eddyter/style.css';
function App() {
return (
<EditorProvider>
<ConfigurableEditorWithAuth
apiKey="your-api-key"
onAuthSuccess={() => console.log('Authenticated')}
onAuthError={(error) => console.error(error)}
/>
</EditorProvider>
);
}Editor with Content Handling
import React, { useState } from 'react';
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
import 'eddyter/style.css';
function App() {
const [editorContent, setEditorContent] = useState('');
// Current user (typically from your auth system)
const currentUser = {
id: 'user-456',
name: 'Jane Smith',
email: '[email protected]'
};
const handleContentChange = (html) => {
setEditorContent(html);
console.log('Current content:', html);
// You can also save to localStorage, send to API, etc.
};
const handleSave = () => {
// Save the HTML content to your backend or localStorage
localStorage.setItem('saved-content', editorContent);
console.log('Content saved!');
};
const loadSavedContent = () => {
const saved = '<p>Start writing your content here...</p>';
return saved;
};
return (
<div>
<EditorProvider currentUser={currentUser}>
<ConfigurableEditorWithAuth
apiKey="your-api-key"
initialContent={loadSavedContent()}
onChange={handleContentChange}
defaultFontFamilies={['Arial', 'Helvetica', 'Times New Roman']}
mentionUserList={['Alice', 'Bob', 'Charlie']}
onAuthSuccess={() => console.log('Ready to edit!')}
onAuthError={(error) => console.error('Auth failed:', error)}
/>
</EditorProvider>
<button onClick={handleSave} style={{ marginTop: '10px', padding: '10px' }}>
Save Content
</button>
<div style={{ marginTop: '20px', padding: '10px', background: '#f5f5f5' }}>
<h3>Current HTML Content:</h3>
<pre>{editorContent}</pre>
</div>
</div>
);
}Custom API Key Verification
import React from 'react';
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
import 'eddyter/style.css';
function App() {
const customVerifyKey = async (apiKey) => {
try {
const response = await fetch('/api/verify-key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey })
});
const data = await response.json();
return {
success: data.valid,
message: data.message || 'API key verified'
};
} catch (error) {
return {
success: false,
message: 'Failed to verify API key'
};
}
};
return (
<EditorProvider>
<ConfigurableEditorWithAuth
apiKey="your-api-key"
customVerifyKey={customVerifyKey}
onChange={(html) => console.log('Content changed:', html)}
/>
</EditorProvider>
);
}Link Preview Feature
Eddyter includes automatic link preview on hover. When users hover over links in content wrapped by EditorProvider, a preview popup shows the link's title, description, and image.
How It Works
- Inside the editor: Link preview works automatically after authentication
- Outside the editor (preview mode, saved content): Link preview works if the user has opened the editor at least once in the session (authentication stores the API key)
Preview-Only Scenarios
If your application displays saved content without ever opening the editor (e.g., a read-only view), you need to pass apiKey to EditorProvider:
import React from 'react';
import { EditorProvider } from 'eddyter';
import 'eddyter/style.css';
function ContentPreviewPage({ savedHtml }) {
return (
<EditorProvider apiKey="your-api-key">
<div dangerouslySetInnerHTML={{ __html: savedHtml }} />
</EditorProvider>
);
}Disabling Link Preview
To disable link preview entirely:
<EditorProvider enableLinkPreview={false}>
{/* Your content */}
</EditorProvider>License
This project is licensed under the MIT License - see the LICENSE file for details.
