@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,300 +0,0 @@
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 } 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
-
20
- @Injectable({ multiple: true })
21
- export class CommentsThread extends Disposable implements ICommentsThread {
22
- @Autowired(ICommentsService)
23
- commentsService: ICommentsService;
24
-
25
- @Autowired(IContextKeyService)
26
- private readonly globalContextKeyService: IContextKeyService;
27
-
28
- private readonly _contextKeyService: IContextKeyService;
29
-
30
- @Autowired(EditorCollectionService)
31
- private readonly editorCollectionService: EditorCollectionService;
32
-
33
- @Autowired(INJECTOR_TOKEN)
34
- private readonly injector: Injector;
35
-
36
- @observable
37
- public comments: IThreadComment[];
38
-
39
- @observable
40
- public label: string | undefined;
41
-
42
- public data: any;
43
-
44
- set contextValue(value: string | undefined) {
45
- this._contextKeyService.createKey<string>('thread', value);
46
- }
47
-
48
- get contextValue() {
49
- return this._contextKeyService.getContextValue('thread');
50
- }
51
-
52
- private widgets = new Map<IEditor, CommentsZoneWidget>();
53
-
54
- private _id = `thread_${uuid()}`;
55
-
56
- @observable
57
- private _readOnly = false;
58
-
59
- @observable
60
- public isCollapsed: boolean;
61
-
62
- constructor(
63
- public uri: URI,
64
- public range: IRange,
65
- public providerId: string,
66
- public options: ICommentsThreadOptions,
67
- ) {
68
- super();
69
- this.comments = options.comments
70
- ? options.comments.map((comment) => ({
71
- ...comment,
72
- id: uuid(),
73
- }))
74
- : [];
75
- this.data = this.options.data;
76
- this._contextKeyService = this.registerDispose(this.globalContextKeyService.createScoped());
77
- // 设置 resource context key
78
- const resourceContext = new ResourceContextKey(this._contextKeyService);
79
- resourceContext.set(uri);
80
- this._contextKeyService.createKey<string>('thread', options.contextValue);
81
- this.readOnly = !!options.readOnly;
82
- this.label = options.label;
83
- this.isCollapsed = !!this.options.isCollapsed;
84
- const threadsLengthContext = this._contextKeyService.createKey<number>(
85
- 'threadsLength',
86
- this.commentsService.getThreadsByUri(uri).length,
87
- );
88
- const commentsLengthContext = this._contextKeyService.createKey<number>('commentsLength', this.comments.length);
89
- // vscode 用于判断 thread 是否为空
90
- const commentThreadIsEmptyContext = this._contextKeyService.createKey<boolean>(
91
- 'commentThreadIsEmpty',
92
- !this.comments.length,
93
- );
94
- // vscode 用于判断是否为当前 controller 注册
95
- this._contextKeyService.createKey<string>('commentController', providerId);
96
- // 监听 comments 的变化
97
- autorun(() => {
98
- commentsLengthContext.set(this.comments.length);
99
- commentThreadIsEmptyContext.set(!this.comments.length);
100
- });
101
- autorun(() => {
102
- if (this.isCollapsed) {
103
- this.hideAll();
104
- } else {
105
- this.showAll();
106
- }
107
- });
108
- // 监听每次 thread 的变化,重新设置 threadsLength
109
- this.addDispose(
110
- this.commentsService.onThreadsChanged((thread) => {
111
- if (thread.uri.isEqual(uri)) {
112
- threadsLengthContext.set(this.commentsService.getThreadsByUri(uri).length);
113
- }
114
- }),
115
- );
116
- this.addDispose({
117
- dispose: () => {
118
- this.comments = [];
119
- },
120
- });
121
- }
122
- getWidgetByEditor(editor: IEditor): ICommentsZoneWidget | undefined {
123
- return this.widgets.get(editor);
124
- }
125
-
126
- get id() {
127
- return this._id;
128
- }
129
-
130
- get contextKeyService() {
131
- return this._contextKeyService;
132
- }
133
-
134
- @computed
135
- get readOnly() {
136
- return this._readOnly;
137
- }
138
-
139
- set readOnly(readOnly: boolean) {
140
- this._readOnly = readOnly;
141
- this._contextKeyService.createKey<boolean>('readOnly', this._readOnly);
142
- }
143
-
144
- @computed
145
- get threadHeaderTitle() {
146
- if (this.label) {
147
- return this.label;
148
- }
149
- if (this.comments.length) {
150
- const commentAuthors = new Set<string>(this.comments.map((comment) => `@${comment.author.name}`));
151
- return `${localize('comments.participants')}: ` + [...commentAuthors].join(' ');
152
- } else {
153
- return localize('comments.zone.title');
154
- }
155
- }
156
-
157
- private getEditorsByUri(uri: URI): IEditor[] {
158
- return this.editorCollectionService.listEditors().filter((editor) => editor.currentUri?.isEqual(uri));
159
- }
160
-
161
- private addWidgetByEditor(editor: IEditor) {
162
- const widget = this.injector.get(CommentsZoneWidget, [editor, this]);
163
- // 如果当前 widget 发生高度变化,通知同一个 同一个 editor 的其他 range 相同的 thread 也重新计算一下高度
164
- this.addDispose(
165
- widget.onChangeZoneWidget(() => {
166
- const threads = this.commentsService.commentsThreads.filter((thread) => this.isEqual(thread));
167
- // 只需要 resize 当前 thread 之后的 thread
168
- const currentIndex = threads.findIndex((thread) => thread === this);
169
- const resizeThreads = threads.slice(currentIndex + 1);
170
- for (const thread of resizeThreads) {
171
- if (thread.isShowWidget(editor)) {
172
- const widget = thread.getWidgetByEditor(editor);
173
- widget?.resize();
174
- }
175
- }
176
- }),
177
- );
178
- this.addDispose(widget);
179
- this.widgets.set(editor, widget);
180
- editor.onDispose(() => {
181
- widget.dispose();
182
- this.widgets.delete(editor);
183
- });
184
- return widget;
185
- }
186
-
187
- public toggle = (editor: IEditor) => {
188
- if (this.comments.length > 0) {
189
- const widget = this.widgets.get(editor);
190
- if (widget) {
191
- widget.toggle();
192
- }
193
- } else {
194
- this.dispose();
195
- }
196
- };
197
-
198
- public show(editor?: IEditor) {
199
- if (editor) {
200
- let widget = this.widgets.get(editor);
201
- // 说明是在新的 group 中打开
202
- if (!widget) {
203
- widget = this.addWidgetByEditor(editor);
204
- }
205
- widget.show();
206
- } else {
207
- // 每次都拿所有的有这个 uri 的 editor
208
- const editors = this.getEditorsByUri(this.uri);
209
- editors.forEach((editor) => {
210
- let widget = this.widgets.get(editor);
211
- // 说明是在新的 group 中打开
212
- if (!widget) {
213
- widget = this.addWidgetByEditor(editor);
214
- }
215
- // 如果标记之前是已经展示的 widget,则调用 show 方法
216
- if (editor.currentUri?.isEqual(this.uri)) {
217
- widget.show();
218
- }
219
- });
220
- }
221
- }
222
-
223
- public showWidgetsIfShowed() {
224
- for (const editor of this.getEditorsByUri(this.uri)) {
225
- let widget = this.widgets.get(editor);
226
- // 说明是在新的 group 中打开
227
- if (!widget) {
228
- widget = this.addWidgetByEditor(editor);
229
- }
230
- // 如果标记之前是已经展示的 widget,则调用 show 方法
231
- if (editor.currentUri?.isEqual(this.uri) && widget.isShow) {
232
- widget.show();
233
- }
234
- }
235
- }
236
-
237
- public hideWidgetsByDispose(): void {
238
- for (const [editor, widget] of this.widgets) {
239
- !editor.currentUri?.isEqual(this.uri) && widget.dispose();
240
- }
241
- }
242
-
243
- public isShowWidget(editor?: IEditor) {
244
- if (editor) {
245
- const widget = this.widgets.get(editor);
246
- return widget ? widget.isShow : false;
247
- } else {
248
- for (const [, widget] of this.widgets) {
249
- return widget.isShow;
250
- }
251
- return false;
252
- }
253
- }
254
-
255
- public hide(editor?: IEditor) {
256
- if (editor) {
257
- const widget = this.widgets.get(editor);
258
- widget?.hide();
259
- } else {
260
- this.hideAll();
261
- }
262
- }
263
-
264
- public showAll() {
265
- for (const [, widget] of this.widgets) {
266
- widget.show();
267
- }
268
- }
269
-
270
- public hideAll(isDospose?: boolean) {
271
- for (const [editor, widget] of this.widgets) {
272
- if (isDospose) {
273
- // 如果 thread 出现在当前 editor 则不隐藏
274
- !editor.currentUri?.isEqual(this.uri) && widget.dispose();
275
- } else {
276
- widget.hide();
277
- }
278
- }
279
- }
280
-
281
- public addComment(...comments: IComment[]) {
282
- this.comments.push(
283
- ...comments.map((comment) => ({
284
- ...comment,
285
- id: uuid(),
286
- })),
287
- );
288
- }
289
-
290
- public removeComment(comment: IComment) {
291
- const index = this.comments.findIndex((c) => c === comment);
292
- if (index !== -1) {
293
- this.comments.splice(index, 1);
294
- }
295
- }
296
-
297
- public isEqual(thread: ICommentsThread): boolean {
298
- return thread.uri.isEqual(this.uri) && thread.range.startLineNumber === this.range.startLineNumber;
299
- }
300
- }
@@ -1,29 +0,0 @@
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
- }
@@ -1,206 +0,0 @@
1
- import clx from 'classnames';
2
- import { observer } from 'mobx-react-lite';
3
- import React from 'react';
4
- import ReactDOM from 'react-dom';
5
-
6
- import { INJECTOR_TOKEN, Injectable, Autowired } from '@opensumi/di';
7
- import { ConfigProvider, localize, AppConfig, useInjectable, Event, Emitter } from '@opensumi/ide-core-browser';
8
- import { InlineActionBar } from '@opensumi/ide-core-browser/lib/components/actions';
9
- import { MenuId } from '@opensumi/ide-core-browser/lib/menu/next';
10
- import { IEditor } from '@opensumi/ide-editor';
11
- import { ResizeZoneWidget } from '@opensumi/ide-monaco-enhance';
12
-
13
- import {
14
- ICommentReply,
15
- ICommentsZoneWidget,
16
- ICommentThreadTitle,
17
- ICommentsFeatureRegistry,
18
- ICommentsThread,
19
- } from '../common';
20
-
21
- import { CommentItem } from './comments-item.view';
22
- import { CommentsTextArea } from './comments-textarea.view';
23
- import { CommentsZoneService } from './comments-zone.service';
24
- import styles from './comments.module.less';
25
-
26
- export interface ICommentProps {
27
- thread: ICommentsThread;
28
- widget: ICommentsZoneWidget;
29
- }
30
-
31
- const CommentsZone: React.FC<ICommentProps> = observer(({ thread, widget }) => {
32
- const { comments, threadHeaderTitle, contextKeyService } = thread;
33
- const injector = useInjectable(INJECTOR_TOKEN);
34
- const commentsZoneService: CommentsZoneService = injector.get(CommentsZoneService, [thread]);
35
- const commentsFeatureRegistry = useInjectable<ICommentsFeatureRegistry>(ICommentsFeatureRegistry);
36
- const fileUploadHandler = React.useMemo(() => commentsFeatureRegistry.getFileUploadHandler(), []);
37
- const [replyText, setReplyText] = React.useState('');
38
- const commentIsEmptyContext = React.useMemo(
39
- () => contextKeyService.createKey<boolean>('commentIsEmpty', !replyText),
40
- [],
41
- );
42
- const commentThreadTitle = commentsZoneService.commentThreadTitle;
43
- const commentThreadContext = commentsZoneService.commentThreadContext;
44
-
45
- const onChangeReply = React.useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
46
- const { value } = event.target;
47
- setReplyText(value);
48
- commentIsEmptyContext.set(!value);
49
- }, []);
50
-
51
- const placeholder = React.useMemo(
52
- () =>
53
- commentsFeatureRegistry.getProviderFeature(thread.providerId)?.placeholder ||
54
- `${localize('comments.reply.placeholder')}...`,
55
- [],
56
- );
57
-
58
- const handleDragFiles = React.useCallback(
59
- async (files: FileList) => {
60
- if (fileUploadHandler) {
61
- const appendText = await fileUploadHandler(replyText, files);
62
- setReplyText((text) => {
63
- const value = text + appendText;
64
- commentIsEmptyContext.set(!value);
65
- return value;
66
- });
67
- }
68
- },
69
- [replyText],
70
- );
71
-
72
- React.useEffect(() => {
73
- const disposer = widget.onFirstDisplay(() => {
74
- setTimeout(() => {
75
- widget.coreEditor.monacoEditor.revealLine(thread.range.startLineNumber + 1);
76
- }, 0);
77
- });
78
- return () => {
79
- disposer.dispose();
80
- };
81
- }, []);
82
-
83
- return (
84
- <div className={clx(thread.options.threadClassName, styles.comment_container)}>
85
- <div className={clx(thread.options.threadHeadClassName, styles.head)}>
86
- <div className={styles.review_title}>{threadHeaderTitle}</div>
87
- <InlineActionBar<ICommentThreadTitle>
88
- menus={commentThreadTitle}
89
- context={[
90
- {
91
- thread,
92
- widget,
93
- menuId: MenuId.CommentsCommentThreadTitle,
94
- },
95
- ]}
96
- separator='inline'
97
- type='icon'
98
- />
99
- </div>
100
- <div className={styles.comment_body}>
101
- {comments.length > 0 ? (
102
- <CommentItem widget={widget} commentThreadContext={commentThreadContext} thread={thread} />
103
- ) : (
104
- <div>
105
- <CommentsTextArea
106
- focusDelay={100}
107
- initialHeight={'auto'}
108
- value={replyText}
109
- onChange={onChangeReply}
110
- placeholder={placeholder}
111
- dragFiles={handleDragFiles}
112
- />
113
- <div className={styles.comment_bottom_actions}>
114
- <InlineActionBar<ICommentReply>
115
- className={styles.comment_reply_actions}
116
- separator='inline'
117
- type='button'
118
- context={[
119
- {
120
- text: replyText,
121
- widget,
122
- thread,
123
- menuId: MenuId.CommentsCommentThreadContext,
124
- },
125
- ]}
126
- menus={commentThreadContext}
127
- />
128
- </div>
129
- </div>
130
- )}
131
- </div>
132
- </div>
133
- );
134
- });
135
-
136
- @Injectable({ multiple: true })
137
- export class CommentsZoneWidget extends ResizeZoneWidget implements ICommentsZoneWidget {
138
- protected _fillContainer(container: HTMLElement): void {}
139
- @Autowired(AppConfig)
140
- appConfig: AppConfig;
141
-
142
- @Autowired(ICommentsFeatureRegistry)
143
- private readonly commentsFeatureRegistry: ICommentsFeatureRegistry;
144
-
145
- private _wrapper: HTMLDivElement;
146
-
147
- private _editor: IEditor;
148
-
149
- private _onShow = new Emitter<void>();
150
- public onShow: Event<void> = this._onShow.event;
151
-
152
- private _onHide = new Emitter<void>();
153
- public onHide: Event<void> = this._onHide.event;
154
-
155
- constructor(editor: IEditor, thread: ICommentsThread) {
156
- super(editor.monacoEditor, thread.range);
157
- this._editor = editor;
158
- this._wrapper = document.createElement('div');
159
- this._isShow = !thread.isCollapsed;
160
- this._container.appendChild(this._wrapper);
161
- this.observeContainer(this._wrapper);
162
- const customRender = this.commentsFeatureRegistry.getZoneWidgetRender();
163
- ReactDOM.render(
164
- <ConfigProvider value={this.appConfig}>
165
- {customRender ? customRender(thread, this) : <CommentsZone thread={thread} widget={this} />}
166
- </ConfigProvider>,
167
- this._wrapper,
168
- );
169
- }
170
-
171
- get coreEditor() {
172
- return this._editor;
173
- }
174
-
175
- get isShow() {
176
- return this._isShow;
177
- }
178
-
179
- public show() {
180
- super.show();
181
- this._isShow = true;
182
- this._onShow.fire();
183
- }
184
-
185
- public hide() {
186
- super.dispose();
187
- this._isShow = false;
188
- this._onHide.fire();
189
- }
190
-
191
- public toggle() {
192
- if (this._isShow) {
193
- this.hide();
194
- } else {
195
- this.show();
196
- }
197
- }
198
-
199
- protected applyClass(): void {
200
- // noop
201
- }
202
-
203
- protected applyStyle(): void {
204
- // noop
205
- }
206
- }