@openim/im-composer 1.0.0 → 1.0.2

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.
@@ -0,0 +1,617 @@
1
+ import * as React$1 from 'react';
2
+ import React__default, { MouseEventHandler, ReactNode } from 'react';
3
+ import { Editor, useEditor } from '@tiptap/react';
4
+ import * as react_jsx_runtime from 'react/jsx-runtime';
5
+ import { Editor as Editor$1 } from '@tiptap/core';
6
+
7
+ /**
8
+ * Represents a file attachment in plain mode
9
+ */
10
+ interface Attachment {
11
+ /** Unique identifier generated by the component */
12
+ id: string;
13
+ /** Original File object */
14
+ file: File;
15
+ /** File name */
16
+ name: string;
17
+ /** File size in bytes */
18
+ size: number;
19
+ /** MIME type */
20
+ mime: string;
21
+ /** File last modified timestamp */
22
+ lastModified?: number;
23
+ /** Preview URL for images (objectURL) */
24
+ previewUrl?: string;
25
+ }
26
+ /**
27
+ * Information about a mention in the message
28
+ */
29
+ interface MentionInfo {
30
+ /** User ID */
31
+ userId: string;
32
+ /** Display name */
33
+ display: string;
34
+ /** Start index in plainText (UTF-16, inclusive) */
35
+ start: number;
36
+ /** End index in plainText (UTF-16, exclusive) */
37
+ end: number;
38
+ }
39
+ /**
40
+ * Member data for mention suggestions
41
+ */
42
+ interface Member {
43
+ /** Unique user identifier */
44
+ userId: string;
45
+ /** Display name shown in the editor */
46
+ display: string;
47
+ /** Optional avatar URL */
48
+ avatarUrl?: string;
49
+ }
50
+ /**
51
+ * Quoted message information
52
+ */
53
+ interface QuoteInfo {
54
+ /** Quote title, e.g., "Reply to Alice:" */
55
+ title: string;
56
+ /** Quoted content */
57
+ content: string;
58
+ }
59
+ /**
60
+ * Result from image upload handler
61
+ */
62
+ interface UploadImageResult {
63
+ /** URL of the uploaded image */
64
+ url: string;
65
+ /** Optional alt text */
66
+ alt?: string;
67
+ }
68
+ /**
69
+ * Progress callback for image upload
70
+ */
71
+ interface UploadProgressEvent {
72
+ /** Upload progress percentage (0-100) */
73
+ progress: number;
74
+ }
75
+ /**
76
+ * Image upload function type with optional progress callback
77
+ */
78
+ type UploadImageFn = (file: File, onProgress?: (event: UploadProgressEvent) => void) => Promise<UploadImageResult>;
79
+ /**
80
+ * Payload for plain text mode messages
81
+ */
82
+ interface PlainMessagePayload {
83
+ type: 'text';
84
+ /** Plain text with mentions as @userId */
85
+ plainText: string;
86
+ /** List of mentions with their positions */
87
+ mentions: MentionInfo[];
88
+ /** List of attached files */
89
+ attachments: Attachment[];
90
+ /** Quoted message (if any) */
91
+ quote?: QuoteInfo;
92
+ }
93
+ /**
94
+ * Payload for rich text mode messages (Markdown)
95
+ */
96
+ interface MarkdownMessagePayload {
97
+ type: 'markdown';
98
+ /** Markdown content */
99
+ markdown: string;
100
+ }
101
+ /**
102
+ * Union type for all message payloads
103
+ */
104
+ type MessagePayload = PlainMessagePayload | MarkdownMessagePayload;
105
+ /**
106
+ * Draft state for saving/restoring editor content
107
+ */
108
+ interface ComposerDraft {
109
+ /** Editor mode */
110
+ mode?: EditorMode;
111
+ /** Serialized editor state (JSON string from getJSON) - deprecated, use json */
112
+ editorState?: string;
113
+ /** Editor JSON content */
114
+ json?: Record<string, unknown>;
115
+ /** Attachments (plain mode only) */
116
+ attachments?: Attachment[];
117
+ /** Quick restore text for plain mode */
118
+ text?: string;
119
+ /** Quote information (plain mode only) */
120
+ quote?: QuoteInfo;
121
+ }
122
+ /**
123
+ * Locale strings for internationalization
124
+ */
125
+ interface IMComposerLocale {
126
+ /** Placeholder text for plain mode */
127
+ placeholderPlain?: string;
128
+ /** Placeholder text for rich mode */
129
+ placeholderRich?: string;
130
+ /** No results found in mention list */
131
+ mentionNoResults?: string;
132
+ /** Loading text for mention list */
133
+ mentionLoading?: string;
134
+ /** Error text for mention list */
135
+ mentionError?: string;
136
+ /** Remove attachment button label */
137
+ removeAttachment?: string;
138
+ /** Remove quote button label */
139
+ removeQuote?: string;
140
+ /** Upload failed message */
141
+ uploadFailed?: string;
142
+ /** Uploading text */
143
+ uploading?: string;
144
+ /** Undo button */
145
+ undo?: string;
146
+ /** Redo button */
147
+ redo?: string;
148
+ /** Heading dropdown */
149
+ heading?: string;
150
+ /** Heading 1 */
151
+ heading1?: string;
152
+ /** Heading 2 */
153
+ heading2?: string;
154
+ /** Heading 3 */
155
+ heading3?: string;
156
+ /** Heading 4 */
157
+ heading4?: string;
158
+ /** Paragraph */
159
+ paragraph?: string;
160
+ /** List dropdown */
161
+ list?: string;
162
+ /** Bullet list */
163
+ bulletList?: string;
164
+ /** Ordered list */
165
+ orderedList?: string;
166
+ /** Task list */
167
+ taskList?: string;
168
+ /** Blockquote button */
169
+ blockquote?: string;
170
+ /** Code block button */
171
+ codeBlock?: string;
172
+ /** Bold button */
173
+ bold?: string;
174
+ /** Italic button */
175
+ italic?: string;
176
+ /** Strikethrough button */
177
+ strike?: string;
178
+ /** Inline code button */
179
+ code?: string;
180
+ /** Underline button */
181
+ underline?: string;
182
+ /** Highlight button */
183
+ highlight?: string;
184
+ /** Remove highlight */
185
+ removeHighlight?: string;
186
+ /** Link button */
187
+ link?: string;
188
+ /** Link input placeholder */
189
+ linkPlaceholder?: string;
190
+ /** Apply link */
191
+ applyLink?: string;
192
+ /** Open link */
193
+ openLink?: string;
194
+ /** Remove link */
195
+ removeLink?: string;
196
+ /** Superscript button */
197
+ superscript?: string;
198
+ /** Subscript button */
199
+ subscript?: string;
200
+ /** Align left button */
201
+ alignLeft?: string;
202
+ /** Align center button */
203
+ alignCenter?: string;
204
+ /** Align right button */
205
+ alignRight?: string;
206
+ /** Align justify button */
207
+ alignJustify?: string;
208
+ /** Insert image button */
209
+ insertImage?: string;
210
+ /** Upload image */
211
+ uploadImage?: string;
212
+ /** Click to upload */
213
+ clickToUpload?: string;
214
+ /** Or drag and drop */
215
+ orDragAndDrop?: string;
216
+ /** Maximum files */
217
+ maxFiles?: string;
218
+ /** Clear all */
219
+ clearAll?: string;
220
+ }
221
+ /**
222
+ * Default English locale
223
+ */
224
+ declare const defaultLocale: IMComposerLocale;
225
+ /**
226
+ * Send keymap configuration
227
+ */
228
+ type SendKeymap = 'enter' | 'ctrlEnter' | 'cmdEnter';
229
+ /**
230
+ * Editor mode
231
+ */
232
+ type EditorMode = 'plain' | 'rich';
233
+ /**
234
+ * Attachment limit exceeded reason
235
+ */
236
+ type AttachmentLimitReason = 'count' | 'size' | 'mime';
237
+ /**
238
+ * Main component props
239
+ */
240
+ interface IMComposerProps {
241
+ /** Controlled mode */
242
+ mode?: EditorMode;
243
+ /** Default mode for uncontrolled usage */
244
+ defaultMode?: EditorMode;
245
+ /** Called when user triggers send action */
246
+ onSend?: (payload: PlainMessagePayload | MarkdownMessagePayload) => void;
247
+ /** Called when editor content changes */
248
+ onChange?: () => void;
249
+ /** Context menu handler */
250
+ onContextMenu?: MouseEventHandler<HTMLDivElement>;
251
+ /** Called when quote is removed */
252
+ onQuoteRemoved?: () => void;
253
+ /** Enable @mention feature */
254
+ enableMention?: boolean;
255
+ /** Async provider for mention suggestions */
256
+ mentionProvider?: (query: string) => Promise<Member[]>;
257
+ /** Maximum number of mentions allowed */
258
+ maxMentions?: number;
259
+ /** Custom render function for mention list items */
260
+ renderMentionItem?: (props: {
261
+ member: Member;
262
+ isSelected: boolean;
263
+ }) => ReactNode;
264
+ /** Mention list placement relative to cursor: 'top' or 'bottom' */
265
+ mentionPlacement?: 'top' | 'bottom';
266
+ /** Enable file attachments */
267
+ enableAttachments?: boolean;
268
+ /** Attachment preview bar placement */
269
+ attachmentPreviewPlacement?: 'top' | 'bottom';
270
+ /** Maximum number of attachments */
271
+ maxAttachments?: number;
272
+ /** Allowed MIME types (supports wildcards like "image/*") */
273
+ allowedMimeTypes?: string[];
274
+ /** Maximum file size in bytes */
275
+ maxFileSize?: number;
276
+ /** Called when attachment limit is exceeded */
277
+ onAttachmentLimitExceeded?: (reason: AttachmentLimitReason, file: File) => void;
278
+ /** Called when attachments change */
279
+ onFilesChange?: (attachments: Attachment[]) => void;
280
+ /** Show attachment preview bar */
281
+ showAttachmentPreview?: boolean;
282
+ /** Markdown options */
283
+ markdownOptions?: {
284
+ enabledSyntax?: string[];
285
+ };
286
+ /** Image upload handler with optional progress callback */
287
+ uploadImage?: UploadImageFn;
288
+ /** Keymap configuration */
289
+ keymap?: {
290
+ send?: SendKeymap;
291
+ };
292
+ /** Placeholder text (string or per-mode object) */
293
+ placeholder?: string | {
294
+ plain?: string;
295
+ rich?: string;
296
+ };
297
+ /** Disable the editor */
298
+ disabled?: boolean;
299
+ /** Additional CSS class */
300
+ className?: string;
301
+ /** Locale strings */
302
+ locale?: IMComposerLocale;
303
+ }
304
+ /**
305
+ * Methods exposed via ref
306
+ */
307
+ interface IMComposerRef {
308
+ /** Focus the editor */
309
+ focus: () => void;
310
+ /** Clear the editor content */
311
+ clear: () => void;
312
+ /** Export current content as payload. Returns null if empty or uploading */
313
+ exportPayload: () => PlainMessagePayload | MarkdownMessagePayload | null;
314
+ /** Import markdown content (rich mode only) */
315
+ importMarkdown: (markdown: string) => void;
316
+ /** Get current attachments */
317
+ getAttachments: () => Attachment[];
318
+ /** Set attachments */
319
+ setAttachments: (attachments: Attachment[]) => void;
320
+ /** Add files to attachments */
321
+ addFiles: (files: FileList | File[]) => void;
322
+ /** Remove a specific attachment by ID */
323
+ removeAttachment: (id: string) => void;
324
+ /** Clear all attachments */
325
+ clearAttachments: () => void;
326
+ /** Insert or replace a quote */
327
+ insertQuote: (title: string, content: string) => void;
328
+ /** Programmatically insert a mention */
329
+ insertMention: (userId: string, display: string) => void;
330
+ /** Get current draft state */
331
+ getDraft: () => ComposerDraft;
332
+ /** Restore from draft state */
333
+ setDraft: (draft: ComposerDraft) => void;
334
+ /** Set text content (with optional mentions for plain mode) */
335
+ setText: (text: string, mentions?: Member[]) => void;
336
+ /** Insert text at cursor */
337
+ insertText: (text: string) => void;
338
+ }
339
+ /**
340
+ * @internal Mention suggestion state
341
+ */
342
+ interface MentionSuggestionState {
343
+ active: boolean;
344
+ query: string;
345
+ items: Member[];
346
+ selectedIndex: number;
347
+ loading: boolean;
348
+ error: boolean;
349
+ /** Command to insert a mention (replaces @ trigger) */
350
+ command: ((member: {
351
+ userId: string;
352
+ display: string;
353
+ }) => void) | null;
354
+ }
355
+
356
+ /**
357
+ * IM Composer component supporting plain text and rich text modes.
358
+ */
359
+ declare const IMComposer: React__default.ForwardRefExoticComponent<IMComposerProps & React__default.RefAttributes<IMComposerRef>>;
360
+
361
+ interface RichEditorProps {
362
+ placeholder?: string;
363
+ disabled?: boolean;
364
+ uploadImage?: UploadImageFn;
365
+ onUploadingChange?: (count: number) => void;
366
+ onSend?: () => void;
367
+ onChange?: () => void;
368
+ sendKeymap?: 'enter' | 'ctrlEnter' | 'cmdEnter';
369
+ }
370
+ interface RichEditorRef {
371
+ editor: Editor | null;
372
+ focus: () => void;
373
+ clear: () => void;
374
+ exportPayload: () => MarkdownMessagePayload | null;
375
+ importMarkdown: (markdown: string) => void;
376
+ getDraft: () => ComposerDraft;
377
+ setDraft: (draft: ComposerDraft) => void;
378
+ setText: (text: string) => void;
379
+ insertText: (text: string) => void;
380
+ isUploading: () => boolean;
381
+ }
382
+ declare const RichEditor: React$1.ForwardRefExoticComponent<RichEditorProps & React$1.RefAttributes<RichEditorRef>>;
383
+
384
+ declare function LocaleProvider({ locale, children, }: {
385
+ locale?: IMComposerLocale;
386
+ children: React.ReactNode;
387
+ }): react_jsx_runtime.JSX.Element;
388
+ declare function useLocale(): IMComposerLocale;
389
+
390
+ interface MentionListProps {
391
+ /** List of member suggestions */
392
+ items: Member[];
393
+ /** Currently selected index */
394
+ selectedIndex: number;
395
+ /** Loading state */
396
+ loading: boolean;
397
+ /** Error state */
398
+ error: boolean;
399
+ /** Called when item is clicked */
400
+ onSelect: (member: Member) => void;
401
+ /** Called when mouse enters an item */
402
+ onHover: (index: number) => void;
403
+ /** Custom render function */
404
+ renderItem?: (props: {
405
+ member: Member;
406
+ isSelected: boolean;
407
+ }) => React__default.ReactNode;
408
+ /** Locale strings */
409
+ locale?: {
410
+ noResults?: string;
411
+ loading?: string;
412
+ error?: string;
413
+ };
414
+ }
415
+ /**
416
+ * Mention suggestion list component.
417
+ */
418
+ declare function MentionList({ items, selectedIndex, loading, error, onSelect, onHover, renderItem, locale, }: MentionListProps): react_jsx_runtime.JSX.Element | null;
419
+
420
+ interface AttachmentPreviewProps {
421
+ /** List of attachments */
422
+ attachments: Attachment[];
423
+ /** Called when remove button is clicked */
424
+ onRemove: (id: string) => void;
425
+ /** Placement position */
426
+ placement?: 'top' | 'bottom';
427
+ /** Remove button label */
428
+ removeLabel?: string;
429
+ }
430
+ /**
431
+ * Attachment preview bar component.
432
+ */
433
+ declare function AttachmentPreview({ attachments, onRemove, placement, removeLabel, }: AttachmentPreviewProps): react_jsx_runtime.JSX.Element | null;
434
+
435
+ interface QuoteBarProps {
436
+ /** Quote information */
437
+ quote: QuoteInfo;
438
+ /** Called when remove button is clicked */
439
+ onRemove: () => void;
440
+ /** Remove button label */
441
+ removeLabel?: string;
442
+ }
443
+ /**
444
+ * Quote message bar component.
445
+ */
446
+ declare function QuoteBar({ quote, onRemove, removeLabel }: QuoteBarProps): react_jsx_runtime.JSX.Element;
447
+
448
+ interface UseAttachmentsOptions {
449
+ /** Maximum number of attachments */
450
+ maxAttachments?: number;
451
+ /** Maximum file size in bytes */
452
+ maxFileSize?: number;
453
+ /** Allowed MIME types */
454
+ allowedMimeTypes?: string[];
455
+ /** Called when limit is exceeded */
456
+ onLimitExceeded?: (reason: AttachmentLimitReason, file: File) => void;
457
+ /** Called when attachments change */
458
+ onChange?: (attachments: Attachment[]) => void;
459
+ }
460
+ interface UseAttachmentsReturn {
461
+ /** Current attachments */
462
+ attachments: Attachment[];
463
+ /** Add files to attachments */
464
+ addFiles: (files: FileList | File[]) => void;
465
+ /** Remove attachment by ID */
466
+ removeAttachment: (id: string) => void;
467
+ /** Clear all attachments */
468
+ clearAttachments: () => void;
469
+ /** Set attachments directly */
470
+ setAttachments: (attachments: Attachment[]) => void;
471
+ /** Get current attachments */
472
+ getAttachments: () => Attachment[];
473
+ }
474
+ /**
475
+ * Hook for managing file attachments with proper objectURL lifecycle.
476
+ */
477
+ declare function useAttachments(options?: UseAttachmentsOptions): UseAttachmentsReturn;
478
+
479
+ interface UsePlainEditorOptions {
480
+ /** Placeholder text */
481
+ placeholder?: string;
482
+ /** Keymap for send */
483
+ sendKeymap?: SendKeymap;
484
+ /** Called when send is triggered */
485
+ onSend?: () => void;
486
+ /** Enable mention */
487
+ enableMention?: boolean;
488
+ /** Mention provider */
489
+ mentionProvider?: (query: string) => Promise<Member[]>;
490
+ /** Max mentions */
491
+ maxMentions?: number;
492
+ /** Called when mention state changes */
493
+ onMentionStateChange?: (state: MentionSuggestionState) => void;
494
+ /** Called when files are pasted */
495
+ onPasteFiles?: (files: File[]) => void;
496
+ /** Get current uploading state */
497
+ isUploading?: () => boolean;
498
+ /** Disabled state */
499
+ disabled?: boolean;
500
+ /** Called on content change */
501
+ onChange?: () => void;
502
+ /** Called when quote is removed */
503
+ onQuoteRemoved?: () => void;
504
+ }
505
+ interface UsePlainEditorReturn {
506
+ /** Tiptap editor instance */
507
+ editor: ReturnType<typeof useEditor>;
508
+ /** Check if composing (IME) */
509
+ isComposing: () => boolean;
510
+ /** Check if mention list is open */
511
+ isMentionListOpen: () => boolean;
512
+ /** Export payload */
513
+ exportPayload: (attachments: Attachment[]) => PlainMessagePayload | null;
514
+ /** Get draft */
515
+ getDraft: (attachments: Attachment[]) => ComposerDraft;
516
+ /** Set draft */
517
+ setDraft: (draft: ComposerDraft) => void;
518
+ /** Insert mention programmatically */
519
+ insertMention: (userId: string, display: string) => void;
520
+ /** Insert quote */
521
+ insertQuote: (title: string, content: string) => void;
522
+ /** Remove quote */
523
+ removeQuote: () => void;
524
+ /** Get current quote */
525
+ getQuote: () => QuoteInfo | undefined;
526
+ /** Set text content */
527
+ setText: (text: string) => void;
528
+ /** Insert text at cursor */
529
+ insertText: (text: string) => void;
530
+ /** Clear editor */
531
+ clear: () => void;
532
+ /** Focus editor */
533
+ focus: () => void;
534
+ }
535
+ /**
536
+ * Hook for managing plain text editor.
537
+ */
538
+ declare function usePlainEditor(options?: UsePlainEditorOptions): UsePlainEditorReturn;
539
+
540
+ /**
541
+ * Extract plain text and mention information from the editor.
542
+ * Mention tokens are output as @{userId} followed by a space.
543
+ *
544
+ * @param editor Tiptap editor instance
545
+ * @returns Object containing plainText and mentions array with UTF-16 indices
546
+ */
547
+ declare function extractPlainTextWithMentions(editor: Editor$1): {
548
+ plainText: string;
549
+ mentions: MentionInfo[];
550
+ };
551
+ /**
552
+ * Check if mention indices are valid and aligned with the plainText.
553
+ */
554
+ declare function validateMentionIndices(plainText: string, mentions: MentionInfo[]): boolean;
555
+ /**
556
+ * Create a mention info object with calculated indices.
557
+ */
558
+ declare function createMentionInfo(userId: string, display: string, startIndex: number): MentionInfo;
559
+
560
+ /**
561
+ * Create an Attachment object from a File.
562
+ */
563
+ declare function createAttachment(file: File): Attachment;
564
+ /**
565
+ * Revoke object URL for an attachment.
566
+ */
567
+ declare function revokeAttachmentUrl(attachment: Attachment): void;
568
+ /**
569
+ * Revoke all object URLs for a list of attachments.
570
+ */
571
+ declare function revokeAllAttachmentUrls(attachments: Attachment[]): void;
572
+ /**
573
+ * Validate a file against attachment constraints.
574
+ * Returns the reason for rejection, or null if valid.
575
+ */
576
+ declare function validateFile(file: File, currentCount: number, options: {
577
+ maxAttachments?: number;
578
+ maxFileSize?: number;
579
+ allowedMimeTypes?: string[];
580
+ }): AttachmentLimitReason | null;
581
+ /**
582
+ * Format file size for display.
583
+ */
584
+ declare function formatFileSize(bytes: number): string;
585
+
586
+ /**
587
+ * Markdown serializer for Tiptap editor content.
588
+ * Converts the editor document to Markdown string.
589
+ */
590
+ declare function editorToMarkdown(editor: Editor$1): string;
591
+ /**
592
+ * Parse Markdown to Tiptap-compatible JSON content.
593
+ * This is a simplified parser for the Markdown subset we support.
594
+ */
595
+ declare function markdownToEditorContent(markdown: string): any;
596
+
597
+ /**
598
+ * Check if a URL has an allowed protocol for links.
599
+ */
600
+ declare function isAllowedLinkProtocol(url: string): boolean;
601
+ /**
602
+ * Check if a URL has an allowed protocol for images.
603
+ */
604
+ declare function isAllowedImageProtocol(url: string): boolean;
605
+ /**
606
+ * Sanitize a URL for links.
607
+ * - Adds https:// if no protocol is present
608
+ * - Returns null if protocol is not allowed (including javascript:, vbscript:, etc.)
609
+ */
610
+ declare function sanitizeLinkUrl(url: string): string | null;
611
+ /**
612
+ * Sanitize a URL for images.
613
+ * Returns null if protocol is not allowed.
614
+ */
615
+ declare function sanitizeImageUrl(url: string): string | null;
616
+
617
+ export { type Attachment, type AttachmentLimitReason, AttachmentPreview, type AttachmentPreviewProps, type ComposerDraft, type EditorMode, IMComposer, type IMComposerLocale, type IMComposerProps, type IMComposerRef, LocaleProvider, type MarkdownMessagePayload, type Member, type MentionInfo, MentionList, type MentionListProps, type MessagePayload, type PlainMessagePayload, QuoteBar, type QuoteBarProps, type QuoteInfo, RichEditor, type RichEditorProps, type RichEditorRef, type SendKeymap, type UploadImageFn, type UploadImageResult, type UploadProgressEvent, type UseAttachmentsOptions, type UseAttachmentsReturn, type UsePlainEditorOptions, type UsePlainEditorReturn, createAttachment, createMentionInfo, defaultLocale, editorToMarkdown, extractPlainTextWithMentions, formatFileSize, isAllowedImageProtocol, isAllowedLinkProtocol, markdownToEditorContent, revokeAllAttachmentUrls, revokeAttachmentUrl, sanitizeImageUrl, sanitizeLinkUrl, useAttachments, useLocale, usePlainEditor, validateFile, validateMentionIndices };