@connectedxm/entity-editor 0.0.1 โ 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -106
- package/dist/index.es.js +432 -400
- package/dist/src/Editor.d.ts +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Entity Editor
|
|
2
2
|
|
|
3
|
-
A **zero-dependency**, lightweight React text editor
|
|
3
|
+
A **zero-dependency**, lightweight React text editor with intelligent entity recognition and real-time formatting using an innovative bitmap-based architecture.
|
|
4
4
|
|
|
5
5
|
## โจ Features
|
|
6
6
|
|
|
@@ -9,12 +9,13 @@ A **zero-dependency**, lightweight React text editor that intelligently recogniz
|
|
|
9
9
|
- **@mentions** (`@username`)
|
|
10
10
|
- **#interests** (`#hashtag`)
|
|
11
11
|
- **๐ Links** (`https://example.com`)
|
|
12
|
-
|
|
13
|
-
- **โก Real-time Processing**: Instant styling as you type with
|
|
12
|
+
- **๐ Rich Text Formatting**: Bold, italic, underline, strikethrough with keyboard shortcuts
|
|
13
|
+
- **โก Real-time Processing**: Instant styling as you type with optimized performance
|
|
14
14
|
- **๐๏ธ Bitmap Architecture**: Ultra-efficient character-level state tracking using bit operations
|
|
15
|
+
- **๐จ Customizable Styling**: Configurable colors and styles for all entity types
|
|
16
|
+
- **๐ Search & Autocomplete**: Built-in search functionality for mentions and interests
|
|
17
|
+
- **โจ๏ธ Keyboard Shortcuts**: Cmd/Ctrl+B/I/U/Shift+S for formatting
|
|
15
18
|
- **๐ฑ Accessible**: Built on standard `contentEditable` with proper cursor management
|
|
16
|
-
- **๐จ Customizable**: CSS classes for easy theming
|
|
17
|
-
- **โจ๏ธ Keyboard Shortcuts**: Cmd/Ctrl+B/I/U/S for formatting selected text
|
|
18
19
|
|
|
19
20
|
## ๐๏ธ Bitmap Architecture
|
|
20
21
|
|
|
@@ -58,53 +59,142 @@ npm install @connectedxm/entity-editor
|
|
|
58
59
|
### Basic Usage
|
|
59
60
|
|
|
60
61
|
```tsx
|
|
61
|
-
import React, { useState } from "react";
|
|
62
|
-
import
|
|
62
|
+
import React, { useState, useRef } from "react";
|
|
63
|
+
import {
|
|
64
|
+
Editor,
|
|
65
|
+
EditorRef,
|
|
66
|
+
Entity,
|
|
67
|
+
MarkState,
|
|
68
|
+
SearchEntity,
|
|
69
|
+
} from "@connectedxm/entity-editor";
|
|
63
70
|
|
|
64
71
|
function App() {
|
|
72
|
+
const editorRef = useRef<EditorRef>(null);
|
|
65
73
|
const [plainText, setPlainText] = useState("");
|
|
66
|
-
const [entities, setEntities] = useState([]);
|
|
67
|
-
const [
|
|
74
|
+
const [entities, setEntities] = useState<Entity[]>([]);
|
|
75
|
+
const [markState, setMarkState] = useState<MarkState>({
|
|
76
|
+
bold: false,
|
|
77
|
+
italic: false,
|
|
78
|
+
underline: false,
|
|
79
|
+
strike: false,
|
|
80
|
+
});
|
|
81
|
+
const [search, setSearch] = useState<SearchEntity | null>(null);
|
|
68
82
|
|
|
69
83
|
return (
|
|
70
84
|
<Editor
|
|
85
|
+
ref={editorRef}
|
|
71
86
|
plainText={plainText}
|
|
72
87
|
setPlainText={setPlainText}
|
|
73
88
|
entities={entities}
|
|
74
89
|
setEntities={setEntities}
|
|
90
|
+
markState={markState}
|
|
91
|
+
setMarkState={setMarkState}
|
|
75
92
|
search={search}
|
|
76
93
|
setSearch={setSearch}
|
|
77
|
-
options={{
|
|
78
|
-
|
|
94
|
+
options={{
|
|
95
|
+
mentions: true,
|
|
96
|
+
interests: true,
|
|
97
|
+
links: true,
|
|
98
|
+
}}
|
|
99
|
+
style={{
|
|
100
|
+
border: "1px solid #ccc",
|
|
101
|
+
minHeight: "100px",
|
|
102
|
+
padding: "10px",
|
|
103
|
+
}}
|
|
79
104
|
/>
|
|
80
105
|
);
|
|
81
106
|
}
|
|
82
107
|
```
|
|
83
108
|
|
|
84
|
-
### Advanced
|
|
85
|
-
|
|
86
|
-
The editor supports entity recognition options and debounced updates for performance:
|
|
109
|
+
### Advanced Usage with Custom Styling
|
|
87
110
|
|
|
88
111
|
```tsx
|
|
89
112
|
<Editor
|
|
113
|
+
ref={editorRef}
|
|
90
114
|
plainText={plainText}
|
|
91
115
|
setPlainText={setPlainText}
|
|
92
116
|
entities={entities}
|
|
93
117
|
setEntities={setEntities}
|
|
118
|
+
markState={markState}
|
|
119
|
+
setMarkState={setMarkState}
|
|
94
120
|
search={search}
|
|
95
121
|
setSearch={setSearch}
|
|
96
122
|
options={{
|
|
97
|
-
mentions: true,
|
|
98
|
-
interests: true,
|
|
99
|
-
links: true,
|
|
123
|
+
mentions: true,
|
|
124
|
+
interests: true,
|
|
125
|
+
links: true,
|
|
126
|
+
}}
|
|
127
|
+
entityStyles={{
|
|
128
|
+
mentionColor: "#1da1f2",
|
|
129
|
+
interestColor: "#ff6b35",
|
|
130
|
+
linkColor: "#9c27b0",
|
|
100
131
|
}}
|
|
101
|
-
debounceDelay={250} // Delay before processing changes
|
|
102
|
-
debounceMaxWait={500} // Maximum wait time for updates
|
|
103
132
|
className="custom-editor"
|
|
133
|
+
debug={false}
|
|
104
134
|
/>
|
|
105
135
|
```
|
|
106
136
|
|
|
107
|
-
###
|
|
137
|
+
### Using the Editor Ref API
|
|
138
|
+
|
|
139
|
+
The editor exposes imperative methods through a ref:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
const editorRef = useRef<EditorRef>(null);
|
|
143
|
+
|
|
144
|
+
// Programmatically select/replace entities
|
|
145
|
+
const handleMentionSelect = (username: string) => {
|
|
146
|
+
if (search?.type === "mention") {
|
|
147
|
+
editorRef.current?.selectEntity(
|
|
148
|
+
"mention",
|
|
149
|
+
search.startIndex,
|
|
150
|
+
search.endIndex,
|
|
151
|
+
username
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Toggle formatting programmatically
|
|
157
|
+
const makeBold = () => {
|
|
158
|
+
editorRef.current?.toggleMark("bold");
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Handling Search and Autocomplete
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
// Handle search results for mentions
|
|
166
|
+
{
|
|
167
|
+
search?.type === "mention" && (
|
|
168
|
+
<div className="autocomplete-dropdown">
|
|
169
|
+
<h4>Select a user to mention:</h4>
|
|
170
|
+
{searchResults.map((user) => (
|
|
171
|
+
<div key={user.id} onClick={() => handleMentionSelect(user.username)}>
|
|
172
|
+
{user.name} (@{user.username})
|
|
173
|
+
</div>
|
|
174
|
+
))}
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Handle search results for interests
|
|
180
|
+
{
|
|
181
|
+
search?.type === "interest" && (
|
|
182
|
+
<div className="autocomplete-dropdown">
|
|
183
|
+
<h4>Select an interest to tag:</h4>
|
|
184
|
+
{interestResults.map((interest) => (
|
|
185
|
+
<div
|
|
186
|
+
key={interest.id}
|
|
187
|
+
onClick={() => handleInterestSelect(interest.name)}
|
|
188
|
+
>
|
|
189
|
+
#{interest.name}
|
|
190
|
+
</div>
|
|
191
|
+
))}
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Styling Entities with CSS
|
|
108
198
|
|
|
109
199
|
Add CSS to style the recognized entities:
|
|
110
200
|
|
|
@@ -117,34 +207,35 @@ Add CSS to style the recognized entities:
|
|
|
117
207
|
}
|
|
118
208
|
|
|
119
209
|
.activity-interest {
|
|
120
|
-
color: #
|
|
210
|
+
color: #ff6b35;
|
|
121
211
|
font-weight: 500;
|
|
122
212
|
}
|
|
123
213
|
|
|
124
214
|
.activity-link {
|
|
125
|
-
color: #
|
|
215
|
+
color: #9c27b0;
|
|
126
216
|
text-decoration: underline;
|
|
127
217
|
}
|
|
128
218
|
|
|
129
219
|
.activity-bold {
|
|
130
220
|
font-weight: bold;
|
|
131
221
|
}
|
|
222
|
+
|
|
132
223
|
.activity-italic {
|
|
133
224
|
font-style: italic;
|
|
134
225
|
}
|
|
226
|
+
|
|
135
227
|
.activity-underline {
|
|
136
228
|
text-decoration: underline;
|
|
137
229
|
}
|
|
230
|
+
|
|
138
231
|
.activity-strike {
|
|
139
232
|
text-decoration: line-through;
|
|
140
233
|
}
|
|
141
234
|
```
|
|
142
235
|
|
|
143
|
-
## ๐
|
|
144
|
-
|
|
145
|
-
The bitmap system automatically converts character-level bit data into structured entities for easy consumption:
|
|
236
|
+
## ๐ Entity Structure
|
|
146
237
|
|
|
147
|
-
|
|
238
|
+
The editor converts bitmap data into structured entities for easy consumption:
|
|
148
239
|
|
|
149
240
|
```typescript
|
|
150
241
|
interface Entity {
|
|
@@ -152,49 +243,38 @@ interface Entity {
|
|
|
152
243
|
startIndex: number;
|
|
153
244
|
endIndex: number;
|
|
154
245
|
marks: ("bold" | "italic" | "underline" | "strike")[];
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
interest?: string; // For
|
|
246
|
+
href?: string; // For links
|
|
247
|
+
username?: string; // For mentions
|
|
248
|
+
interest?: string; // For interests
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
interface SearchEntity {
|
|
252
|
+
type: "mention" | "interest" | "link";
|
|
253
|
+
search: string;
|
|
254
|
+
startIndex: number;
|
|
255
|
+
endIndex: number;
|
|
158
256
|
}
|
|
159
257
|
```
|
|
160
258
|
|
|
161
|
-
### Conversion
|
|
259
|
+
### Bitmap to Entity Conversion
|
|
162
260
|
|
|
163
261
|
1. **Bitmap Scanning**: The system scans through the bitmap array
|
|
164
262
|
2. **Grouping**: Consecutive characters with identical bit values are grouped together
|
|
165
263
|
3. **Entity Creation**: Each group becomes an entity with its type determined by the bitmap value
|
|
166
264
|
4. **Mark Extraction**: Formatting bits are converted to a `marks` array
|
|
167
265
|
|
|
168
|
-
**Example Conversion**:
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
// Input bitmap for "Hello @john"
|
|
172
|
-
bitmap: [0, 0, 0, 0, 0, 0, 32, 32, 32, 32, 32];
|
|
173
|
-
|
|
174
|
-
// Output entities
|
|
175
|
-
entities: [
|
|
176
|
-
{
|
|
177
|
-
type: "mention",
|
|
178
|
-
startIndex: 6,
|
|
179
|
-
endIndex: 11,
|
|
180
|
-
marks: [],
|
|
181
|
-
mention: undefined, // Will be filled when mention is resolved
|
|
182
|
-
},
|
|
183
|
-
];
|
|
184
|
-
```
|
|
185
|
-
|
|
186
266
|
## ๐ฏ Recognition Patterns
|
|
187
267
|
|
|
188
268
|
- **Mentions**: `@username` (alphanumeric, underscores, hyphens, apostrophes)
|
|
189
269
|
- **Interests**: `#hashtag` (alphanumeric, underscores, hyphens)
|
|
190
270
|
- **Links**: `https://` or `http://` URLs
|
|
191
|
-
- **Formatting**:
|
|
271
|
+
- **Formatting**: Applied via keyboard shortcuts or toolbar
|
|
192
272
|
|
|
193
273
|
## โก Performance Benefits
|
|
194
274
|
|
|
195
275
|
### Why Bitmap Architecture?
|
|
196
276
|
|
|
197
|
-
The bitmap approach provides significant performance advantages
|
|
277
|
+
The bitmap approach provides significant performance advantages:
|
|
198
278
|
|
|
199
279
|
**Memory Efficiency**:
|
|
200
280
|
|
|
@@ -206,48 +286,81 @@ The bitmap approach provides significant performance advantages over traditional
|
|
|
206
286
|
|
|
207
287
|
- Bit operations are CPU-native and extremely fast
|
|
208
288
|
- O(1) property checks vs O(n) object property lookups
|
|
209
|
-
- Efficient grouping algorithm for entity generation
|
|
210
289
|
- **5-10x faster** entity processing
|
|
211
290
|
|
|
212
291
|
**Update Performance**:
|
|
213
292
|
|
|
214
293
|
- Only modified bitmap regions trigger re-rendering
|
|
215
|
-
-
|
|
216
|
-
- Minimal React re-renders through optimized state management
|
|
294
|
+
- Optimized React re-renders through efficient state management
|
|
217
295
|
|
|
218
|
-
|
|
296
|
+
## ๐ API Reference
|
|
219
297
|
|
|
220
|
-
|
|
221
|
-
Traditional Approach vs Bitmap Approach
|
|
222
|
-
โโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ
|
|
223
|
-
Memory: 500KB+ Memory: 40KB
|
|
224
|
-
Update: 15-30ms Update: 2-5ms
|
|
225
|
-
Bundle: +50KB deps Bundle: 0KB deps
|
|
226
|
-
```
|
|
298
|
+
### Props
|
|
227
299
|
|
|
228
|
-
|
|
300
|
+
| Prop | Type | Required | Description |
|
|
301
|
+
| -------------- | ---------------------------------------- | -------- | ------------------------------------- |
|
|
302
|
+
| `ref` | `React.RefObject<EditorRef>` | Yes | Ref to access editor methods |
|
|
303
|
+
| `plainText` | `string` | Yes | The current plain text content |
|
|
304
|
+
| `setPlainText` | `(text: string) => void` | Yes | Callback to update plain text |
|
|
305
|
+
| `entities` | `Entity[]` | Yes | Array of recognized entities |
|
|
306
|
+
| `setEntities` | `(entities: Entity[]) => void` | Yes | Callback to update entities |
|
|
307
|
+
| `markState` | `MarkState` | Yes | Current formatting state |
|
|
308
|
+
| `setMarkState` | `(state: MarkState) => void` | Yes | Callback to update formatting state |
|
|
309
|
+
| `search` | `SearchEntity \| null` | Yes | Current search state for autocomplete |
|
|
310
|
+
| `setSearch` | `(search: SearchEntity \| null) => void` | Yes | Callback to update search state |
|
|
311
|
+
| `options` | `EntityOptions` | No | Configure which entities to detect |
|
|
312
|
+
| `entityStyles` | `StyleOptions` | No | Custom colors for entity types |
|
|
313
|
+
| `style` | `React.CSSProperties` | No | Inline styles for the editor |
|
|
314
|
+
| `className` | `string` | No | CSS class name for the editor |
|
|
315
|
+
| `debug` | `boolean` | No | Enable debug mode (default: false) |
|
|
316
|
+
|
|
317
|
+
### Interfaces
|
|
229
318
|
|
|
230
|
-
|
|
319
|
+
```typescript
|
|
320
|
+
interface EntityOptions {
|
|
321
|
+
mentions?: boolean; // Enable @mention detection
|
|
322
|
+
interests?: boolean; // Enable #hashtag detection
|
|
323
|
+
links?: boolean; // Enable URL detection
|
|
324
|
+
}
|
|
231
325
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
326
|
+
interface StyleOptions {
|
|
327
|
+
mentionColor?: string; // Custom color for mentions
|
|
328
|
+
interestColor?: string; // Custom color for interests
|
|
329
|
+
linkColor?: string; // Custom color for links
|
|
330
|
+
}
|
|
237
331
|
|
|
238
|
-
|
|
332
|
+
interface MarkState {
|
|
333
|
+
bold: boolean;
|
|
334
|
+
italic: boolean;
|
|
335
|
+
underline: boolean;
|
|
336
|
+
strike: boolean;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
interface EditorRef {
|
|
340
|
+
selectEntity: (
|
|
341
|
+
entityType: EntityType,
|
|
342
|
+
startIndex: number,
|
|
343
|
+
endIndex: number,
|
|
344
|
+
newText: string
|
|
345
|
+
) => void;
|
|
346
|
+
toggleMark: (markType: MarkType) => void;
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Keyboard Shortcuts
|
|
351
|
+
|
|
352
|
+
- **Ctrl/Cmd + B**: Toggle bold
|
|
353
|
+
- **Ctrl/Cmd + I**: Toggle italic
|
|
354
|
+
- **Ctrl/Cmd + U**: Toggle underline
|
|
355
|
+
- **Ctrl/Cmd + Shift + S**: Toggle strikethrough
|
|
239
356
|
|
|
240
|
-
|
|
357
|
+
## ๐งช Testing
|
|
241
358
|
|
|
242
|
-
|
|
243
|
-
- **Entity Conversion**: Bitmap-to-entity transformation accuracy
|
|
244
|
-
- **Keyboard Shortcuts**: Formatting toggle functionality via Cmd/Ctrl+B/I/U/S
|
|
245
|
-
- **Edge Cases**: Boundary conditions, overlapping entities, empty states
|
|
246
|
-
- **Performance**: Large document handling and update efficiency
|
|
359
|
+
Run the test suite:
|
|
247
360
|
|
|
248
361
|
```bash
|
|
249
|
-
npm test # Run
|
|
250
|
-
npm run test:run # Run tests once
|
|
362
|
+
npm test # Run tests in watch mode
|
|
363
|
+
npm run test:run # Run tests once
|
|
251
364
|
```
|
|
252
365
|
|
|
253
366
|
## ๐๏ธ Development
|
|
@@ -262,37 +375,19 @@ npm run dev
|
|
|
262
375
|
# Build for production
|
|
263
376
|
npm run build
|
|
264
377
|
|
|
265
|
-
#
|
|
266
|
-
npm
|
|
378
|
+
# Create local package
|
|
379
|
+
npm run local
|
|
267
380
|
```
|
|
268
381
|
|
|
269
|
-
##
|
|
270
|
-
|
|
271
|
-
### Props
|
|
382
|
+
## ๐ Why Zero Dependencies?
|
|
272
383
|
|
|
273
|
-
|
|
274
|
-
| ----------------- | ---------------------------------------- | ------- | ------------------------------------- |
|
|
275
|
-
| `plainText` | `string` | - | The current plain text content |
|
|
276
|
-
| `setPlainText` | `(text: string) => void` | - | Callback to update plain text |
|
|
277
|
-
| `entities` | `Entity[]` | - | Array of recognized entities |
|
|
278
|
-
| `setEntities` | `(entities: Entity[]) => void` | - | Callback to update entities |
|
|
279
|
-
| `search` | `ActiveEntity \| null` | - | Current search state for autocomplete |
|
|
280
|
-
| `setSearch` | `(search: ActiveEntity \| null) => void` | - | Callback to update search state |
|
|
281
|
-
| `options` | `EntityOptions` | `{}` | Configure which entities to detect |
|
|
282
|
-
| `debounceDelay` | `number` | `250` | Delay before processing changes (ms) |
|
|
283
|
-
| `debounceMaxWait` | `number` | `500` | Maximum wait time for updates (ms) |
|
|
284
|
-
| `style` | `React.CSSProperties` | - | Optional inline styles |
|
|
285
|
-
| `className` | `string` | - | Optional CSS class name |
|
|
286
|
-
|
|
287
|
-
### EntityOptions
|
|
384
|
+
This editor proves that powerful text editing doesn't require heavy libraries:
|
|
288
385
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
```
|
|
386
|
+
- **Performance**: No bundle bloat, faster load times
|
|
387
|
+
- **Security**: No third-party vulnerabilities
|
|
388
|
+
- **Control**: Full understanding and control over every feature
|
|
389
|
+
- **Maintenance**: Easier updates and customization
|
|
390
|
+
- **Reliability**: No breaking changes from external dependencies
|
|
296
391
|
|
|
297
392
|
## ๐ค Contributing
|
|
298
393
|
|
|
@@ -302,16 +397,20 @@ This project maintains its zero-dependency philosophy and bitmap-based architect
|
|
|
302
397
|
2. **Bitmap First**: All new features should leverage the bitmap system
|
|
303
398
|
3. **Performance Focused**: Optimize for memory usage and processing speed
|
|
304
399
|
4. **TypeScript**: Maintain strict typing throughout
|
|
305
|
-
5. **Test Coverage**: Add comprehensive tests for
|
|
400
|
+
5. **Test Coverage**: Add comprehensive tests for new features
|
|
306
401
|
6. **API Stability**: Keep the component interface simple and focused
|
|
307
402
|
|
|
308
403
|
### Understanding the Codebase
|
|
309
404
|
|
|
405
|
+
- `src/Editor.tsx` - Main editor component
|
|
406
|
+
- `src/interfaces.ts` - TypeScript interfaces and types
|
|
310
407
|
- `src/helpers/bitmap.ts` - Core bitmap manipulation functions
|
|
311
408
|
- `src/helpers/entities.ts` - Bitmap-to-entity conversion logic
|
|
312
409
|
- `src/helpers/marks/` - Individual formatting bit operations
|
|
313
410
|
- `src/helpers/entities/` - Entity type detection and processing
|
|
314
411
|
- `src/helpers/dom.ts` - DOM manipulation and cursor management
|
|
412
|
+
- `src/helpers/keyboard.ts` - Keyboard shortcut handling
|
|
413
|
+
- `src/hooks/useDebounce.ts` - Debounce hook for performance
|
|
315
414
|
|
|
316
415
|
## ๐ License
|
|
317
416
|
|