Skip to content

Commit

Permalink
Comments view refresh (#140979)
Browse files Browse the repository at this point in the history
Fixes #142081
  • Loading branch information
alexr00 authored Feb 15, 2022
1 parent 2d23306 commit 2467540
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 44 deletions.
136 changes: 99 additions & 37 deletions src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchAsyncDataTree, IListService, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IColorMapping } from 'vs/platform/theme/common/styler';
import { TimestampWidget } from 'vs/workbench/contrib/comments/browser/timestamp';
import { Codicon } from 'vs/base/common/codicons';
import { IMarkdownString } from 'vs/base/common/htmlContent';

export const COMMENTS_VIEW_ID = 'workbench.panel.comments';
export const COMMENTS_VIEW_TITLE = 'Comments';

export class CommentsAsyncDataSource implements IAsyncDataSource<any, any> {
hasChildren(element: any): boolean {
return element instanceof CommentsModel || element instanceof ResourceWithCommentThreads || (element instanceof CommentNode && !!element.replies.length);
return (element instanceof CommentsModel || element instanceof ResourceWithCommentThreads) && !(element instanceof CommentNode);
}

getChildren(element: any): any[] | Promise<any[]> {
Expand All @@ -37,9 +40,6 @@ export class CommentsAsyncDataSource implements IAsyncDataSource<any, any> {
if (element instanceof ResourceWithCommentThreads) {
return Promise.resolve(element.commentThreads);
}
if (element instanceof CommentNode) {
return Promise.resolve(element.replies);
}
return Promise.resolve([]);
}
}
Expand All @@ -49,9 +49,21 @@ interface IResourceTemplateData {
}

interface ICommentThreadTemplateData {
icon: HTMLImageElement;
userName: HTMLSpanElement;
commentText: HTMLElement;
threadMetadata: {
icon?: HTMLElement;
userNames: HTMLSpanElement;
timestamp: TimestampWidget;
separator: HTMLElement;
commentPreview: HTMLSpanElement;
};
repliesMetadata: {
container: HTMLElement;
icon: HTMLElement;
count: HTMLSpanElement;
lastReplyDetail: HTMLSpanElement;
separator: HTMLElement;
timestamp: TimestampWidget;
};
disposables: IDisposable[];
}

Expand All @@ -61,6 +73,9 @@ export class CommentsModelVirualDelegate implements IListVirtualDelegate<any> {


getHeight(element: any): number {
if ((element instanceof CommentNode) && element.hasReply()) {
return 44;
}
return 22;
}

Expand Down Expand Up @@ -105,50 +120,97 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
templateId: string = 'comment-node';

constructor(
@IOpenerService private readonly openerService: IOpenerService
@IOpenerService private readonly openerService: IOpenerService,
@IConfigurationService private readonly configurationService: IConfigurationService
) { }

renderTemplate(container: HTMLElement) {
const data = <ICommentThreadTemplateData>Object.create(null);
const labelContainer = dom.append(container, dom.$('.comment-container'));
data.userName = dom.append(labelContainer, dom.$('.user'));
data.commentText = dom.append(labelContainer, dom.$('.text'));
data.disposables = [];

const threadContainer = dom.append(container, dom.$('.comment-thread-container'));
const metadataContainer = dom.append(threadContainer, dom.$('.comment-metadata-container'));
data.threadMetadata = {
icon: dom.append(metadataContainer, dom.$('.icon')),
userNames: dom.append(metadataContainer, dom.$('.user')),
timestamp: new TimestampWidget(this.configurationService, dom.append(metadataContainer, dom.$('.timestamp-container'))),
separator: dom.append(metadataContainer, dom.$('.separator')),
commentPreview: dom.append(metadataContainer, dom.$('.text'))
};
data.threadMetadata.separator.innerText = '\u00b7';

const snippetContainer = dom.append(threadContainer, dom.$('.comment-snippet-container'));
data.repliesMetadata = {
container: snippetContainer,
icon: dom.append(snippetContainer, dom.$('.icon')),
count: dom.append(snippetContainer, dom.$('.count')),
lastReplyDetail: dom.append(snippetContainer, dom.$('.reply-detail')),
separator: dom.append(snippetContainer, dom.$('.separator')),
timestamp: new TimestampWidget(this.configurationService, dom.append(snippetContainer, dom.$('.timestamp-container'))),
};
data.repliesMetadata.separator.innerText = '\u00b7';
data.repliesMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(Codicon.indent));
data.disposables = [data.threadMetadata.timestamp, data.repliesMetadata.timestamp];

return data;
}

private getCountString(commentCount: number): string {
if (commentCount > 1) {
return nls.localize('commentsCount', "{0} comments", commentCount);
} else {
return nls.localize('commentCount', "1 comment");
}
}

private getRenderedComment(commentBody: IMarkdownString, disposables: DisposableStore) {
const renderedComment = renderMarkdown(commentBody, {
inline: true,
actionHandler: {
callback: (content) => {
this.openerService.open(content, { allowCommands: commentBody.isTrusted }).catch(onUnexpectedError);
},
disposables: disposables
}
});
const images = renderedComment.element.getElementsByTagName('img');
for (let i = 0; i < images.length; i++) {
const image = images[i];
const textDescription = dom.$('');
textDescription.textContent = image.alt ? nls.localize('imageWithLabel', "Image: {0}", image.alt) : nls.localize('image', "Image");
image.parentNode!.replaceChild(textDescription, image);
}
return renderedComment;
}

renderElement(node: ITreeNode<CommentNode>, index: number, templateData: ICommentThreadTemplateData, height: number | undefined): void {
templateData.userName.textContent = node.element.comment.userName;
templateData.commentText.innerText = '';
if (typeof node.element.comment.body === 'string') {
templateData.commentText.innerText = node.element.comment.body;
const commentCount = node.element.replies.length + 1;
templateData.threadMetadata.icon?.classList.add(...ThemeIcon.asClassNameArray((commentCount === 1) ? Codicon.comment : Codicon.commentDiscussion));
templateData.threadMetadata.userNames.textContent = node.element.comment.userName;
templateData.threadMetadata.timestamp.setTimestamp(node.element.comment.timestamp ? new Date(node.element.comment.timestamp) : undefined);
const originalComment = node.element;

templateData.threadMetadata.commentPreview.innerText = '';
if (typeof originalComment.comment.body === 'string') {
templateData.threadMetadata.commentPreview.innerText = originalComment.comment.body;
} else {
const commentBody = node.element.comment.body;
const disposables = new DisposableStore();
templateData.disposables.push(disposables);
const renderedComment = renderMarkdown(commentBody, {
inline: true,
actionHandler: {
callback: (content) => {
this.openerService.open(content, { allowCommands: commentBody.isTrusted }).catch(onUnexpectedError);
},
disposables: disposables
}
});
const renderedComment = this.getRenderedComment(originalComment.comment.body, disposables);
templateData.disposables.push(renderedComment);
templateData.threadMetadata.commentPreview.appendChild(renderedComment.element);
templateData.threadMetadata.commentPreview.title = renderedComment.element.textContent ?? '';
}

const images = renderedComment.element.getElementsByTagName('img');
for (let i = 0; i < images.length; i++) {
const image = images[i];
const textDescription = dom.$('');
textDescription.textContent = image.alt ? nls.localize('imageWithLabel', "Image: {0}", image.alt) : nls.localize('image', "Image");
image.parentNode!.replaceChild(textDescription, image);
}

templateData.commentText.appendChild(renderedComment.element);
templateData.commentText.title = renderedComment.element.textContent ?? '';
if (!node.element.hasReply()) {
templateData.repliesMetadata.container.style.display = 'none';
return;
}

templateData.repliesMetadata.container.style.display = '';
templateData.repliesMetadata.count.textContent = this.getCountString(commentCount);
templateData.repliesMetadata.lastReplyDetail.textContent = nls.localize('lastReplyFrom', "Last reply from {0}", node.element.replies[node.element.replies.length - 1].comment.userName);
templateData.repliesMetadata.timestamp.setTimestamp(originalComment.comment.timestamp ? new Date(originalComment.comment.timestamp) : undefined);

}

disposeTemplate(templateData: ICommentThreadTemplateData): void {
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/comments/browser/commentsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class CommentsPanel extends ViewPane {

let domContainer = dom.append(container, dom.$('.comments-panel-container'));
this.treeContainer = dom.append(domContainer, dom.$('.tree-container'));
this.treeContainer.classList.add('file-icon-themable-tree', 'show-file-icons');
this.commentsModel = new CommentsModel();

this.createTree();
Expand Down
43 changes: 37 additions & 6 deletions src/vs/workbench/contrib/comments/browser/media/panel.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,57 @@
visibility: hidden;
}

.comments-panel .comments-panel-container .tree-container .comment-thread-container {
display: block;
}

.comments-panel .comments-panel-container .tree-container .resource-container,
.comments-panel .comments-panel-container .tree-container .comment-container {
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-metadata-container,
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-snippet-container {
display: flex;
}

.comments-panel .count,
.comments-panel .user {
padding-right: 5px;
opacity: 0.5;
}

.comments-panel .comments-panel-container .tree-container .comment-container .text {
.comments-panel .comments-panel-container .tree-container .comment-thread-container .icon {
padding-top: 4px;
padding-right: 5px;
}

.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-metadata-container .count,
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-snippet-container .text {
display: flex;
flex: 1;
min-width: 0;
}

.comments-panel .comments-panel-container .tree-container .comment-container .text * {
.comments-panel .comments-panel-container .tree-container .comment-thread-container .reply-detail,
.comments-panel .comments-panel-container .tree-container .comment-thread-container .timestamp {
display:flex;
font-size: 0.9em;
padding-right: 5px;
opacity: 0.8;
}

.comments-panel .comments-panel-container .tree-container .comment-thread-container .text * {
margin: 0;
text-overflow: ellipsis;
max-width: 500px;
overflow: hidden;
}

.comments-panel .comments-panel-container .tree-container .comment-container .text code {
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-snippet-container .text code {
font-family: var(--monaco-monospace-font);
}

.comments-panel .comments-panel-container .tree-container .comment-thread-container .separator {
padding-right: 5px;
opacity: 0.8;
}

.comments-panel .comments-panel-container .message-box-container {
line-height: 22px;
padding-left: 20px;
Expand All @@ -55,7 +81,12 @@
margin-left: 10px;
}

.comments-panel .comments-panel-container .tree-container .comment-container {
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-metadata-container,
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-snippet-container {
line-height: 22px;
margin-right: 5px;
}

.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-snippet-container {
padding-left: 16px;
}
4 changes: 3 additions & 1 deletion src/vs/workbench/contrib/comments/browser/timestamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class TimestampWidget extends Disposable {
constructor(private configurationService: IConfigurationService, container: HTMLElement, timeStamp?: Date) {
super();
this._date = dom.append(container, dom.$('span.timestamp'));
this._date.style.display = 'none';
this._useRelativeTime = this.useRelativeTimeSetting;
this.setTimestamp(timeStamp);
}
Expand All @@ -37,9 +38,10 @@ export class TimestampWidget extends Disposable {
private updateDate(timestamp?: Date) {
if (!timestamp) {
this._date.textContent = '';
this._date.style.display = 'none';
} else if ((timestamp !== this._timestamp)
|| (this.useRelativeTimeSetting !== this._useRelativeTime)) {

this._date.style.display = '';
let textContent: string;
let tooltip: string | undefined;
if (this.useRelativeTimeSetting) {
Expand Down

0 comments on commit 2467540

Please sign in to comment.