@opensumi/ide-comments 2.21.13 → 2.22.0

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.
Files changed (64) hide show
  1. package/lib/browser/comments-feature.registry.js.map +1 -1
  2. package/lib/browser/comments-panel.view.d.ts +2 -3
  3. package/lib/browser/comments-panel.view.d.ts.map +1 -1
  4. package/lib/browser/comments-panel.view.js +32 -70
  5. package/lib/browser/comments-panel.view.js.map +1 -1
  6. package/lib/browser/comments-thread.d.ts +4 -2
  7. package/lib/browser/comments-thread.d.ts.map +1 -1
  8. package/lib/browser/comments-thread.js +14 -7
  9. package/lib/browser/comments-thread.js.map +1 -1
  10. package/lib/browser/comments-zone.service.js.map +1 -1
  11. package/lib/browser/comments-zone.view.d.ts +1 -1
  12. package/lib/browser/comments-zone.view.d.ts.map +1 -1
  13. package/lib/browser/comments-zone.view.js +1 -1
  14. package/lib/browser/comments-zone.view.js.map +1 -1
  15. package/lib/browser/comments.contribution.d.ts +1 -0
  16. package/lib/browser/comments.contribution.d.ts.map +1 -1
  17. package/lib/browser/comments.contribution.js +6 -1
  18. package/lib/browser/comments.contribution.js.map +1 -1
  19. package/lib/browser/comments.service.d.ts +7 -4
  20. package/lib/browser/comments.service.d.ts.map +1 -1
  21. package/lib/browser/comments.service.js +60 -87
  22. package/lib/browser/comments.service.js.map +1 -1
  23. package/lib/browser/index.d.ts +0 -1
  24. package/lib/browser/index.d.ts.map +1 -1
  25. package/lib/browser/index.js +5 -1
  26. package/lib/browser/index.js.map +1 -1
  27. package/lib/browser/tree/comment-node.d.ts +14 -0
  28. package/lib/browser/tree/comment-node.d.ts.map +1 -0
  29. package/lib/browser/tree/comment-node.js +64 -0
  30. package/lib/browser/tree/comment-node.js.map +1 -0
  31. package/lib/browser/tree/tree-model.service.d.ts +43 -0
  32. package/lib/browser/tree/tree-model.service.d.ts.map +1 -0
  33. package/lib/browser/tree/tree-model.service.js +150 -0
  34. package/lib/browser/tree/tree-model.service.js.map +1 -0
  35. package/lib/browser/tree/tree-node.defined.d.ts +61 -0
  36. package/lib/browser/tree/tree-node.defined.d.ts.map +1 -0
  37. package/lib/browser/tree/tree-node.defined.js +116 -0
  38. package/lib/browser/tree/tree-node.defined.js.map +1 -0
  39. package/lib/browser/tree/tree-node.module.less +154 -0
  40. package/lib/common/index.d.ts +38 -36
  41. package/lib/common/index.d.ts.map +1 -1
  42. package/lib/common/index.js.map +1 -1
  43. package/package.json +13 -12
  44. package/src/browser/comment-reactions.view.tsx +109 -0
  45. package/src/browser/comments-body.tsx +57 -0
  46. package/src/browser/comments-feature.registry.ts +91 -0
  47. package/src/browser/comments-item.view.tsx +362 -0
  48. package/src/browser/comments-panel.view.tsx +90 -0
  49. package/src/browser/comments-textarea.view.tsx +194 -0
  50. package/src/browser/comments-thread.ts +309 -0
  51. package/src/browser/comments-zone.service.ts +29 -0
  52. package/src/browser/comments-zone.view.tsx +206 -0
  53. package/src/browser/comments.contribution.ts +201 -0
  54. package/src/browser/comments.module.less +210 -0
  55. package/src/browser/comments.service.ts +546 -0
  56. package/src/browser/index.ts +29 -0
  57. package/src/browser/markdown.style.ts +25 -0
  58. package/src/browser/mentions.style.ts +55 -0
  59. package/src/browser/tree/comment-node.tsx +130 -0
  60. package/src/browser/tree/tree-model.service.ts +173 -0
  61. package/src/browser/tree/tree-node.defined.ts +167 -0
  62. package/src/browser/tree/tree-node.module.less +154 -0
  63. package/src/common/index.ts +710 -0
  64. package/src/index.ts +1 -0
@@ -0,0 +1,90 @@
1
+ import clx from 'classnames';
2
+ import React, { FC, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+
4
+ import { IRecycleTreeHandle, RecycleTree } from '@opensumi/ide-components';
5
+ import { useInjectable, ViewState, localize } from '@opensumi/ide-core-browser';
6
+
7
+ import { ICommentsFeatureRegistry } from '../common';
8
+
9
+ import styles from './comments.module.less';
10
+ import { CommentNodeRendered, COMMENT_TREE_NODE_HEIGHT, ICommentNodeRenderedProps } from './tree/comment-node';
11
+ import { CommentModelService, CommentTreeModel } from './tree/tree-model.service';
12
+
13
+ export const CommentsPanel: FC<{ viewState: ViewState }> = ({ viewState }) => {
14
+ const commentModelService = useInjectable<CommentModelService>(CommentModelService);
15
+ const [model, setModel] = useState<CommentTreeModel | undefined>();
16
+ const wrapperRef: RefObject<HTMLDivElement> = useRef(null);
17
+
18
+ const commentsFeatureRegistry = useInjectable<ICommentsFeatureRegistry>(ICommentsFeatureRegistry);
19
+
20
+ const { handleTreeBlur } = commentModelService;
21
+
22
+ useEffect(() => {
23
+ setModel(commentModelService.treeModel);
24
+ const disposable = commentModelService.onDidUpdateTreeModel((model?: CommentTreeModel) => {
25
+ setModel(model);
26
+ });
27
+ return () => {
28
+ disposable.dispose();
29
+ };
30
+ }, []);
31
+
32
+ const handleTreeReady = useCallback(
33
+ (handle: IRecycleTreeHandle) => {
34
+ commentModelService.handleTreeHandler(handle);
35
+ },
36
+ [commentModelService],
37
+ );
38
+
39
+ const renderTreeNode = useCallback(
40
+ (props: ICommentNodeRenderedProps) => (
41
+ <CommentNodeRendered
42
+ item={props.item}
43
+ itemType={props.itemType}
44
+ decorations={commentModelService.decorations.getDecorations(props.item as any)}
45
+ defaultLeftPadding={8}
46
+ onClick={commentModelService.handleItemClick}
47
+ leftPadding={8}
48
+ />
49
+ ),
50
+ [model],
51
+ );
52
+
53
+ const commentsPanelOptions = useMemo(() => commentsFeatureRegistry.getCommentsPanelOptions(), []);
54
+
55
+ const headerComponent = useMemo(() => commentsPanelOptions.header, [commentsPanelOptions]);
56
+
57
+ const defaultPlaceholder = useMemo(
58
+ () => (
59
+ <div className={styles.panel_placeholder}>
60
+ {commentsPanelOptions.defaultPlaceholder || localize('comments.panel.placeholder')}
61
+ </div>
62
+ ),
63
+ [commentsPanelOptions],
64
+ );
65
+
66
+ const renderSearchTree = useCallback(() => {
67
+ if (model) {
68
+ return (
69
+ <RecycleTree
70
+ height={viewState.height - (headerComponent?.height || 0)}
71
+ itemHeight={COMMENT_TREE_NODE_HEIGHT}
72
+ onReady={handleTreeReady}
73
+ model={model}
74
+ placeholder={() => defaultPlaceholder}
75
+ >
76
+ {renderTreeNode}
77
+ </RecycleTree>
78
+ );
79
+ } else {
80
+ return defaultPlaceholder;
81
+ }
82
+ }, [model, headerComponent, viewState.height]);
83
+
84
+ return (
85
+ <div className={styles.comment_panel} tabIndex={-1} onBlur={handleTreeBlur} ref={wrapperRef}>
86
+ {headerComponent?.component}
87
+ {renderSearchTree()}
88
+ </div>
89
+ );
90
+ };
@@ -0,0 +1,194 @@
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
+ });
@@ -0,0 +1,309 @@
1
+ import { observable, computed, autorun } from 'mobx';
2
+
3
+ import { Injectable, Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di';
4
+ import { IRange, Disposable, URI, IContextKeyService, uuid, localize, Emitter } from '@opensumi/ide-core-browser';
5
+ import { ResourceContextKey } from '@opensumi/ide-core-browser/lib/contextkey/resource';
6
+ import { IEditor, EditorCollectionService } from '@opensumi/ide-editor';
7
+
8
+ import {
9
+ ICommentsThread,
10
+ IComment,
11
+ ICommentsThreadOptions,
12
+ ICommentsService,
13
+ IThreadComment,
14
+ ICommentsZoneWidget,
15
+ } from '../common';
16
+
17
+ import { CommentsZoneWidget } from './comments-zone.view';
18
+
19
+ @Injectable({ multiple: true })
20
+ export class CommentsThread extends Disposable implements ICommentsThread {
21
+ @Autowired(ICommentsService)
22
+ commentsService: ICommentsService;
23
+
24
+ @Autowired(IContextKeyService)
25
+ private readonly globalContextKeyService: IContextKeyService;
26
+
27
+ private readonly _contextKeyService: IContextKeyService;
28
+
29
+ @Autowired(EditorCollectionService)
30
+ private readonly editorCollectionService: EditorCollectionService;
31
+
32
+ @Autowired(INJECTOR_TOKEN)
33
+ private readonly injector: Injector;
34
+
35
+ // FIXME: update by https://github.com/opensumi/core/blob/82ab63b916c8fe90cf5898d55c0fe335dd852b91/packages/extension/src/browser/vscode/api/main.thread.comments.ts#L319
36
+ @observable
37
+ public comments: IThreadComment[];
38
+
39
+ @observable
40
+ public label: string | undefined;
41
+
42
+ @observable
43
+ private _readOnly = false;
44
+
45
+ @observable
46
+ public isCollapsed: boolean;
47
+
48
+ public data: any;
49
+
50
+ set contextValue(value: string | undefined) {
51
+ this._contextKeyService.createKey<string>('thread', value);
52
+ }
53
+
54
+ get contextValue() {
55
+ return this._contextKeyService.getContextValue('thread');
56
+ }
57
+
58
+ private widgets = new Map<IEditor, CommentsZoneWidget>();
59
+
60
+ private _id = `thread_${uuid()}`;
61
+
62
+ private onDidChangeEmitter: Emitter<void> = new Emitter();
63
+
64
+ get onDidChange() {
65
+ return this.onDidChangeEmitter.event;
66
+ }
67
+
68
+ constructor(
69
+ public uri: URI,
70
+ public range: IRange,
71
+ public providerId: string,
72
+ public options: ICommentsThreadOptions,
73
+ ) {
74
+ super();
75
+ this.comments = options.comments
76
+ ? options.comments.map((comment) => ({
77
+ ...comment,
78
+ id: uuid(),
79
+ }))
80
+ : [];
81
+ this.data = this.options.data;
82
+ this._contextKeyService = this.registerDispose(this.globalContextKeyService.createScoped());
83
+ // 设置 resource context key
84
+ const resourceContext = new ResourceContextKey(this._contextKeyService);
85
+ resourceContext.set(uri);
86
+ this._contextKeyService.createKey<string>('thread', options.contextValue);
87
+ this.readOnly = !!options.readOnly;
88
+ this.label = options.label;
89
+ this.isCollapsed = !!this.options.isCollapsed;
90
+ const threadsLengthContext = this._contextKeyService.createKey<number>(
91
+ 'threadsLength',
92
+ this.commentsService.getThreadsByUri(uri).length,
93
+ );
94
+ const commentsLengthContext = this._contextKeyService.createKey<number>('commentsLength', this.comments.length);
95
+ // vscode 用于判断 thread 是否为空
96
+ const commentThreadIsEmptyContext = this._contextKeyService.createKey<boolean>(
97
+ 'commentThreadIsEmpty',
98
+ !this.comments.length,
99
+ );
100
+ // vscode 用于判断是否为当前 controller 注册
101
+ this._contextKeyService.createKey<string>('commentController', providerId);
102
+ // 监听 comments 的变化
103
+ autorun(() => {
104
+ commentsLengthContext.set(this.comments.length);
105
+ commentThreadIsEmptyContext.set(!this.comments.length);
106
+ });
107
+ autorun(() => {
108
+ if (this.isCollapsed) {
109
+ this.hideAll();
110
+ } else {
111
+ this.showAll();
112
+ }
113
+ });
114
+ // 监听每次 thread 的变化,重新设置 threadsLength
115
+ this.addDispose(
116
+ this.commentsService.onThreadsChanged((thread) => {
117
+ if (thread.uri.isEqual(uri)) {
118
+ threadsLengthContext.set(this.commentsService.getThreadsByUri(uri).length);
119
+ }
120
+ }),
121
+ );
122
+ this.addDispose({
123
+ dispose: () => {
124
+ this.comments = [];
125
+ },
126
+ });
127
+ this.onDidChangeEmitter.fire();
128
+ }
129
+ getWidgetByEditor(editor: IEditor): ICommentsZoneWidget | undefined {
130
+ return this.widgets.get(editor);
131
+ }
132
+
133
+ get id() {
134
+ return this._id;
135
+ }
136
+
137
+ get contextKeyService() {
138
+ return this._contextKeyService;
139
+ }
140
+
141
+ @computed
142
+ get readOnly() {
143
+ return this._readOnly;
144
+ }
145
+
146
+ set readOnly(readOnly: boolean) {
147
+ this._readOnly = readOnly;
148
+ this._contextKeyService.createKey<boolean>('readOnly', this._readOnly);
149
+ }
150
+
151
+ @computed
152
+ get threadHeaderTitle() {
153
+ if (this.label) {
154
+ return this.label;
155
+ }
156
+ if (this.comments.length) {
157
+ const commentAuthors = new Set<string>(this.comments.map((comment) => `@${comment.author.name}`));
158
+ return `${localize('comments.participants')}: ` + [...commentAuthors].join(' ');
159
+ } else {
160
+ return localize('comments.zone.title');
161
+ }
162
+ }
163
+
164
+ private getEditorsByUri(uri: URI): IEditor[] {
165
+ return this.editorCollectionService.listEditors().filter((editor) => editor.currentUri?.isEqual(uri));
166
+ }
167
+
168
+ private addWidgetByEditor(editor: IEditor) {
169
+ const widget = this.injector.get(CommentsZoneWidget, [editor, this]);
170
+ // 如果当前 widget 发生高度变化,通知同一个 同一个 editor 的其他 range 相同的 thread 也重新计算一下高度
171
+ this.addDispose(
172
+ widget.onChangeZoneWidget(() => {
173
+ const threads = this.commentsService.commentsThreads.filter((thread) => this.isEqual(thread));
174
+ // 只需要 resize 当前 thread 之后的 thread
175
+ const currentIndex = threads.findIndex((thread) => thread === this);
176
+ const resizeThreads = threads.slice(currentIndex + 1);
177
+ for (const thread of resizeThreads) {
178
+ if (thread.isShowWidget(editor)) {
179
+ const widget = thread.getWidgetByEditor(editor);
180
+ widget?.resize();
181
+ }
182
+ }
183
+ }),
184
+ );
185
+ this.addDispose(widget);
186
+ this.widgets.set(editor, widget);
187
+ editor.onDispose(() => {
188
+ widget.dispose();
189
+ this.widgets.delete(editor);
190
+ });
191
+ return widget;
192
+ }
193
+
194
+ public toggle = (editor: IEditor) => {
195
+ if (this.comments.length > 0) {
196
+ const widget = this.widgets.get(editor);
197
+ if (widget) {
198
+ widget.toggle();
199
+ }
200
+ } else {
201
+ this.dispose();
202
+ }
203
+ };
204
+
205
+ public show(editor?: IEditor) {
206
+ if (editor) {
207
+ let widget = this.widgets.get(editor);
208
+ // 说明是在新的 group 中打开
209
+ if (!widget) {
210
+ widget = this.addWidgetByEditor(editor);
211
+ }
212
+ widget.show();
213
+ } else {
214
+ // 每次都拿所有的有这个 uri 的 editor
215
+ const editors = this.getEditorsByUri(this.uri);
216
+ editors.forEach((editor) => {
217
+ let widget = this.widgets.get(editor);
218
+ // 说明是在新的 group 中打开
219
+ if (!widget) {
220
+ widget = this.addWidgetByEditor(editor);
221
+ }
222
+ // 如果标记之前是已经展示的 widget,则调用 show 方法
223
+ if (editor.currentUri?.isEqual(this.uri)) {
224
+ widget.show();
225
+ }
226
+ });
227
+ }
228
+ }
229
+
230
+ public showWidgetsIfShowed() {
231
+ for (const editor of this.getEditorsByUri(this.uri)) {
232
+ let widget = this.widgets.get(editor);
233
+ // 说明是在新的 group 中打开
234
+ if (!widget) {
235
+ widget = this.addWidgetByEditor(editor);
236
+ }
237
+ // 如果标记之前是已经展示的 widget,则调用 show 方法
238
+ if (editor.currentUri?.isEqual(this.uri) && widget.isShow) {
239
+ widget.show();
240
+ }
241
+ }
242
+ }
243
+
244
+ public hideWidgetsByDispose(): void {
245
+ for (const [editor, widget] of this.widgets) {
246
+ !editor.currentUri?.isEqual(this.uri) && widget.dispose();
247
+ }
248
+ }
249
+
250
+ public isShowWidget(editor?: IEditor) {
251
+ if (editor) {
252
+ const widget = this.widgets.get(editor);
253
+ return widget ? widget.isShow : false;
254
+ } else {
255
+ for (const [, widget] of this.widgets) {
256
+ return widget.isShow;
257
+ }
258
+ return false;
259
+ }
260
+ }
261
+
262
+ public hide(editor?: IEditor) {
263
+ if (editor) {
264
+ const widget = this.widgets.get(editor);
265
+ widget?.hide();
266
+ } else {
267
+ this.hideAll();
268
+ }
269
+ }
270
+
271
+ public showAll() {
272
+ for (const [, widget] of this.widgets) {
273
+ widget.show();
274
+ }
275
+ }
276
+
277
+ public hideAll(isDospose?: boolean) {
278
+ for (const [editor, widget] of this.widgets) {
279
+ if (isDospose) {
280
+ // 如果 thread 出现在当前 editor 则不隐藏
281
+ !editor.currentUri?.isEqual(this.uri) && widget.dispose();
282
+ } else {
283
+ widget.hide();
284
+ }
285
+ }
286
+ }
287
+
288
+ public addComment(...comments: IComment[]) {
289
+ this.comments.push(
290
+ ...comments.map((comment) => ({
291
+ ...comment,
292
+ id: uuid(),
293
+ })),
294
+ );
295
+ this.onDidChangeEmitter.fire();
296
+ }
297
+
298
+ public removeComment(comment: IComment) {
299
+ const index = this.comments.findIndex((c) => c === comment);
300
+ if (index !== -1) {
301
+ this.comments.splice(index, 1);
302
+ }
303
+ this.onDidChangeEmitter.fire();
304
+ }
305
+
306
+ public isEqual(thread: ICommentsThread): boolean {
307
+ return thread.uri.isEqual(this.uri) && thread.range.startLineNumber === this.range.startLineNumber;
308
+ }
309
+ }
@@ -0,0 +1,29 @@
1
+ import { Autowired, Injectable, Optional } from '@opensumi/di';
2
+ import { AbstractMenuService, MenuId, IMenu } from '@opensumi/ide-core-browser/lib/menu/next';
3
+ import { Disposable, memoize } from '@opensumi/ide-core-common';
4
+
5
+ import { CommentsThread } from './comments-thread';
6
+
7
+ @Injectable({ multiple: true })
8
+ export class CommentsZoneService extends Disposable {
9
+ @Autowired(AbstractMenuService)
10
+ private readonly menuService: AbstractMenuService;
11
+
12
+ constructor(@Optional() readonly thread: CommentsThread) {
13
+ super();
14
+ }
15
+
16
+ @memoize
17
+ get commentThreadTitle(): IMenu {
18
+ return this.registerDispose(
19
+ this.menuService.createMenu(MenuId.CommentsCommentThreadTitle, this.thread.contextKeyService),
20
+ );
21
+ }
22
+
23
+ @memoize
24
+ get commentThreadContext(): IMenu {
25
+ return this.registerDispose(
26
+ this.menuService.createMenu(MenuId.CommentsCommentThreadContext, this.thread.contextKeyService),
27
+ );
28
+ }
29
+ }