@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,206 @@
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(): 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
+ }
@@ -0,0 +1,201 @@
1
+ import { Autowired } from '@opensumi/di';
2
+ import {
3
+ Domain,
4
+ ClientAppContribution,
5
+ Disposable,
6
+ localize,
7
+ ContributionProvider,
8
+ Event,
9
+ ToolbarRegistry,
10
+ CommandContribution,
11
+ CommandRegistry,
12
+ getIcon,
13
+ TabBarToolbarContribution,
14
+ IEventBus,
15
+ } from '@opensumi/ide-core-browser';
16
+ import { IMenuRegistry, MenuId, MenuContribution } from '@opensumi/ide-core-browser/lib/menu/next';
17
+ import { IEditor } from '@opensumi/ide-editor';
18
+ import { BrowserEditorContribution, IEditorFeatureRegistry } from '@opensumi/ide-editor/lib/browser';
19
+ import { IMainLayoutService } from '@opensumi/ide-main-layout';
20
+
21
+ import {
22
+ ICommentsService,
23
+ CommentPanelId,
24
+ CommentsContribution,
25
+ ICommentsFeatureRegistry,
26
+ CollapseId,
27
+ CommentPanelCollapse,
28
+ CloseThreadId,
29
+ ICommentThreadTitle,
30
+ SwitchCommandReaction,
31
+ ICommentsThread,
32
+ CommentReactionPayload,
33
+ CommentReactionClick,
34
+ } from '../common';
35
+
36
+ import { CommentModelService } from './tree/tree-model.service';
37
+
38
+ @Domain(
39
+ ClientAppContribution,
40
+ BrowserEditorContribution,
41
+ CommandContribution,
42
+ TabBarToolbarContribution,
43
+ MenuContribution,
44
+ )
45
+ export class CommentsBrowserContribution
46
+ extends Disposable
47
+ implements
48
+ ClientAppContribution,
49
+ BrowserEditorContribution,
50
+ CommandContribution,
51
+ TabBarToolbarContribution,
52
+ MenuContribution
53
+ {
54
+ @Autowired(ICommentsService)
55
+ private readonly commentsService: ICommentsService;
56
+
57
+ @Autowired(IMainLayoutService)
58
+ private readonly layoutService: IMainLayoutService;
59
+
60
+ @Autowired(ICommentsFeatureRegistry)
61
+ private readonly commentsFeatureRegistry: ICommentsFeatureRegistry;
62
+
63
+ @Autowired(CommentsContribution)
64
+ private readonly contributions: ContributionProvider<CommentsContribution>;
65
+
66
+ @Autowired(IEventBus)
67
+ private readonly eventBus: IEventBus;
68
+
69
+ @Autowired(CommentModelService)
70
+ private commentModelService: CommentModelService;
71
+
72
+ onStart() {
73
+ this.registerCommentsFeature();
74
+ this.listenToCreateCommentsPanel();
75
+ this.commentsService.init();
76
+ }
77
+
78
+ get panelBadge() {
79
+ const length = this.commentsService.commentsThreads.length;
80
+ return length ? length + '' : '';
81
+ }
82
+
83
+ registerCommands(registry: CommandRegistry) {
84
+ registry.registerCommand(
85
+ {
86
+ id: CollapseId,
87
+ label: '%comments.panel.action.collapse%',
88
+ iconClass: getIcon('collapse-all'),
89
+ },
90
+ {
91
+ execute: () => {
92
+ this.commentModelService.collapsedAll();
93
+ },
94
+ },
95
+ );
96
+
97
+ registry.registerCommand(
98
+ {
99
+ id: CloseThreadId,
100
+ label: '%comments.thread.action.close%',
101
+ iconClass: getIcon('up'),
102
+ },
103
+ {
104
+ execute: (threadTitle: ICommentThreadTitle) => {
105
+ const { thread, widget } = threadTitle;
106
+ if (!thread.comments.length) {
107
+ thread.dispose();
108
+ } else {
109
+ if (widget.isShow) {
110
+ widget.toggle();
111
+ }
112
+ }
113
+ },
114
+ },
115
+ );
116
+
117
+ registry.registerCommand(
118
+ { id: SwitchCommandReaction },
119
+ {
120
+ execute: (payload: CommentReactionPayload) => {
121
+ this.eventBus.fire(new CommentReactionClick(payload));
122
+ },
123
+ },
124
+ );
125
+ }
126
+
127
+ registerMenus(registry: IMenuRegistry): void {
128
+ registry.registerMenuItem(MenuId.CommentsCommentThreadTitle, {
129
+ command: CloseThreadId,
130
+ group: 'inline',
131
+ order: Number.MAX_SAFE_INTEGER,
132
+ });
133
+ }
134
+
135
+ registerToolbarItems(registry: ToolbarRegistry) {
136
+ registry.registerItem({
137
+ id: CollapseId,
138
+ viewId: CommentPanelId,
139
+ command: CollapseId,
140
+ tooltip: localize('comments.panel.action.collapse'),
141
+ });
142
+ }
143
+
144
+ private registerCommentsFeature() {
145
+ this.contributions.getContributions().forEach((contribution, index) => {
146
+ this.addDispose(
147
+ this.commentsService.registerCommentRangeProvider(`contribution_${index}`, {
148
+ getCommentingRanges: (documentModel) => contribution.provideCommentingRanges(documentModel),
149
+ }),
150
+ );
151
+ if (contribution.registerCommentsFeature) {
152
+ contribution.registerCommentsFeature(this.commentsFeatureRegistry);
153
+ }
154
+ });
155
+ }
156
+ /**
157
+ * 因为大多数情况下没有评论,所以默认先不注册底部面板
158
+ * 在第一次创建 thread 的时候再创建底部面板
159
+ * @memberof CommentsBrowserContribution
160
+ */
161
+ private listenToCreateCommentsPanel() {
162
+ if (this.commentsFeatureRegistry.getCommentsPanelOptions().defaultShow) {
163
+ this.commentsService.registerCommentPanel();
164
+ } else {
165
+ Event.once(this.commentsService.onThreadsCreated)(() => {
166
+ this.commentsService.registerCommentPanel();
167
+ });
168
+ }
169
+
170
+ this.addDispose(
171
+ Event.debounce(
172
+ this.commentsService.onThreadsChanged,
173
+ () => {},
174
+ 100,
175
+ )(() => {
176
+ const handler = this.layoutService.getTabbarHandler(CommentPanelId);
177
+ handler?.setBadge(this.panelBadge);
178
+ }, this),
179
+ );
180
+ }
181
+
182
+ registerEditorFeature(registry: IEditorFeatureRegistry) {
183
+ registry.registerEditorFeatureContribution({
184
+ contribute: (editor: IEditor) => this.commentsService.handleOnCreateEditor(editor),
185
+ provideEditorOptionsForUri: async (uri) => {
186
+ const ranges = await this.commentsService.getContributionRanges(uri);
187
+
188
+ // 说明当前 uri 可以评论
189
+ if (ranges.length) {
190
+ return {
191
+ // 让编辑器的 lineDecorationsWidth 宽一点,以便放下评论 icon
192
+ lineDecorationsWidth: 25,
193
+ lineNumbersMinChars: 5,
194
+ };
195
+ } else {
196
+ return {};
197
+ }
198
+ },
199
+ });
200
+ }
201
+ }
@@ -0,0 +1,210 @@
1
+ :global {
2
+ .monaco-editor {
3
+ .comments-decoration {
4
+ border-radius: 50%;
5
+ // 防止被其他的 decoration 盖住
6
+ position: absolute;
7
+ cursor: pointer;
8
+ z-index: 1;
9
+ &::before {
10
+ font-weight: normal;
11
+ color: var(--foreground);
12
+ background: var(--editor-background);
13
+ border-radius: 50%;
14
+ }
15
+ }
16
+
17
+ .comments-add {
18
+ margin-left: 2px;
19
+ }
20
+
21
+ .comments-thread {
22
+ left: 4px !important;
23
+ }
24
+
25
+ .cldr.insert-sign,
26
+ .cldr.delete-sign {
27
+ background-position: right;
28
+ }
29
+ }
30
+ }
31
+
32
+ .comment_container {
33
+ font-size: 12px;
34
+ background-color: var(--kt-popover-background);
35
+ border-top: 1px solid var(--peekView-border);
36
+ border-bottom: 1px solid var(--peekView-border);
37
+ }
38
+
39
+ .head {
40
+ display: flex;
41
+ justify-content: space-between;
42
+ align-items: center;
43
+ height: 28px;
44
+ line-height: 22px;
45
+ padding: 0 2px 0 14px;
46
+ border-bottom: 1px solid var(--kt-tab-borderDown);
47
+ }
48
+
49
+ .review_title {
50
+ overflow: hidden;
51
+ text-overflow: ellipsis;
52
+ white-space: nowrap;
53
+ }
54
+
55
+ .title_actions {
56
+ display: flex;
57
+ }
58
+
59
+ .comment_body {
60
+ padding: 18px;
61
+ }
62
+
63
+ .comment_shadow_box {
64
+ padding: 8px 0;
65
+ }
66
+
67
+ .comment_bottom_actions {
68
+ margin-top: 12px;
69
+ }
70
+
71
+ .reply_item_title {
72
+ visibility: hidden;
73
+ display: inline-block;
74
+ margin-left: 8px;
75
+ }
76
+
77
+ .textarea_container {
78
+ background-color: var(--input-background);
79
+ padding: 4px 16px;
80
+ border-radius: 2px;
81
+ }
82
+
83
+ .mention {
84
+ width: 100%;
85
+ }
86
+
87
+ .textarea_preview {
88
+ min-height: 56px;
89
+ }
90
+
91
+ .comment_reply_actions {
92
+ justify-content: flex-start;
93
+ }
94
+
95
+ .comment_item {
96
+ display: flex;
97
+ }
98
+
99
+ .comment_item_content {
100
+ flex: 1;
101
+ }
102
+
103
+ .comment_item_head {
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: space-between;
107
+ margin-bottom: 4px;
108
+ }
109
+
110
+ .comment_item_author {
111
+ display: flex;
112
+ }
113
+
114
+ .comment_item_name {
115
+ display: flex;
116
+ align-items: center;
117
+ }
118
+
119
+ .comment_item_author_name {
120
+ line-height: 20px;
121
+ color: var(--descriptionForeground);
122
+ }
123
+
124
+ .comment_item_timestamp {
125
+ margin-left: 2px;
126
+ line-height: 20px;
127
+ color: var(--descriptionForeground);
128
+ }
129
+
130
+ .comment_item_markdown_header {
131
+ display: flex;
132
+ justify-content: space-between;
133
+ }
134
+
135
+ .comment_item_label {
136
+ margin-left: 5px;
137
+ color: var(--descriptionForeground);
138
+ }
139
+
140
+ .comment_item_icon {
141
+ width: 24px;
142
+ height: 24px;
143
+ border-radius: 50%;
144
+ margin-right: 10px;
145
+ }
146
+
147
+ .reply_item {
148
+ margin-bottom: 8px;
149
+ &:last-child {
150
+ margin-bottom: 0;
151
+ }
152
+ &:hover {
153
+ .reply_item_title {
154
+ visibility: visible;
155
+ }
156
+ }
157
+ }
158
+
159
+ .reply_item_icon {
160
+ width: 16px;
161
+ height: 16px;
162
+ border-radius: 50%;
163
+ margin-right: 8px;
164
+ }
165
+
166
+ .comment_item_actions {
167
+ display: flex;
168
+ }
169
+
170
+ .comment_item_reply_button {
171
+ margin: 0 6px;
172
+ }
173
+
174
+ .comment_item_reply_wrap {
175
+ margin-top: 4px;
176
+ padding: 14px 12px;
177
+ background: var(--kt-popover-prominentBackground);
178
+ border-radius: 2px;
179
+ }
180
+
181
+ .comment_item_reply {
182
+ margin-top: 12px;
183
+ }
184
+
185
+ .comment_item_context {
186
+ margin: 12px 0;
187
+ }
188
+
189
+ .comment_panel {
190
+ font-size: 12px;
191
+ line-height: 22px;
192
+ width: 100%;
193
+ height: 100%;
194
+ }
195
+
196
+ .panel_placeholder {
197
+ position: absolute;
198
+ top: 50%;
199
+ left: 50%;
200
+ transform: translate(-50%, -50%);
201
+ color: var(--kt-panel-secondaryForeground);
202
+ }
203
+
204
+ .comment_reactions {
205
+ margin-top: 4px;
206
+ }
207
+
208
+ .comment_reaction {
209
+ margin-right: 4px;
210
+ }