From cae0668a0ed9fb9be106de11ab5d5725f12f9a4b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 5 Jun 2019 21:08:41 -0700 Subject: [PATCH] reactions handler --- src/vs/vscode.d.ts | 35 +++++++++++ src/vs/vscode.proposed.d.ts | 3 - .../api/browser/mainThreadComments.ts | 3 + .../workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostComments.ts | 50 ++++++++++++---- .../workbench/browser/web.simpleservices.ts | 1 + .../contrib/comments/browser/commentNode.ts | 59 +++++++++++-------- .../comments/browser/commentService.ts | 11 ++++ 8 files changed, 126 insertions(+), 37 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 6add914d3b9..f92802372e3 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -9054,6 +9054,31 @@ declare module 'vscode' { iconPath?: Uri; } + /** + * Reactions of a [comment](#Comment) + */ + export interface CommentReaction { + /** + * The human-readable label for the reaction + */ + readonly label: string; + + /** + * Icon for the reaction shown in UI. + */ + readonly iconPath: string | Uri; + + /** + * The number of users who have reacted to this reaction + */ + readonly count: number; + + /** + * Whether the [author](CommentAuthorInformation) of the comment has reacted to this reaction + */ + readonly authorHasReacted: boolean; + } + /** * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. */ @@ -9093,6 +9118,11 @@ declare module 'vscode' { */ contextValue?: string; + /** + * Optional reactions of the [comment](#Comment) + */ + reactions?: CommentReaction[]; + /** * Optional label describing the [Comment](#Comment) * Label will be rendered next to authorName if exists. @@ -9157,6 +9187,11 @@ declare module 'vscode' { */ createCommentThread(uri: Uri, range: Range, comments: Comment[]): CommentThread; + /** + * Optional reaction handler for creating and deleting reactions on a [comment](#Comment). + */ + reactionHandler?: (comment: Comment, reaction: CommentReaction) => Promise; + /** * Dispose this comment controller. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 177aeee3353..771f8a65c13 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -851,9 +851,6 @@ declare module 'vscode' { * Stay in proposed. */ interface CommentReaction { - readonly label?: string; - readonly iconPath?: string | Uri; - count?: number; readonly hasReacted?: boolean; } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index aa0e454810a..292a0c03256 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -284,6 +284,9 @@ export class MainThreadCommentController { private readonly _threads: Map = new Map(); public activeCommentThread?: MainThreadCommentThread; + get features(): CommentProviderFeatures { + return this._features; + } constructor( private readonly _proxy: ExtHostCommentsShape, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ed5dec5a66f..331ff4d517e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -131,6 +131,7 @@ export interface CommentProviderFeatures { finishDraftLabel?: string; reactionGroup?: modes.CommentReaction[]; commentThreadTemplate?: CommentThreadTemplate; + reactionHandler?: boolean; } export interface MainThreadCommentsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 9276ca5f685..afeecb3d37d 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -210,7 +210,7 @@ export class ExtHostComments implements ExtHostCommentsShape { const document = this._documents.getDocument(URI.revive(uri)); const commentController = this._commentControllers.get(commentControllerHandle); - if (!commentController || !commentController.reactionProvider || !commentController.reactionProvider.toggleReaction) { + if (!commentController || !((commentController.reactionProvider && commentController.reactionProvider.toggleReaction) || commentController.reactionHandler)) { return Promise.resolve(undefined); } @@ -219,9 +219,16 @@ export class ExtHostComments implements ExtHostCommentsShape { if (commentThread) { const vscodeComment = commentThread.getComment(comment.commentId); - if (commentController !== undefined && commentController.reactionProvider && commentController.reactionProvider.toggleReaction && vscodeComment) { - return commentController.reactionProvider.toggleReaction(document, vscodeComment, convertFromReaction(reaction)); + if (commentController !== undefined && vscodeComment) { + if (commentController.reactionHandler) { + return commentController.reactionHandler(vscodeComment, convertFromReaction(reaction)); + } + + if (commentController.reactionProvider && commentController.reactionProvider.toggleReaction) { + return commentController.reactionProvider.toggleReaction(document, vscodeComment, convertFromReaction(reaction)); + } } + } return Promise.resolve(undefined); @@ -635,7 +642,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { } getComment(commentId: string): vscode.Comment | undefined { - const comments = this._comments.filter(comment => comment.commentId === commentId); + const comments = this._comments.filter(comment => (comment.commentId === commentId || comment.id === commentId)); if (comments && comments.length) { return comments[0]; @@ -707,6 +714,9 @@ export class ExtHostCommentInputBox implements vscode.CommentInputBox { this._value = input; } } + +type ReactionHandler = (comment: vscode.Comment, reaction: vscode.CommentReaction) => Promise; + class ExtHostCommentController implements vscode.CommentController { get id(): string { return this._id; @@ -740,6 +750,18 @@ class ExtHostCommentController implements vscode.CommentController { } } + private _reactionHandler?: ReactionHandler; + + get reactionHandler(): ReactionHandler | undefined { + return this._reactionHandler; + } + + set reactionHandler(handler: ReactionHandler | undefined) { + this._reactionHandler = handler; + + this._proxy.$updateCommentControllerFeatures(this.handle, { reactionHandler: !!handler }); + } + constructor( _extension: IExtensionDescription, private _handle: number, @@ -861,9 +883,12 @@ function convertFromComment(comment: modes.Comment): vscode.Comment { isDraft: comment.isDraft, commentReactions: comment.commentReactions ? comment.commentReactions.map(reaction => { return { - label: reaction.label, - count: reaction.count, - hasReacted: reaction.hasReacted + label: reaction.label || '', + count: reaction.count || 0, + iconPath: reaction.iconPath ? URI.revive(reaction.iconPath) : '', + hasReacted: reaction.hasReacted, + authorHasReacted: reaction.hasReacted || false + }; }) : undefined, mode: comment.mode ? comment.mode : modes.CommentMode.Preview @@ -878,6 +903,7 @@ function convertToModeComment2(thread: ExtHostCommentThread, commentController: } const iconPath = vscodeComment.author && vscodeComment.author.iconPath ? vscodeComment.author.iconPath.toString() : undefined; + const reactions = vscodeComment.reactions || vscodeComment.commentReactions; return { commentId: vscodeComment.id || vscodeComment.commentId, @@ -892,7 +918,7 @@ function convertToModeComment2(thread: ExtHostCommentThread, commentController: editCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.editCommand) : undefined, deleteCommand: vscodeComment.deleteCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand) : undefined, label: vscodeComment.label, - commentReactions: vscodeComment.commentReactions ? vscodeComment.commentReactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction)) : undefined + commentReactions: reactions ? reactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction)) : undefined }; } @@ -939,9 +965,11 @@ function convertToReaction2(provider: vscode.CommentReactionProvider | undefined function convertFromReaction(reaction: modes.CommentReaction): vscode.CommentReaction { return { - label: reaction.label, - count: reaction.count, - hasReacted: reaction.hasReacted + label: reaction.label || '', + count: reaction.count || 0, + iconPath: reaction.iconPath ? URI.revive(reaction.iconPath) : '', + hasReacted: reaction.hasReacted, + authorHasReacted: reaction.hasReacted || false }; } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 49385f06a2e..effbef1620f 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -649,6 +649,7 @@ export class SimpleCommentService implements ICommentService { addReaction: any; deleteReaction: any; getReactionGroup: any; + hasReactionHandler: any; toggleReaction: any; setActiveCommentThread: any; } diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 09e2426a914..63c9f7e5968 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -119,7 +119,7 @@ export class CommentNode extends Disposable { this._md = this.markdownRenderer.render(comment.body).element; this._body.appendChild(this._md); - if (this.comment.commentReactions && this.comment.commentReactions.length) { + if (this.comment.commentReactions && this.comment.commentReactions.length && this.comment.commentReactions.filter(reaction => !!reaction.count).length) { this.createReactionsContainer(this._commentDetailsContainer); } @@ -154,15 +154,23 @@ export class CommentNode extends Disposable { private createActionsToolbar() { const actions: IAction[] = []; - let reactionGroup = this.commentService.getReactionGroup(this.owner); - if (reactionGroup && reactionGroup.length) { - let commentThread = this.commentThread as modes.CommentThread2; - if (commentThread.commentThreadHandle !== undefined) { - let toggleReactionAction = this.createReactionPicker2(); - actions.push(toggleReactionAction); - } else { - let toggleReactionAction = this.createReactionPicker(); - actions.push(toggleReactionAction); + let hasReactionHandler = this.commentService.hasReactionHandler(this.owner); + + if (hasReactionHandler) { + let toggleReactionAction = this.createReactionPicker2(this.comment.commentReactions || []); + actions.push(toggleReactionAction); + } else { + let reactionGroup = this.commentService.getReactionGroup(this.owner); + if (reactionGroup && reactionGroup.length) { + let commentThread = this.commentThread as modes.CommentThread2; + if (commentThread.commentThreadHandle !== undefined) { + let reactionGroup = this.commentService.getReactionGroup(this.owner); + let toggleReactionAction = this.createReactionPicker2(reactionGroup || []); + actions.push(toggleReactionAction); + } else { + let toggleReactionAction = this.createReactionPicker(); + actions.push(toggleReactionAction); + } } } @@ -241,7 +249,7 @@ export class CommentNode extends Disposable { } } - private createReactionPicker2(): ToggleReactionsAction { + private createReactionPicker2(reactionGroup: modes.CommentReaction[]): ToggleReactionsAction { let toggleReactionActionViewItem: DropdownMenuActionViewItem; let toggleReactionAction = this._register(new ToggleReactionsAction(() => { if (toggleReactionActionViewItem) { @@ -250,7 +258,6 @@ export class CommentNode extends Disposable { }, nls.localize('commentToggleReaction', "Toggle Reaction"))); let reactionMenuActions: Action[] = []; - let reactionGroup = this.commentService.getReactionGroup(this.owner); if (reactionGroup && reactionGroup.length) { reactionMenuActions = reactionGroup.map((reaction) => { return new Action(`reaction.command.${reaction.label}`, `${reaction.label}`, '', true, async () => { @@ -356,8 +363,9 @@ export class CommentNode extends Disposable { }); this._register(this._reactionsActionBar); - this.comment.commentReactions!.map(reaction => { - let action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && reaction.canEdit ? 'active' : '', reaction.canEdit, async () => { + let hasReactionHandler = this.commentService.hasReactionHandler(this.owner); + this.comment.commentReactions!.filter(reaction => !!reaction.count).map(reaction => { + let action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && (reaction.canEdit || hasReactionHandler) ? 'active' : '', (reaction.canEdit || hasReactionHandler), async () => { try { let commentThread = this.commentThread as modes.CommentThread2; if (commentThread.commentThreadHandle !== undefined) { @@ -390,15 +398,20 @@ export class CommentNode extends Disposable { } }); - let reactionGroup = this.commentService.getReactionGroup(this.owner); - if (reactionGroup && reactionGroup.length) { - let commentThread = this.commentThread as modes.CommentThread2; - if (commentThread.commentThreadHandle !== undefined) { - let toggleReactionAction = this.createReactionPicker2(); - this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); - } else { - let toggleReactionAction = this.createReactionPicker(); - this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); + if (hasReactionHandler) { + let toggleReactionAction = this.createReactionPicker2(this.comment.commentReactions || []); + this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); + } else { + let reactionGroup = this.commentService.getReactionGroup(this.owner); + if (reactionGroup && reactionGroup.length) { + let commentThread = this.commentThread as modes.CommentThread2; + if (commentThread.commentThreadHandle !== undefined) { + let toggleReactionAction = this.createReactionPicker2(reactionGroup || []); + this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); + } else { + let toggleReactionAction = this.createReactionPicker(); + this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); + } } } } diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 9b2286d4677..b42f4bba6b5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -69,6 +69,7 @@ export interface ICommentService { addReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; getReactionGroup(owner: string): CommentReaction[] | undefined; + hasReactionHandler(owner: string): boolean; toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; setActiveCommentThread(commentThread: CommentThread | null): void; } @@ -304,6 +305,16 @@ export class CommentService extends Disposable implements ICommentService { return undefined; } + hasReactionHandler(owner: string): boolean { + const commentProvider = this._commentControls.get(owner); + + if (commentProvider) { + return !!commentProvider.features.reactionHandler; + } + + return false; + } + getStartDraftLabel(owner: string): string | undefined { const commentProvider = this._commentProviders.get(owner);