mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
Package hover: Show when last published (#177634)
This commit is contained in:
parent
18703b5872
commit
7a4092f1b0
201
extensions/npm/src/features/date.ts
Normal file
201
extensions/npm/src/features/date.ts
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { l10n } from 'vscode';
|
||||||
|
|
||||||
|
|
||||||
|
const minute = 60;
|
||||||
|
const hour = minute * 60;
|
||||||
|
const day = hour * 24;
|
||||||
|
const week = day * 7;
|
||||||
|
const month = day * 30;
|
||||||
|
const year = day * 365;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a localized of the time between now and the specified date.
|
||||||
|
* @param date The date to generate the difference from.
|
||||||
|
* @param appendAgoLabel Whether to append the " ago" to the end.
|
||||||
|
* @param useFullTimeWords Whether to use full words (eg. seconds) instead of
|
||||||
|
* shortened (eg. secs).
|
||||||
|
* @param disallowNow Whether to disallow the string "now" when the difference
|
||||||
|
* is less than 30 seconds.
|
||||||
|
*/
|
||||||
|
export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTimeWords?: boolean, disallowNow?: boolean): string {
|
||||||
|
if (typeof date !== 'number') {
|
||||||
|
date = date.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
const seconds = Math.round((new Date().getTime() - date) / 1000);
|
||||||
|
if (seconds < -30) {
|
||||||
|
return l10n.t('in {0}', fromNow(new Date().getTime() + seconds * 1000, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!disallowNow && seconds < 30) {
|
||||||
|
return l10n.t('now');
|
||||||
|
}
|
||||||
|
|
||||||
|
let value: number;
|
||||||
|
if (seconds < minute) {
|
||||||
|
value = seconds;
|
||||||
|
|
||||||
|
if (appendAgoLabel) {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} second ago', value)
|
||||||
|
: l10n.t('{0} sec ago', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} seconds ago', value)
|
||||||
|
: l10n.t('{0} secs ago', value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} second', value)
|
||||||
|
: l10n.t('{0} sec', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} seconds', value)
|
||||||
|
: l10n.t('{0} secs', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds < hour) {
|
||||||
|
value = Math.floor(seconds / minute);
|
||||||
|
if (appendAgoLabel) {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} minute ago', value)
|
||||||
|
: l10n.t('{0} min ago', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} minutes ago', value)
|
||||||
|
: l10n.t('{0} mins ago', value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} minute', value)
|
||||||
|
: l10n.t('{0} min', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} minutes', value)
|
||||||
|
: l10n.t('{0} mins', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds < day) {
|
||||||
|
value = Math.floor(seconds / hour);
|
||||||
|
if (appendAgoLabel) {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} hour ago', value)
|
||||||
|
: l10n.t('{0} hr ago', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} hours ago', value)
|
||||||
|
: l10n.t('{0} hrs ago', value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} hour', value)
|
||||||
|
: l10n.t('{0} hr', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} hours', value)
|
||||||
|
: l10n.t('{0} hrs', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds < week) {
|
||||||
|
value = Math.floor(seconds / day);
|
||||||
|
if (appendAgoLabel) {
|
||||||
|
return value === 1
|
||||||
|
? l10n.t('{0} day ago', value)
|
||||||
|
: l10n.t('{0} days ago', value);
|
||||||
|
} else {
|
||||||
|
return value === 1
|
||||||
|
? l10n.t('{0} day', value)
|
||||||
|
: l10n.t('{0} days', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds < month) {
|
||||||
|
value = Math.floor(seconds / week);
|
||||||
|
if (appendAgoLabel) {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} week ago', value)
|
||||||
|
: l10n.t('{0} wk ago', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} weeks ago', value)
|
||||||
|
: l10n.t('{0} wks ago', value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} week', value)
|
||||||
|
: l10n.t('{0} wk', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} weeks', value)
|
||||||
|
: l10n.t('{0} wks', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds < year) {
|
||||||
|
value = Math.floor(seconds / month);
|
||||||
|
if (appendAgoLabel) {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} month ago', value)
|
||||||
|
: l10n.t('{0} mo ago', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} months ago', value)
|
||||||
|
: l10n.t('{0} mos ago', value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} month', value)
|
||||||
|
: l10n.t('{0} mo', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} months', value)
|
||||||
|
: l10n.t('{0} mos', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = Math.floor(seconds / year);
|
||||||
|
if (appendAgoLabel) {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} year ago', value)
|
||||||
|
: l10n.t('{0} yr ago', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} years ago', value)
|
||||||
|
: l10n.t('{0} yrs ago', value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value === 1) {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} year', value)
|
||||||
|
: l10n.t('{0} yr', value);
|
||||||
|
} else {
|
||||||
|
return useFullTimeWords
|
||||||
|
? l10n.t('{0} years', value)
|
||||||
|
: l10n.t('{0} yrs', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import { Location } from 'jsonc-parser';
|
||||||
|
|
||||||
import * as cp from 'child_process';
|
import * as cp from 'child_process';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
|
import { fromNow } from './date';
|
||||||
|
|
||||||
const LIMIT = 40;
|
const LIMIT = 40;
|
||||||
|
|
||||||
|
@ -215,14 +216,14 @@ export class PackageJSONContribution implements IJSONContribution {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDocumentation(description: string | undefined, version: string | undefined, homepage: string | undefined): MarkdownString {
|
private getDocumentation(description: string | undefined, version: string | undefined, time: string | undefined, homepage: string | undefined): MarkdownString {
|
||||||
const str = new MarkdownString();
|
const str = new MarkdownString();
|
||||||
if (description) {
|
if (description) {
|
||||||
str.appendText(description);
|
str.appendText(description);
|
||||||
}
|
}
|
||||||
if (version) {
|
if (version) {
|
||||||
str.appendText('\n\n');
|
str.appendText('\n\n');
|
||||||
str.appendText(l10n.t("Latest version: {0}", version));
|
str.appendText(time ? l10n.t("Latest version: {0} published {1}", version, fromNow(Date.parse(time), true, true)) : l10n.t("Latest version: {0}", version));
|
||||||
}
|
}
|
||||||
if (homepage) {
|
if (homepage) {
|
||||||
str.appendText('\n\n');
|
str.appendText('\n\n');
|
||||||
|
@ -241,7 +242,7 @@ export class PackageJSONContribution implements IJSONContribution {
|
||||||
|
|
||||||
return this.fetchPackageInfo(name, resource).then(info => {
|
return this.fetchPackageInfo(name, resource).then(info => {
|
||||||
if (info) {
|
if (info) {
|
||||||
item.documentation = this.getDocumentation(info.description, info.version, info.homepage);
|
item.documentation = this.getDocumentation(info.description, info.version, info.time, info.homepage);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -283,15 +284,17 @@ export class PackageJSONContribution implements IJSONContribution {
|
||||||
|
|
||||||
private npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise<ViewPackageInfo | undefined> {
|
private npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise<ViewPackageInfo | undefined> {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version'];
|
const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version', 'time'];
|
||||||
const cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined;
|
const cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined;
|
||||||
cp.execFile(npmCommandPath, args, { cwd }, (error, stdout) => {
|
cp.execFile(npmCommandPath, args, { cwd }, (error, stdout) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
try {
|
try {
|
||||||
const content = JSON.parse(stdout);
|
const content = JSON.parse(stdout);
|
||||||
|
const version = content['dist-tags.latest'] || content['version'];
|
||||||
resolve({
|
resolve({
|
||||||
description: content['description'],
|
description: content['description'],
|
||||||
version: content['dist-tags.latest'] || content['version'],
|
version,
|
||||||
|
time: content.time?.[version],
|
||||||
homepage: content['homepage']
|
homepage: content['homepage']
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -316,6 +319,7 @@ export class PackageJSONContribution implements IJSONContribution {
|
||||||
return {
|
return {
|
||||||
description: obj.description || '',
|
description: obj.description || '',
|
||||||
version,
|
version,
|
||||||
|
time: obj.time?.[version],
|
||||||
homepage: obj.homepage || ''
|
homepage: obj.homepage || ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -334,7 +338,7 @@ export class PackageJSONContribution implements IJSONContribution {
|
||||||
if (typeof pack === 'string') {
|
if (typeof pack === 'string') {
|
||||||
return this.fetchPackageInfo(pack, resource).then(info => {
|
return this.fetchPackageInfo(pack, resource).then(info => {
|
||||||
if (info) {
|
if (info) {
|
||||||
return [this.getDocumentation(info.description, info.version, info.homepage)];
|
return [this.getDocumentation(info.description, info.version, info.time, info.homepage)];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
@ -363,7 +367,7 @@ export class PackageJSONContribution implements IJSONContribution {
|
||||||
proposal.kind = CompletionItemKind.Property;
|
proposal.kind = CompletionItemKind.Property;
|
||||||
proposal.insertText = insertText;
|
proposal.insertText = insertText;
|
||||||
proposal.filterText = JSON.stringify(name);
|
proposal.filterText = JSON.stringify(name);
|
||||||
proposal.documentation = this.getDocumentation(pack.description, pack.version, pack?.links?.homepage);
|
proposal.documentation = this.getDocumentation(pack.description, pack.version, undefined, pack?.links?.homepage);
|
||||||
collector.add(proposal);
|
collector.add(proposal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -379,5 +383,6 @@ interface SearchPackageInfo {
|
||||||
interface ViewPackageInfo {
|
interface ViewPackageInfo {
|
||||||
description: string;
|
description: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
time?: string;
|
||||||
homepage?: string;
|
homepage?: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue