@opensumi/ide-comments 2.21.7-rc-1670229502.0 → 2.21.7

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.
@@ -1,362 +0,0 @@
1
- import { observer } from 'mobx-react-lite';
2
- import React from 'react';
3
-
4
- import { Button } from '@opensumi/ide-components';
5
- import {
6
- useInjectable,
7
- localize,
8
- IContextKeyService,
9
- isUndefined,
10
- IMarkdownString,
11
- toLocalString,
12
- toMarkdownHtml,
13
- } from '@opensumi/ide-core-browser';
14
- import { InlineActionBar } from '@opensumi/ide-core-browser/lib/components/actions';
15
- import { AbstractMenuService, MenuId, IMenu } from '@opensumi/ide-core-browser/lib/menu/next';
16
-
17
- import {
18
- IThreadComment,
19
- ICommentsCommentTitle,
20
- CommentMode,
21
- ICommentReply,
22
- ICommentsCommentContext,
23
- ICommentsZoneWidget,
24
- ICommentsFeatureRegistry,
25
- ICommentsThread,
26
- } from '../common';
27
-
28
- import { CommentReactions, CommentReactionSwitcher } from './comment-reactions.view';
29
- import { CommentsBody } from './comments-body';
30
- import { CommentsTextArea } from './comments-textarea.view';
31
- import styles from './comments.module.less';
32
-
33
- // TODO: 更好的时间格式化组件
34
- const Timestamp: React.FC<{ timestamp: string }> = ({ timestamp }) => {
35
- const formatTimestamp = React.useMemo(() => {
36
- const date = new Date(timestamp);
37
- return toLocalString(date);
38
- }, [timestamp]);
39
-
40
- return <span className={styles.comment_item_timestamp}>{formatTimestamp}</span>;
41
- };
42
-
43
- const useCommentContext = (
44
- contextKeyService: IContextKeyService,
45
- comment: IThreadComment,
46
- ): [
47
- string | IMarkdownString,
48
- React.Dispatch<React.SetStateAction<string | IMarkdownString>>,
49
- (event: React.ChangeEvent<HTMLTextAreaElement>) => void,
50
- IMenu,
51
- IMenu,
52
- (files: FileList) => Promise<void>,
53
- ] => {
54
- const menuService = useInjectable<AbstractMenuService>(AbstractMenuService);
55
- const { body, contextValue } = comment;
56
- const [textValue, setTextValue] = React.useState<string | IMarkdownString>('');
57
- const commentsFeatureRegistry = useInjectable<ICommentsFeatureRegistry>(ICommentsFeatureRegistry);
58
- const fileUploadHandler = React.useMemo(() => commentsFeatureRegistry.getFileUploadHandler(), []);
59
- // set textValue when body changed
60
- React.useEffect(() => {
61
- setTextValue(body);
62
- }, [body]);
63
-
64
- // Each comment has its own commentContext and commentTitleContext.
65
- const commentContextService = React.useMemo(() => contextKeyService.createScoped(), []);
66
- // it's value will true when textarea is empty
67
- const commentIsEmptyContext = React.useMemo(
68
- () => commentContextService.createKey<boolean>('commentIsEmpty', !comment.body),
69
- [],
70
- );
71
- // below the comment textarea
72
- const commentContext = React.useMemo(
73
- () => menuService.createMenu(MenuId.CommentsCommentContext, commentContextService),
74
- [],
75
- );
76
- // after the comment body
77
- const commentTitleContext = React.useMemo(
78
- () => menuService.createMenu(MenuId.CommentsCommentTitle, commentContextService),
79
- [],
80
- );
81
-
82
- const itemCommentContext = React.useRef(commentContextService.createKey('comment', contextValue));
83
-
84
- React.useEffect(() => {
85
- itemCommentContext.current.set(contextValue);
86
- }, [contextValue]);
87
-
88
- const onChangeTextArea = React.useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
89
- commentIsEmptyContext.set(!event.target.value);
90
- setTextValue(event.target.value);
91
- }, []);
92
-
93
- const handleDragFiles = React.useCallback(
94
- async (files: FileList) => {
95
- if (fileUploadHandler) {
96
- const appendText = await fileUploadHandler(textValue, files);
97
- setTextValue((text) => {
98
- const value = text + appendText;
99
- commentIsEmptyContext.set(!value);
100
- return value;
101
- });
102
- }
103
- },
104
- [textValue],
105
- );
106
-
107
- return [textValue, setTextValue, onChangeTextArea, commentContext, commentTitleContext, handleDragFiles];
108
- };
109
-
110
- const ReplyItem: React.FC<{
111
- reply: IThreadComment;
112
- thread: ICommentsThread;
113
- }> = observer(({ reply, thread }) => {
114
- const { contextKeyService } = thread;
115
- const { author, label, body, mode, timestamp } = reply;
116
- const iconUrl = author.iconPath?.toString();
117
- const [textValue, setTextValue, onChangeTextArea, commentContext, commentTitleContext, handleDragFiles] =
118
- useCommentContext(contextKeyService, reply);
119
-
120
- // 判断是正常 Inline Text 还是 Markdown Text
121
- const isInlineText = React.useMemo(() => {
122
- const parsedStr = toMarkdownHtml(typeof body === 'string' ? body : body.value);
123
- // 解析出来非纯p标签的则为Markdown Text
124
- const isInline = /^\<p\>[^<>]+\<\/p\>\n$/.test(parsedStr);
125
- return isInline;
126
- }, [body]);
127
-
128
- return (
129
- <div className={styles.reply_item}>
130
- {isUndefined(mode) || mode === CommentMode.Preview ? (
131
- <div>
132
- {isInlineText ? (
133
- <>
134
- {iconUrl && <img className={styles.reply_item_icon} src={iconUrl} alt={author.name} />}
135
- <span className={styles.comment_item_author_name}>{author.name}</span>
136
- {timestamp && <Timestamp timestamp={timestamp} />}
137
- {typeof label === 'string' ? <span className={styles.comment_item_label}>{label}</span> : label}
138
- {' : '}
139
- <span className={styles.comment_item_body}>{body}</span>
140
- {reply.reactions && reply.reactions.length > 0 && (
141
- <CommentReactionSwitcher className={styles.reply_item_title} thread={thread} comment={reply} />
142
- )}
143
- <InlineActionBar<ICommentsCommentTitle>
144
- separator='inline'
145
- className={styles.reply_item_title}
146
- menus={commentTitleContext}
147
- context={[
148
- {
149
- thread,
150
- comment: reply,
151
- menuId: MenuId.CommentsCommentTitle,
152
- },
153
- ]}
154
- type='icon'
155
- />
156
- </>
157
- ) : (
158
- <>
159
- <div className={styles.comment_item_markdown_header}>
160
- <div>
161
- {iconUrl && <img className={styles.reply_item_icon} src={iconUrl} alt={author.name} />}
162
- <span className={styles.comment_item_author_name}>{author.name}</span>
163
- {timestamp && <Timestamp timestamp={timestamp} />}
164
- {typeof label === 'string' ? <span className={styles.comment_item_label}>{label}</span> : label}
165
- {' : '}
166
- </div>
167
- <InlineActionBar<ICommentsCommentTitle>
168
- separator='inline'
169
- className={styles.reply_item_title}
170
- menus={commentTitleContext}
171
- context={[
172
- {
173
- thread,
174
- comment: reply,
175
- menuId: MenuId.CommentsCommentTitle,
176
- },
177
- ]}
178
- type='icon'
179
- />
180
- </div>
181
- <CommentsBody body={body} />
182
- </>
183
- )}
184
- </div>
185
- ) : (
186
- <div>
187
- <CommentsTextArea
188
- value={typeof textValue === 'string' ? textValue : textValue.value}
189
- autoFocus={true}
190
- onChange={onChangeTextArea}
191
- dragFiles={handleDragFiles}
192
- />
193
- <InlineActionBar<ICommentsCommentContext>
194
- className={styles.comment_item_reply}
195
- menus={commentContext}
196
- context={[
197
- {
198
- thread,
199
- comment: reply,
200
- body: textValue,
201
- menuId: MenuId.CommentsCommentContext,
202
- },
203
- ]}
204
- type='button'
205
- separator='inline'
206
- afterClick={() => {
207
- // restore textarea value
208
- setTextValue(body);
209
- }}
210
- />
211
- </div>
212
- )}
213
- {reply.reactions && reply.reactions.length > 0 && <CommentReactions thread={thread} comment={reply} />}
214
- </div>
215
- );
216
- });
217
-
218
- export const CommentItem: React.FC<{
219
- thread: ICommentsThread;
220
- commentThreadContext: IMenu;
221
- widget: ICommentsZoneWidget;
222
- }> = observer(({ thread, commentThreadContext, widget }) => {
223
- const { readOnly, contextKeyService } = thread;
224
- const [showReply, setShowReply] = React.useState(false);
225
- const [replyText, setReplyText] = React.useState('');
226
- const [comment, ...replies] = thread.comments;
227
- const { author, label, body, mode, timestamp } = comment;
228
- const iconUrl = author.iconPath?.toString();
229
- const [textValue, setTextValue, onChangeTextArea, commentContext, commentTitleContext, handleDragFiles] =
230
- useCommentContext(contextKeyService, comment);
231
- const commentsFeatureRegistry = useInjectable<ICommentsFeatureRegistry>(ICommentsFeatureRegistry);
232
- const fileUploadHandler = React.useMemo(() => commentsFeatureRegistry.getFileUploadHandler(), []);
233
- const replyIsEmptyContext = React.useMemo(() => contextKeyService.createKey<boolean>('commentIsEmpty', true), []);
234
-
235
- // modify reply
236
- function onChangeReply(event: React.ChangeEvent<HTMLTextAreaElement>) {
237
- replyIsEmptyContext.set(!event.target.value);
238
- setReplyText(event.target.value);
239
- }
240
-
241
- const handleDragFilesToReply = React.useCallback(
242
- async (files: FileList) => {
243
- if (fileUploadHandler) {
244
- const appendText = await fileUploadHandler(textValue, files);
245
- setReplyText((text) => {
246
- const value = text + appendText;
247
- replyIsEmptyContext.set(!value);
248
- return value;
249
- });
250
- }
251
- },
252
- [replyText],
253
- );
254
-
255
- return (
256
- <div className={styles.comment_item}>
257
- {iconUrl && <img className={styles.comment_item_icon} src={iconUrl} alt={author.name} />}
258
- <div className={styles.comment_item_content}>
259
- <div className={styles.comment_item_head}>
260
- <div className={styles.comment_item_name}>
261
- <span className={styles.comment_item_author_name}>{author.name}</span>
262
- {timestamp && <Timestamp timestamp={timestamp} />}
263
- {typeof label === 'string' ? <span className={styles.comment_item_label}>{label}</span> : label}
264
- </div>
265
- <div className={styles.comment_item_actions}>
266
- {comment.reactions && comment.reactions.length > 0 && (
267
- <CommentReactionSwitcher thread={thread} comment={comment} />
268
- )}
269
- {!readOnly && (
270
- <Button
271
- className={styles.comment_item_reply_button}
272
- size='small'
273
- type='secondary'
274
- onClick={() => setShowReply(true)}
275
- >
276
- {localize('comments.thread.action.reply')}
277
- </Button>
278
- )}
279
- <InlineActionBar<ICommentsCommentTitle>
280
- menus={commentTitleContext}
281
- context={[
282
- {
283
- thread,
284
- comment,
285
- menuId: MenuId.CommentsCommentTitle,
286
- },
287
- ]}
288
- type='button'
289
- />
290
- </div>
291
- </div>
292
- {isUndefined(mode) || mode === CommentMode.Preview ? (
293
- <CommentsBody body={body} />
294
- ) : (
295
- <div>
296
- <CommentsTextArea
297
- value={typeof textValue === 'string' ? textValue : textValue.value}
298
- autoFocus={true}
299
- onChange={onChangeTextArea}
300
- dragFiles={handleDragFiles}
301
- />
302
- <InlineActionBar<ICommentsCommentContext>
303
- className={styles.comment_item_context}
304
- menus={commentContext}
305
- context={[
306
- {
307
- thread,
308
- comment,
309
- body: textValue,
310
- menuId: MenuId.CommentsCommentContext,
311
- },
312
- ]}
313
- separator='inline'
314
- type='button'
315
- afterClick={() => {
316
- // restore textarea value
317
- setTextValue(body);
318
- }}
319
- />
320
- </div>
321
- )}
322
- {comment.reactions && comment.reactions.length > 0 && <CommentReactions thread={thread} comment={comment} />}
323
- {(replies.length > 0 || showReply) && (
324
- <div className={styles.comment_item_reply_wrap}>
325
- {replies.map((reply) => (
326
- <ReplyItem key={reply.id} thread={thread} reply={reply} />
327
- ))}
328
- {showReply && (
329
- <div>
330
- <CommentsTextArea
331
- autoFocus={true}
332
- value={replyText}
333
- onChange={onChangeReply}
334
- placeholder={`${localize('comments.reply.placeholder')}...`}
335
- dragFiles={handleDragFilesToReply}
336
- />
337
- <InlineActionBar<ICommentReply>
338
- className={styles.comment_item_reply}
339
- menus={commentThreadContext}
340
- context={[
341
- {
342
- thread,
343
- text: replyText,
344
- widget,
345
- menuId: MenuId.CommentsCommentThreadContext,
346
- },
347
- ]}
348
- separator='inline'
349
- type='button'
350
- afterClick={() => {
351
- setReplyText('');
352
- setShowReply(false);
353
- }}
354
- />
355
- </div>
356
- )}
357
- </div>
358
- )}
359
- </div>
360
- </div>
361
- );
362
- });
@@ -1,127 +0,0 @@
1
- import clx from 'classnames';
2
- import { observer } from 'mobx-react-lite';
3
- import React from 'react';
4
-
5
- import { useInjectable, isUndefined, ViewState, IEventBus, localize } from '@opensumi/ide-core-browser';
6
- import { DeprecatedRecycleTree } from '@opensumi/ide-core-browser/lib/components';
7
- import { WorkbenchEditorService } from '@opensumi/ide-editor';
8
-
9
- import { ICommentsService, ICommentsTreeNode, CommentPanelCollapse, ICommentsFeatureRegistry } from '../common';
10
-
11
- import styles from './comments.module.less';
12
-
13
- export const CommentsPanel = observer<{ viewState: ViewState; className?: string }>((props) => {
14
- const commentsService = useInjectable<ICommentsService>(ICommentsService);
15
- const workbenchEditorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
16
- const commentsFeatureRegistry = useInjectable<ICommentsFeatureRegistry>(ICommentsFeatureRegistry);
17
- const [treeNodes, setTreeNodes] = React.useState<ICommentsTreeNode[]>([]);
18
- const eventBus: IEventBus = useInjectable(IEventBus);
19
-
20
- React.useEffect(() => {
21
- eventBus.on(CommentPanelCollapse, () => {
22
- setTreeNodes((nodes) =>
23
- nodes.map((node) => {
24
- if (!isUndefined(node.expanded)) {
25
- node.expanded = false;
26
- }
27
- return node;
28
- }),
29
- );
30
- });
31
- }, []);
32
-
33
- const getRenderTree = React.useCallback(
34
- (nodes: ICommentsTreeNode[]) =>
35
- nodes.filter((node) => {
36
- if (node && node.parent) {
37
- if (node.parent.expanded === false || node.parent.parent?.expanded === false) {
38
- return false;
39
- }
40
- }
41
- return true;
42
- }),
43
- [],
44
- );
45
-
46
- React.useEffect(() => {
47
- setTreeNodes(commentsService.commentsTreeNodes);
48
- }, [commentsService.commentsTreeNodes]);
49
-
50
- const handleSelect = React.useCallback(
51
- ([item]: [ICommentsTreeNode]) => {
52
- // 可能点击到空白位置
53
- if (!item) {
54
- return;
55
- }
56
-
57
- if (!isUndefined(item.expanded)) {
58
- const newNodes = treeNodes.map((node) => {
59
- if (node.id === item.id) {
60
- node.expanded = !node.expanded;
61
- }
62
- node.selected = node.id === item.id;
63
- return node;
64
- });
65
- setTreeNodes(newNodes);
66
- } else {
67
- const newNodes = treeNodes.map((node) => {
68
- node.selected = node.id === item.id;
69
- return node;
70
- });
71
- setTreeNodes(newNodes);
72
- }
73
-
74
- if (item.onSelect) {
75
- item.onSelect(item);
76
- } else {
77
- workbenchEditorService.open(item.uri!, {
78
- range: item.thread.range,
79
- });
80
- }
81
- },
82
- [workbenchEditorService, treeNodes],
83
- );
84
-
85
- const commentsPanelOptions = React.useMemo(() => commentsFeatureRegistry.getCommentsPanelOptions(), []);
86
-
87
- const headerComponent = React.useMemo(() => commentsPanelOptions.header, [commentsPanelOptions]);
88
-
89
- const treeHeight = React.useMemo(
90
- () => props.viewState.height - (headerComponent?.height || 0),
91
- [props.viewState.height],
92
- );
93
-
94
- const scrollContainerStyle = React.useMemo(
95
- () => ({
96
- width: '100%',
97
- height: treeHeight,
98
- }),
99
- [treeHeight],
100
- );
101
-
102
- const defaultPlaceholder = React.useMemo(() => commentsPanelOptions.defaultPlaceholder, [commentsPanelOptions]);
103
-
104
- const nodes = getRenderTree(treeNodes);
105
-
106
- return (
107
- <div className={clx(props.className, styles.comment_panel)}>
108
- {headerComponent?.component}
109
- {nodes.length ? (
110
- <DeprecatedRecycleTree
111
- containerHeight={treeHeight}
112
- scrollContainerStyle={scrollContainerStyle}
113
- nodes={nodes}
114
- foldable={true}
115
- outline={false}
116
- onSelect={(item) => handleSelect(item)}
117
- leftPadding={20}
118
- {...commentsPanelOptions.recycleTreeProps}
119
- />
120
- ) : !defaultPlaceholder || typeof defaultPlaceholder === 'string' ? (
121
- <div className={styles.panel_placeholder}>{defaultPlaceholder || localize('comments.panel.placeholder')}</div>
122
- ) : (
123
- defaultPlaceholder
124
- )}
125
- </div>
126
- );
127
- });
@@ -1,194 +0,0 @@
1
- import React from 'react';
2
- import { MentionsInput, Mention } from 'react-mentions';
3
-
4
- import { Tabs } from '@opensumi/ide-components';
5
- import { localize, useInjectable } from '@opensumi/ide-core-browser';
6
-
7
- import { ICommentsFeatureRegistry } from '../common';
8
-
9
- import { CommentsBody } from './comments-body';
10
- import styles from './comments.module.less';
11
- import { getMentionBoxStyle } from './mentions.style';
12
-
13
-
14
- export interface ICommentTextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
15
- focusDelay?: number;
16
- minRows?: number;
17
- maxRows?: number;
18
- initialHeight?: string;
19
- value: string;
20
- dragFiles?: (files: FileList) => void;
21
- }
22
-
23
- const defaultTrigger = '@';
24
- const defaultMarkup = '@[__display__](__id__)';
25
- const defaultDisplayTransform = (id: string, display: string) => `@${display}`;
26
-
27
- export const CommentsTextArea = React.forwardRef<HTMLTextAreaElement, ICommentTextAreaProps>((props, ref) => {
28
- const {
29
- focusDelay = 0,
30
- autoFocus = false,
31
- placeholder = '',
32
- onFocus,
33
- onBlur,
34
- onChange,
35
- maxRows = 10,
36
- minRows = 2,
37
- value,
38
- initialHeight,
39
- dragFiles,
40
- } = props;
41
- const [index, setIndex] = React.useState(0);
42
- const commentsFeatureRegistry = useInjectable<ICommentsFeatureRegistry>(ICommentsFeatureRegistry);
43
- const inputRef = React.useRef<HTMLTextAreaElement | null>(null);
44
- const mentionsRef = React.useRef<HTMLDivElement | null>(null);
45
- const itemRef = React.useRef<HTMLDivElement | null>(null);
46
- // make `ref` to input works
47
- React.useImperativeHandle(ref, () => inputRef.current!);
48
-
49
- const handleFileSelect = React.useCallback(
50
- async (event: DragEvent) => {
51
- event.stopPropagation();
52
- event.preventDefault();
53
-
54
- const files = event.dataTransfer?.files; // FileList object.
55
- if (files && dragFiles) {
56
- await dragFiles(files);
57
- }
58
-
59
- if (inputRef.current) {
60
- inputRef.current.focus();
61
- selectLastPosition(inputRef.current.value);
62
- }
63
- },
64
- [dragFiles],
65
- );
66
-
67
- const handleDragOver = React.useCallback((event) => {
68
- event.stopPropagation();
69
- event.preventDefault();
70
- event.dataTransfer.dropEffect = 'copy';
71
- }, []);
72
-
73
- const selectLastPosition = React.useCallback((value) => {
74
- const textarea = inputRef.current;
75
- if (textarea) {
76
- const position = value.toString().length;
77
- textarea.setSelectionRange(position, position);
78
- }
79
- }, []);
80
-
81
- React.useEffect(() => {
82
- const textarea = inputRef.current;
83
- if (!textarea) {
84
- return;
85
- }
86
- if (initialHeight && textarea.style) {
87
- textarea.style.height = initialHeight;
88
- }
89
- if (focusDelay) {
90
- setTimeout(() => {
91
- textarea.focus({
92
- preventScroll: true,
93
- });
94
- }, focusDelay);
95
- }
96
- // auto set last selection
97
- selectLastPosition(value);
98
- function handleMouseWheel(event: Event) {
99
- const target = event.target as Element;
100
- if (target) {
101
- if (
102
- // 当前文本框出现滚动时,防止被编辑器滚动拦截,阻止冒泡
103
- (target.nodeName.toUpperCase() === 'TEXTAREA' && target.scrollHeight > target.clientHeight) ||
104
- // 当是在弹出的提及里滚动,防止被编辑器滚动拦截,阻止冒泡
105
- target.nodeName.toUpperCase() === 'UL' ||
106
- target.parentElement?.nodeName.toUpperCase() === 'UL' ||
107
- target.parentElement?.parentElement?.nodeName.toUpperCase() === 'UL'
108
- ) {
109
- event.stopPropagation();
110
- }
111
- }
112
- }
113
- mentionsRef.current?.addEventListener('mousewheel', handleMouseWheel, true);
114
- return () => {
115
- mentionsRef.current?.removeEventListener('mousewheel', handleMouseWheel, true);
116
- };
117
- }, []);
118
-
119
- React.useEffect(() => {
120
- if (index === 0) {
121
- setTimeout(() => {
122
- inputRef.current?.focus({
123
- preventScroll: true,
124
- });
125
- }, focusDelay);
126
- selectLastPosition(value);
127
- }
128
- }, [index]);
129
-
130
- const style = React.useMemo(
131
- () =>
132
- getMentionBoxStyle({
133
- minRows,
134
- maxRows,
135
- }),
136
- [minRows, maxRows],
137
- );
138
-
139
- const mentionsOptions = React.useMemo(() => commentsFeatureRegistry.getMentionsOptions(), [commentsFeatureRegistry]);
140
-
141
- const providerData = React.useCallback(
142
- async (query: string, callback) => {
143
- if (mentionsOptions.providerData) {
144
- const data = await mentionsOptions.providerData(query);
145
- callback(data);
146
- } else {
147
- callback([]);
148
- }
149
- },
150
- [mentionsOptions],
151
- );
152
-
153
- return (
154
- <div className={styles.textarea_container}>
155
- <Tabs
156
- mini
157
- value={index}
158
- onChange={(index: number) => setIndex(index)}
159
- tabs={[localize('comments.thread.textarea.write'), localize('comments.thread.textarea.preview')]}
160
- />
161
- <div>
162
- {index === 0 ? (
163
- <div ref={mentionsRef}>
164
- <MentionsInput
165
- autoFocus={autoFocus}
166
- onDragOver={handleDragOver}
167
- onDrop={handleFileSelect}
168
- inputRef={inputRef}
169
- ref={itemRef}
170
- value={value}
171
- placeholder={placeholder}
172
- onChange={onChange}
173
- onFocus={onFocus}
174
- onBlur={onBlur}
175
- style={style}
176
- >
177
- <Mention
178
- markup={mentionsOptions.markup || defaultMarkup}
179
- renderSuggestion={mentionsOptions.renderSuggestion}
180
- trigger={defaultTrigger}
181
- data={providerData}
182
- displayTransform={mentionsOptions.displayTransform || defaultDisplayTransform}
183
- />
184
- </MentionsInput>
185
- </div>
186
- ) : (
187
- <div className={styles.textarea_preview}>
188
- <CommentsBody body={value} />
189
- </div>
190
- )}
191
- </div>
192
- </div>
193
- );
194
- });