mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-29 18:09:01 +01:00
+109
-37
@@ -12,7 +12,7 @@ const week = day * 7;
|
||||
const month = day * 30;
|
||||
const year = day * 365;
|
||||
|
||||
export function fromNow(date: number | Date, appendAgoLabel?: boolean): string {
|
||||
export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTimeWords?: boolean): string {
|
||||
if (typeof date !== 'number') {
|
||||
date = date.getTime();
|
||||
}
|
||||
@@ -31,39 +31,75 @@ export function fromNow(date: number | Date, appendAgoLabel?: boolean): string {
|
||||
value = seconds;
|
||||
|
||||
if (appendAgoLabel) {
|
||||
return value === 1
|
||||
? localize('date.fromNow.seconds.singular.ago', '{0} sec ago', value)
|
||||
: localize('date.fromNow.seconds.plural.ago', '{0} secs ago', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.seconds.singular.ago.fullWord', '{0} second ago', value)
|
||||
: localize('date.fromNow.seconds.singular.ago', '{0} sec ago', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.seconds.plural.ago.fullWord', '{0} seconds ago', value)
|
||||
: localize('date.fromNow.seconds.plural.ago', '{0} secs ago', value);
|
||||
}
|
||||
} else {
|
||||
return value === 1
|
||||
? localize('date.fromNow.seconds.singular', '{0} sec', value)
|
||||
: localize('date.fromNow.seconds.plural', '{0} secs', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.seconds.singular.fullWord', '{0} second', value)
|
||||
: localize('date.fromNow.seconds.singular', '{0} sec', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.seconds.plural.fullWord', '{0} seconds', value)
|
||||
: localize('date.fromNow.seconds.plural', '{0} secs', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (seconds < hour) {
|
||||
value = Math.floor(seconds / minute);
|
||||
if (appendAgoLabel) {
|
||||
return value === 1
|
||||
? localize('date.fromNow.minutes.singular.ago', '{0} min ago', value)
|
||||
: localize('date.fromNow.minutes.plural.ago', '{0} mins ago', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.minutes.singular.ago.fullWord', '{0} minute ago', value)
|
||||
: localize('date.fromNow.minutes.singular.ago', '{0} min ago', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.minutes.plural.ago.fullWord', '{0} minutes ago', value)
|
||||
: localize('date.fromNow.minutes.plural.ago', '{0} mins ago', value);
|
||||
}
|
||||
} else {
|
||||
return value === 1
|
||||
? localize('date.fromNow.minutes.singular', '{0} min', value)
|
||||
: localize('date.fromNow.minutes.plural', '{0} mins', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.minutes.singular.fullWord', '{0} minute', value)
|
||||
: localize('date.fromNow.minutes.singular', '{0} min', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.minutes.plural.fullWord', '{0} minutes', value)
|
||||
: localize('date.fromNow.minutes.plural', '{0} mins', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (seconds < day) {
|
||||
value = Math.floor(seconds / hour);
|
||||
if (appendAgoLabel) {
|
||||
return value === 1
|
||||
? localize('date.fromNow.hours.singular.ago', '{0} hr ago', value)
|
||||
: localize('date.fromNow.hours.plural.ago', '{0} hrs ago', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.hours.singular.ago.fullWord', '{0} hour ago', value)
|
||||
: localize('date.fromNow.hours.singular.ago', '{0} hr ago', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.hours.plural.ago.fullWord', '{0} hours ago', value)
|
||||
: localize('date.fromNow.hours.plural.ago', '{0} hrs ago', value);
|
||||
}
|
||||
} else {
|
||||
return value === 1
|
||||
? localize('date.fromNow.hours.singular', '{0} hr', value)
|
||||
: localize('date.fromNow.hours.plural', '{0} hrs', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.hours.singular.fullWord', '{0} hour', value)
|
||||
: localize('date.fromNow.hours.singular', '{0} hr', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.hours.plural.fullWord', '{0} hours', value)
|
||||
: localize('date.fromNow.hours.plural', '{0} hrs', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,38 +119,74 @@ export function fromNow(date: number | Date, appendAgoLabel?: boolean): string {
|
||||
if (seconds < month) {
|
||||
value = Math.floor(seconds / week);
|
||||
if (appendAgoLabel) {
|
||||
return value === 1
|
||||
? localize('date.fromNow.weeks.singular.ago', '{0} wk ago', value)
|
||||
: localize('date.fromNow.weeks.plural.ago', '{0} wks ago', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.weeks.singular.ago.fullWord', '{0} week ago', value)
|
||||
: localize('date.fromNow.weeks.singular.ago', '{0} wk ago', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.weeks.plural.ago.fullWord', '{0} weeks ago', value)
|
||||
: localize('date.fromNow.weeks.plural.ago', '{0} wks ago', value);
|
||||
}
|
||||
} else {
|
||||
return value === 1
|
||||
? localize('date.fromNow.weeks.singular', '{0} wk', value)
|
||||
: localize('date.fromNow.weeks.plural', '{0} wks', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.weeks.singular.fullWord', '{0} week', value)
|
||||
: localize('date.fromNow.weeks.singular', '{0} wk', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.weeks.plural.fullWord', '{0} weeks', value)
|
||||
: localize('date.fromNow.weeks.plural', '{0} wks', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (seconds < year) {
|
||||
value = Math.floor(seconds / month);
|
||||
if (appendAgoLabel) {
|
||||
return value === 1
|
||||
? localize('date.fromNow.months.singular.ago', '{0} mo ago', value)
|
||||
: localize('date.fromNow.months.plural.ago', '{0} mos ago', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.months.singular.ago.fullWord', '{0} month ago', value)
|
||||
: localize('date.fromNow.months.singular.ago', '{0} mo ago', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.months.plural.ago.fullWord', '{0} months ago', value)
|
||||
: localize('date.fromNow.months.plural.ago', '{0} mos ago', value);
|
||||
}
|
||||
} else {
|
||||
return value === 1
|
||||
? localize('date.fromNow.months.singular', '{0} mo', value)
|
||||
: localize('date.fromNow.months.plural', '{0} mos', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.months.singular.fullWord', '{0} month', value)
|
||||
: localize('date.fromNow.months.singular', '{0} mo', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.months.plural.fullWord', '{0} months', value)
|
||||
: localize('date.fromNow.months.plural', '{0} mos', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = Math.floor(seconds / year);
|
||||
if (appendAgoLabel) {
|
||||
return value === 1
|
||||
? localize('date.fromNow.years.singular.ago', '{0} yr ago', value)
|
||||
: localize('date.fromNow.years.plural.ago', '{0} yrs ago', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.years.singular.ago.fullWord', '{0} year ago', value)
|
||||
: localize('date.fromNow.years.singular.ago', '{0} yr ago', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.years.plural.ago.fullWord', '{0} years ago', value)
|
||||
: localize('date.fromNow.years.plural.ago', '{0} yrs ago', value);
|
||||
}
|
||||
} else {
|
||||
return value === 1
|
||||
? localize('date.fromNow.years.singular', '{0} yr', value)
|
||||
: localize('date.fromNow.years.plural', '{0} yrs', value);
|
||||
if (value === 1) {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.years.singular.fullWord', '{0} year', value)
|
||||
: localize('date.fromNow.years.singular', '{0} yr', value);
|
||||
} else {
|
||||
return useFullTimeWords
|
||||
? localize('date.fromNow.years.plural.fullWord', '{0} years', value)
|
||||
: localize('date.fromNow.years.plural', '{0} yrs', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ export const enum MarshalledId {
|
||||
TimelineActionContext,
|
||||
NotebookCellActionContext,
|
||||
TestItemContext,
|
||||
Date,
|
||||
}
|
||||
|
||||
export interface MarshalledObject {
|
||||
@@ -68,6 +69,7 @@ export function revive<T = any>(obj: any, depth = 0): Revived<T> {
|
||||
switch ((<MarshalledObject>obj).$mid) {
|
||||
case MarshalledId.Uri: return <any>URI.revive(obj);
|
||||
case MarshalledId.Regexp: return <any>new RegExp(obj.source, obj.flags);
|
||||
case MarshalledId.Date: return <any>new Date(obj.source);
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -1716,6 +1716,7 @@ export interface Comment {
|
||||
readonly commentReactions?: CommentReaction[];
|
||||
readonly label?: string;
|
||||
readonly mode?: CommentMode;
|
||||
readonly detail?: Date | string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
|
||||
import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView';
|
||||
import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from '../common/extHost.protocol';
|
||||
import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentThreadChanges, CommentChanges } from '../common/extHost.protocol';
|
||||
import { COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer';
|
||||
import { ViewContainer, IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
@@ -23,7 +23,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { MarshalledId } from 'vs/base/common/marshalling';
|
||||
import { MarshalledId, revive } from 'vs/base/common/marshalling';
|
||||
|
||||
|
||||
export class MainThreadCommentThread implements modes.CommentThread {
|
||||
@@ -139,11 +139,27 @@ export class MainThreadCommentThread implements modes.CommentThread {
|
||||
if (modified('range')) { this._range = changes.range!; }
|
||||
if (modified('label')) { this._label = changes.label; }
|
||||
if (modified('contextValue')) { this._contextValue = changes.contextValue === null ? undefined : changes.contextValue; }
|
||||
if (modified('comments')) { this._comments = changes.comments; }
|
||||
if (modified('comments')) { this._comments = this.commentsFromCommentChanges(changes.comments); }
|
||||
if (modified('collapseState')) { this._collapsibleState = changes.collapseState; }
|
||||
if (modified('canReply')) { this.canReply = changes.canReply!; }
|
||||
}
|
||||
|
||||
private commentsFromCommentChanges(comments?: CommentChanges[]): modes.Comment[] | undefined {
|
||||
return comments?.map(comment => {
|
||||
return {
|
||||
body: comment.body,
|
||||
uniqueIdInThread: comment.uniqueIdInThread,
|
||||
userName: comment.userName,
|
||||
commentReactions: comment.commentReactions,
|
||||
contextValue: comment.contextValue,
|
||||
detail: comment.detail ? <Date | string>revive<Date | string>(comment.detail) : undefined,
|
||||
label: comment.label,
|
||||
mode: comment.mode,
|
||||
userIconPath: comment.userIconPath
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._isDisposed = true;
|
||||
this._onDidChangeCollasibleState.dispose();
|
||||
|
||||
@@ -10,7 +10,7 @@ import { SerializedError } from 'vs/base/common/errors';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { MarshalledId, revive } from 'vs/base/common/marshalling';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { Dto } from 'vs/base/common/types';
|
||||
@@ -160,11 +160,25 @@ export interface CommentProviderFeatures {
|
||||
options?: modes.CommentOptions;
|
||||
}
|
||||
|
||||
export interface CommentChanges {
|
||||
readonly uniqueIdInThread: number;
|
||||
readonly body: IMarkdownString;
|
||||
readonly userName: string;
|
||||
readonly userIconPath?: string;
|
||||
readonly contextValue?: string;
|
||||
readonly commentReactions?: modes.CommentReaction[];
|
||||
readonly label?: string;
|
||||
readonly mode?: modes.CommentMode;
|
||||
readonly detail?: {
|
||||
$mid: MarshalledId.Date
|
||||
} | string;
|
||||
}
|
||||
|
||||
export type CommentThreadChanges = Partial<{
|
||||
range: IRange,
|
||||
label: string,
|
||||
contextValue: string | null,
|
||||
comments: modes.Comment[],
|
||||
comments: CommentChanges[],
|
||||
collapseState: modes.CommentThreadCollapsibleState;
|
||||
canReply: boolean;
|
||||
}>;
|
||||
|
||||
@@ -16,8 +16,9 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import * as types from 'vs/workbench/api/common/extHostTypes';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtHostCommentsShape, IMainContext, MainContext, CommentThreadChanges } from './extHost.protocol';
|
||||
import { ExtHostCommentsShape, IMainContext, MainContext, CommentThreadChanges, CommentChanges } from './extHost.protocol';
|
||||
import { ExtHostCommands } from './extHostCommands';
|
||||
|
||||
type ProviderHandle = number;
|
||||
@@ -346,7 +347,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
||||
private _uri: vscode.Uri,
|
||||
private _range: vscode.Range,
|
||||
private _comments: vscode.Comment[],
|
||||
extensionId: ExtensionIdentifier
|
||||
public readonly extensionDescription: IExtensionDescription
|
||||
) {
|
||||
this._acceptInputDisposables.value = new DisposableStore();
|
||||
|
||||
@@ -360,7 +361,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
||||
this._id,
|
||||
this._uri,
|
||||
extHostTypeConverter.Range.from(this._range),
|
||||
extensionId
|
||||
extensionDescription.identifier
|
||||
);
|
||||
|
||||
this._localDisposables = [];
|
||||
@@ -433,7 +434,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
||||
}
|
||||
if (modified('comments')) {
|
||||
formattedModifications.comments =
|
||||
this._comments.map(cmt => convertToModeComment(this, cmt, this._commentsMap));
|
||||
this._comments.map(cmt => convertToDTOComment(this, cmt, this._commentsMap));
|
||||
}
|
||||
if (modified('collapsibleState')) {
|
||||
formattedModifications.collapseState = convertToCollapsibleState(this._collapseState);
|
||||
@@ -561,18 +562,18 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
||||
createCommentThread(resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): ExtHostCommentThread;
|
||||
createCommentThread(arg0: vscode.Uri | string, arg1: vscode.Uri | vscode.Range, arg2: vscode.Range | vscode.Comment[], arg3?: vscode.Comment[]): vscode.CommentThread {
|
||||
if (typeof arg0 === 'string') {
|
||||
const commentThread = new ExtHostCommentThread(this.id, this.handle, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[], this._extension.identifier);
|
||||
const commentThread = new ExtHostCommentThread(this.id, this.handle, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[], this._extension);
|
||||
this._threads.set(commentThread.handle, commentThread);
|
||||
return commentThread;
|
||||
} else {
|
||||
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[], this._extension.identifier);
|
||||
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[], this._extension);
|
||||
this._threads.set(commentThread.handle, commentThread);
|
||||
return commentThread;
|
||||
}
|
||||
}
|
||||
|
||||
$createCommentThreadTemplate(uriComponents: UriComponents, range: IRange): ExtHostCommentThread {
|
||||
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension.identifier);
|
||||
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension);
|
||||
commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded;
|
||||
this._threads.set(commentThread.handle, commentThread);
|
||||
return commentThread;
|
||||
@@ -608,7 +609,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
||||
}
|
||||
}
|
||||
|
||||
function convertToModeComment(thread: ExtHostCommentThread, vscodeComment: vscode.Comment, commentsMap: Map<vscode.Comment, number>): modes.Comment {
|
||||
function convertToDTOComment(thread: ExtHostCommentThread, vscodeComment: vscode.Comment, commentsMap: Map<vscode.Comment, number>): CommentChanges {
|
||||
let commentUniqueId = commentsMap.get(vscodeComment)!;
|
||||
if (!commentUniqueId) {
|
||||
commentUniqueId = ++thread.commentHandle;
|
||||
@@ -617,6 +618,20 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
||||
|
||||
const iconPath = vscodeComment.author && vscodeComment.author.iconPath ? vscodeComment.author.iconPath.toString() : undefined;
|
||||
|
||||
if (vscodeComment.detail) {
|
||||
checkProposedApiEnabled(thread.extensionDescription, 'commentTimestamp');
|
||||
}
|
||||
|
||||
let detail: { $mid: MarshalledId.Date, source: any } | string | undefined;
|
||||
if (vscodeComment.detail && (typeof vscodeComment.detail !== 'string')) {
|
||||
detail = {
|
||||
source: vscodeComment.detail,
|
||||
$mid: MarshalledId.Date
|
||||
};
|
||||
} else {
|
||||
detail = vscodeComment.detail;
|
||||
}
|
||||
|
||||
return {
|
||||
mode: vscodeComment.mode,
|
||||
contextValue: vscodeComment.contextValue,
|
||||
@@ -625,7 +640,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
||||
userName: vscodeComment.author.name,
|
||||
userIconPath: iconPath,
|
||||
label: vscodeComment.label,
|
||||
commentReactions: vscodeComment.reactions ? vscodeComment.reactions.map(reaction => convertToReaction(reaction)) : undefined
|
||||
commentReactions: vscodeComment.reactions ? vscodeComment.reactions.map(reaction => convertToReaction(reaction)) : undefined,
|
||||
detail: detail
|
||||
};
|
||||
}
|
||||
|
||||
@@ -661,3 +677,4 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
||||
|
||||
return new ExtHostCommentsImpl();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { MarshalledId } from 'vs/base/common/marshalling';
|
||||
import { TimestampWidget } from 'vs/workbench/contrib/comments/browser/timestamp';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class CommentNode extends Disposable {
|
||||
private _domNode: HTMLElement;
|
||||
@@ -53,6 +55,8 @@ export class CommentNode extends Disposable {
|
||||
private _commentEditorDisposables: IDisposable[] = [];
|
||||
private _commentEditorModel: ITextModel | null = null;
|
||||
private _isPendingLabel!: HTMLElement;
|
||||
private _detail: HTMLElement | undefined;
|
||||
private _timestamp: TimestampWidget | undefined;
|
||||
private _contextKeyService: IContextKeyService;
|
||||
private _commentContextValue: IContextKey<string>;
|
||||
|
||||
@@ -83,7 +87,8 @@ export class CommentNode extends Disposable {
|
||||
@ILanguageService private languageService: ILanguageService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -121,11 +126,38 @@ export class CommentNode extends Disposable {
|
||||
return this._onDidClick.event;
|
||||
}
|
||||
|
||||
private createDetail(container: HTMLElement) {
|
||||
this._detail = dom.append(container, dom.$('span.detail'));
|
||||
this.updateDetail(this.comment.detail);
|
||||
}
|
||||
|
||||
private updateDetail(detail?: Date | string) {
|
||||
if (!this._detail) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!detail) {
|
||||
this._timestamp?.dispose();
|
||||
this._detail.innerText = '';
|
||||
} else if (typeof detail === 'string') {
|
||||
this._timestamp?.dispose();
|
||||
this._detail.innerText = detail;
|
||||
} else {
|
||||
this._detail.innerText = '';
|
||||
if (!this._timestamp) {
|
||||
this._timestamp = new TimestampWidget(this.configurationService, this._detail, detail);
|
||||
this._register(this._timestamp);
|
||||
} else {
|
||||
this._timestamp.setTimestamp(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createHeader(commentDetailsContainer: HTMLElement): void {
|
||||
const header = dom.append(commentDetailsContainer, dom.$(`div.comment-title.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`));
|
||||
const author = dom.append(header, dom.$('strong.author'));
|
||||
author.innerText = this.comment.userName;
|
||||
|
||||
this.createDetail(header);
|
||||
this._isPendingLabel = dom.append(header, dom.$('span.isPending'));
|
||||
|
||||
if (this.comment.label) {
|
||||
@@ -516,6 +548,10 @@ export class CommentNode extends Disposable {
|
||||
} else {
|
||||
this._commentContextValue.reset();
|
||||
}
|
||||
|
||||
if (this.comment.detail) {
|
||||
this.updateDetail(this.comment.detail);
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
|
||||
@@ -25,6 +25,12 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
||||
default: 'openOnSessionStartWithComments',
|
||||
description: nls.localize('openComments', "Controls when the comments panel should open."),
|
||||
restricted: false
|
||||
},
|
||||
'comments.useRelativeTime': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: nls.localize('useRelativeTime', "Determines if relative time will be used in comment timestamps (ex. '1 day ago').")
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -102,6 +102,12 @@
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.monaco-editor .review-widget .body .review-comment .review-comment-contents .timestamp {
|
||||
line-height: 22px;
|
||||
margin: 0 5px 0 5px;
|
||||
padding: 0 2px 0 2px;
|
||||
}
|
||||
|
||||
.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-body {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
const USE_RELATIVE_TIME_CONFIGURATION = 'comments.useRelativeTime';
|
||||
|
||||
export class TimestampWidget extends Disposable {
|
||||
private _date: HTMLElement;
|
||||
private _timestamp: Date | undefined;
|
||||
private _useRelativeTime: boolean;
|
||||
|
||||
constructor(private configurationService: IConfigurationService, container: HTMLElement, timeStamp?: Date) {
|
||||
super();
|
||||
this._date = dom.append(container, dom.$('span.timestamp'));
|
||||
this._useRelativeTime = this.useRelativeTimeSetting;
|
||||
this.setTimestamp(timeStamp);
|
||||
}
|
||||
|
||||
private get useRelativeTimeSetting(): boolean {
|
||||
return this.configurationService.getValue<boolean>(USE_RELATIVE_TIME_CONFIGURATION);
|
||||
}
|
||||
|
||||
public async setTimestamp(timestamp: Date | undefined) {
|
||||
if ((timestamp !== this._timestamp) || (this.useRelativeTimeSetting !== this._useRelativeTime)) {
|
||||
this.updateDate(timestamp);
|
||||
}
|
||||
this._timestamp = timestamp;
|
||||
this._useRelativeTime = this.useRelativeTimeSetting;
|
||||
}
|
||||
|
||||
private updateDate(timestamp?: Date) {
|
||||
if (!timestamp) {
|
||||
this._date.textContent = '';
|
||||
} else if ((timestamp !== this._timestamp)
|
||||
|| (this.useRelativeTimeSetting !== this._useRelativeTime)) {
|
||||
|
||||
let textContent: string;
|
||||
let tooltip: string | undefined;
|
||||
if (this.useRelativeTimeSetting) {
|
||||
textContent = this.getRelative(timestamp);
|
||||
tooltip = this.getDateString(timestamp);
|
||||
} else {
|
||||
textContent = this.getDateString(timestamp);
|
||||
}
|
||||
|
||||
this._date.textContent = textContent;
|
||||
if (tooltip) {
|
||||
this._date.title = tooltip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getRelative(date: Date): string {
|
||||
return fromNow(date, true, true);
|
||||
}
|
||||
|
||||
private getDateString(date: Date): string {
|
||||
return date.toLocaleString();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
export const allApiProposals = Object.freeze({
|
||||
authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts',
|
||||
commentTimestamp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentTimestamp.d.ts',
|
||||
contribIconFonts: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIconFonts.d.ts',
|
||||
contribIcons: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIcons.d.ts',
|
||||
contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
export interface Comment {
|
||||
/**
|
||||
* An optional detail that will be displayed less prominently than the `author`.
|
||||
* If a date is provided, then the date will be formatted according to the user's
|
||||
* locale and settings.
|
||||
*/
|
||||
detail?: Date | string
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user