render private extension (#243503)

* render private extension

* fix all repsitory signed flag
This commit is contained in:
Sandeep Somavarapu
2025-03-14 00:00:08 +01:00
committed by GitHub
parent 78020d1b00
commit d09fd2b5d7
14 changed files with 275 additions and 152 deletions
@@ -48,8 +48,6 @@ const enum ExtensionGalleryResourceType {
PublisherViewUri = 'PublisherViewUriTemplate',
ExtensionDetailsViewUri = 'ExtensionDetailsViewUriTemplate',
ExtensionRatingViewUri = 'ExtensionRatingViewUriTemplate',
WebLanguagePackService = 'WebLanguagePackService',
ExtensionUriTemplate = 'ExtensionUriTemplate',
}
const enum Flag {
@@ -187,6 +185,7 @@ const PropertyType = {
SponsorLink: 'Microsoft.VisualStudio.Code.SponsorLink',
SupportLink: 'Microsoft.VisualStudio.Services.Links.Support',
ExecutesCode: 'Microsoft.VisualStudio.Code.ExecutesCode',
Private: 'PrivateMarketplace',
};
interface ICriterium {
@@ -402,6 +401,11 @@ function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean {
return values.length > 0 && values[0].value === 'true';
}
function isPrivateExtension(version: IRawGalleryExtensionVersion): boolean {
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.Private) : [];
return values.length > 0 && values[0].value === 'true';
}
function executesCode(version: IRawGalleryExtensionVersion): boolean | undefined {
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.ExecutesCode) : [];
return values.length > 0 ? values[0].value === 'true' : undefined;
@@ -557,6 +561,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
},
hasPreReleaseVersion: isPreReleaseVersion(latestVersion),
hasReleaseVersion: true,
private: isPrivateExtension(latestVersion),
preview: getIsPreview(galleryExtension.flags),
isSigned: !!assets.signature,
queryContext,
@@ -646,7 +651,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
sortBy,
filters
},
allRepositorySigned: !extensionGalleryManifest.capabilities.signing?.allRepositorySigned,
allRepositorySigned: !!extensionGalleryManifest.capabilities.signing?.allRepositorySigned,
};
}
@@ -1940,20 +1945,6 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
});
}
if (extensionsGallery.resourceUrlTemplate) {
resources.push({
id: extensionsGallery.resourceUrlTemplate,
type: ExtensionGalleryResourceType.ExtensionUriTemplate
});
}
if (extensionsGallery.nlsBaseUrl) {
resources.push({
id: extensionsGallery.nlsBaseUrl,
type: ExtensionGalleryResourceType.WebLanguagePackService
});
}
const filtering = [
{
name: FilterType.Tag,
@@ -229,6 +229,7 @@ export interface IGalleryExtension {
releaseDate: number;
lastUpdated: number;
preview: boolean;
private: boolean;
hasPreReleaseVersion: boolean;
hasReleaseVersion: boolean;
isSigned: boolean;
@@ -247,6 +248,7 @@ export type InstallSource = 'gallery' | 'vsix' | 'resource';
export interface IGalleryMetadata {
id: string;
publisherId: string;
private: boolean;
publisherDisplayName: string;
isPreReleaseVersion: boolean;
targetPlatform?: TargetPlatform;
@@ -274,6 +276,7 @@ export interface ILocalExtension extends IExtension {
installedTimestamp?: number;
isPreReleaseVersion: boolean;
hasPreReleaseVersion: boolean;
private: boolean;
preRelease: boolean;
updated: boolean;
pinned: boolean;
@@ -869,6 +869,7 @@ export class ExtensionsScanner extends Disposable {
installedTimestamp: extension.metadata?.installedTimestamp,
updated: !!extension.metadata?.updated,
pinned: !!extension.metadata?.pinned,
private: !!extension.metadata?.private,
isWorkspaceScoped: false,
source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix'),
size: extension.metadata?.size ?? 0,
@@ -1041,6 +1042,7 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask<ILocalExtensio
metadata.publisherDisplayName = this.source.publisherDisplayName;
metadata.targetPlatform = this.source.properties.targetPlatform;
metadata.updated = !!existingExtension;
metadata.private = this.source.private;
metadata.isPreReleaseVersion = this.source.properties.isPreReleaseVersion;
metadata.hasPreReleaseVersion = existingExtension?.hasPreReleaseVersion || this.source.properties.isPreReleaseVersion;
metadata.preRelease = isBoolean(this.options.preRelease)
@@ -42,7 +42,6 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet
import { defaultCheckboxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
import { buttonForeground, buttonHoverBackground, editorBackground, textLinkActiveForeground, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js';
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
import { EditorPane } from '../../../browser/parts/editor/editorPane.js';
import { IEditorOpenContext } from '../../../common/editor.js';
import { ExtensionFeaturesTab } from './extensionFeaturesTab.js';
@@ -73,18 +72,15 @@ import {
} from './extensionsActions.js';
import { Delegate } from './extensionsList.js';
import { ExtensionData, ExtensionsGridView, ExtensionsTree, getExtensions } from './extensionsViewer.js';
import { ExtensionRecommendationWidget, ExtensionStatusWidget, ExtensionWidget, InstallCountWidget, RatingsWidget, RemoteBadgeWidget, SponsorWidget, VerifiedPublisherWidget, onClick } from './extensionsWidgets.js';
import { ExtensionRecommendationWidget, ExtensionStatusWidget, ExtensionWidget, InstallCountWidget, RatingsWidget, RemoteBadgeWidget, SponsorWidget, PublisherWidget, onClick, ExtensionKindIndicatorWidget } from './extensionsWidgets.js';
import { ExtensionContainers, ExtensionEditorTab, ExtensionState, IExtension, IExtensionContainer, IExtensionsWorkbenchService } from '../common/extensions.js';
import { ExtensionsInput, IExtensionEditorOptions } from '../common/extensionsInput.js';
import { IExplorerService } from '../../files/browser/files.js';
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from '../../markdown/browser/markdownDocumentRenderer.js';
import { IWebview, IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from '../../webview/browser/webview.js';
import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js';
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
import { VIEW_ID as EXPLORER_VIEW_ID } from '../../files/common/files.js';
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { ByteSize, IFileService } from '../../../../platform/files/common/files.js';
@@ -161,11 +157,6 @@ interface IExtensionEditorTemplate {
name: HTMLElement;
preview: HTMLElement;
builtin: HTMLElement;
publisher: HTMLElement;
publisherDisplayName: HTMLElement;
resource: HTMLElement;
installCount: HTMLElement;
rating: HTMLElement;
description: HTMLElement;
actionsAndStatusContainer: HTMLElement;
extensionActionBar: ActionBar;
@@ -257,10 +248,6 @@ export class ExtensionEditor extends EditorPane {
@ILanguageService private readonly languageService: ILanguageService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IExplorerService private readonly explorerService: IExplorerService,
@IViewsService private readonly viewsService: IViewsService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IHoverService private readonly hoverService: IHoverService,
) {
super(ExtensionEditor.ID, group, telemetryService, themeService, storageService);
@@ -302,29 +289,33 @@ export class ExtensionEditor extends EditorPane {
builtin.textContent = localize('builtin', "Built-in");
const subtitle = append(details, $('.subtitle'));
const publisher = append(append(subtitle, $('.subtitle-entry')), $('.publisher.clickable', { tabIndex: 0 }));
publisher.setAttribute('role', 'button');
const publisherDisplayName = append(publisher, $('.publisher-name'));
const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $('.verified-publisher')), false);
const subTitleEntryContainers: HTMLElement[] = [];
const resource = append(append(subtitle, $('.subtitle-entry.resource')), $('', { tabIndex: 0 }));
resource.setAttribute('role', 'button');
const publisherContainer = append(subtitle, $('.subtitle-entry'));
subTitleEntryContainers.push(publisherContainer);
const publisherWidget = this.instantiationService.createInstance(PublisherWidget, publisherContainer, false);
const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { tabIndex: 0 }));
this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), installCount, localize('install count', "Install count")));
const installCountWidget = this.instantiationService.createInstance(InstallCountWidget, installCount, false);
const extensionKindContainer = append(subtitle, $('.subtitle-entry'));
subTitleEntryContainers.push(extensionKindContainer);
const extensionKindWidget = this.instantiationService.createInstance(ExtensionKindIndicatorWidget, extensionKindContainer, false);
const rating = append(append(subtitle, $('.subtitle-entry')), $('span.rating.clickable', { tabIndex: 0 }));
this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), rating, localize('rating', "Rating")));
rating.setAttribute('role', 'link'); // #132645
const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, rating, false);
const installCountContainer = append(subtitle, $('.subtitle-entry'));
subTitleEntryContainers.push(installCountContainer);
const installCountWidget = this.instantiationService.createInstance(InstallCountWidget, installCountContainer, false);
const sponsorWidget = this.instantiationService.createInstance(SponsorWidget, append(subtitle, $('.subtitle-entry')));
const ratingsContainer = append(subtitle, $('.subtitle-entry'));
subTitleEntryContainers.push(ratingsContainer);
const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, ratingsContainer, false);
const sponsorContainer = append(subtitle, $('.subtitle-entry'));
subTitleEntryContainers.push(sponsorContainer);
const sponsorWidget = this.instantiationService.createInstance(SponsorWidget, sponsorContainer);
const widgets: ExtensionWidget[] = [
remoteBadge,
versionWidget,
verifiedPublisherWidget,
publisherWidget,
extensionKindWidget,
installCountWidget,
ratingsWidget,
sponsorWidget,
@@ -440,18 +431,23 @@ export class ExtensionEditor extends EditorPane {
header,
icon,
iconContainer,
installCount,
name,
navbar,
preview,
publisher,
publisherDisplayName,
resource,
rating,
actionsAndStatusContainer,
extensionActionBar,
set extension(extension: IExtension) {
extensionContainers.extension = extension;
let lastNonEmptySubtitleEntryContainer;
for (const subTitleEntryElement of subTitleEntryContainers) {
subTitleEntryElement.classList.remove('last-non-empty');
if (subTitleEntryElement.children.length > 0) {
lastNonEmptySubtitleEntryContainer = subTitleEntryElement;
}
}
if (lastNonEmptySubtitleEntryContainer) {
lastNonEmptySubtitleEntryContainer.classList.add('last-non-empty');
}
},
set gallery(gallery: IGalleryExtension | null) {
versionWidget.gallery = gallery;
@@ -559,41 +555,8 @@ export class ExtensionEditor extends EditorPane {
template.description.textContent = extension.description;
// subtitle
template.publisher.classList.toggle('clickable', !!extension.url);
template.publisherDisplayName.textContent = extension.publisherDisplayName;
template.publisher.parentElement?.classList.toggle('hide', !!extension.resourceExtension || extension.local?.source === 'resource');
this.transientDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), template.publisher, localize('publisher', "Publisher ({0})", extension.publisher)));
const location = extension.resourceExtension?.location ?? (extension.local?.source === 'resource' ? extension.local?.location : undefined);
template.resource.parentElement?.classList.toggle('hide', !location);
if (location) {
const workspaceFolder = this.contextService.getWorkspaceFolder(location);
if (workspaceFolder && extension.isWorkspaceScoped) {
template.resource.parentElement?.classList.add('clickable');
this.transientDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), template.resource, this.uriIdentityService.extUri.relativePath(workspaceFolder.uri, location)));
template.resource.textContent = localize('workspace extension', "Workspace Extension");
this.transientDisposables.add(onClick(template.resource, () => {
this.viewsService.openView(EXPLORER_VIEW_ID, true).then(() => this.explorerService.select(location, true));
}));
} else {
template.resource.parentElement?.classList.remove('clickable');
this.transientDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), template.resource, location.path));
template.resource.textContent = localize('local extension', "Local Extension");
}
}
template.installCount.parentElement?.classList.toggle('hide', !extension.url);
template.rating.parentElement?.classList.toggle('hide', !extension.url);
template.rating.classList.toggle('clickable', !!extension.url);
if (extension.url) {
this.transientDisposables.add(onClick(template.name, () => this.openerService.open(URI.parse(extension.url!))));
this.transientDisposables.add(onClick(template.publisher, () => this.extensionsWorkbenchService.openSearch(`publisher:"${extension.publisherDisplayName}"`)));
}
if (extension.ratingUrl) {
this.transientDisposables.add(onClick(template.rating, () => this.openerService.open(URI.parse(extension.ratingUrl!))));
}
const manifest = await this.extensionManifest.get().promise;
@@ -23,6 +23,7 @@ export const syncEnabledIcon = registerIcon('extensions-sync-enabled', Codicon.s
export const syncIgnoredIcon = registerIcon('extensions-sync-ignored', Codicon.syncIgnored, localize('syncIgnoredIcon', 'Icon to indicate that an extension is ignored when syncing.'));
export const remoteIcon = registerIcon('extensions-remote', Codicon.remote, localize('remoteIcon', 'Icon to indicate that an extension is remote in the extensions view and editor.'));
export const installCountIcon = registerIcon('extensions-install-count', Codicon.cloudDownload, localize('installCountIcon', 'Icon shown along with the install count in the extensions view and editor.'));
export const privateExtensionIcon = registerIcon('extensions-private', Codicon.lock, localize('lockIcon', 'Icon shown for private extensions in the extensions view and editor.'));
export const ratingIcon = registerIcon('extensions-rating', Codicon.star, localize('ratingIcon', 'Icon shown along with the rating in the extensions view and editor.'));
export const preReleaseIcon = registerIcon('extensions-pre-release', Codicon.versions, localize('preReleaseIcon', 'Icon shown for extensions having pre-release versions in extensions view and editor.'));
export const sponsorIcon = registerIcon('extensions-sponsor', Codicon.heartFilled, localize('sponsorIcon', 'Icon used for sponsoring extensions in the extensions view and editor.'));
@@ -11,11 +11,10 @@ import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
import { IPagedRenderer } from '../../../../base/browser/ui/list/listPaging.js';
import { Event } from '../../../../base/common/event.js';
import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService, IExtensionsViewState } from '../common/extensions.js';
import { ManageExtensionAction, ExtensionRuntimeStateAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ButtonWithDropDownExtensionAction, InstallDropdownAction, InstallingLabelAction, ButtonWithDropdownExtensionActionViewItem, DropDownExtensionAction, WebInstallAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction, UpdateAction } from './extensionsActions.js';
import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionRuntimeStatusWidget, PreReleaseBookmarkWidget, VerifiedPublisherWidget } from './extensionsWidgets.js';
import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionRuntimeStatusWidget, PreReleaseBookmarkWidget, PublisherWidget, ExtensionKindIndicatorWidget } from './extensionsWidgets.js';
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
import { IWorkbenchExtensionEnablementService } from '../../../services/extensionManagement/common/extensionManagement.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
@@ -34,7 +33,6 @@ export interface ITemplateData {
element: HTMLElement;
icon: HTMLImageElement;
name: HTMLElement;
publisherDisplayName: HTMLElement;
description: HTMLElement;
installCount: HTMLElement;
ratings: HTMLElement;
@@ -85,13 +83,12 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const installCount = append(header, $('span.install-count'));
const ratings = append(header, $('span.ratings'));
const syncIgnore = append(header, $('span.sync-ignored'));
const extensionKindIndicator = append(header, $('span'));
const activationStatus = append(header, $('span.activation-status'));
const headerRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, header, false);
const description = append(details, $('.description.ellipsis'));
const footer = append(details, $('.footer'));
const publisher = append(footer, $('.author.ellipsis'));
const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $(`.verified-publisher`)), true);
const publisherDisplayName = append(publisher, $('.publisher-name.ellipsis'));
const publisherWidget = this.instantiationService.createInstance(PublisherWidget, append(footer, $('.publisher-container')), true);
const actionbar = new ActionBar(footer, {
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
if (action instanceof ButtonWithDropDownExtensionAction) {
@@ -140,12 +137,13 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
iconRemoteBadgeWidget,
extensionPackBadgeWidget,
headerRemoteBadgeWidget,
verifiedPublisherWidget,
publisherWidget,
extensionHoverWidget,
this.instantiationService.createInstance(SyncIgnoredWidget, syncIgnore),
this.instantiationService.createInstance(ExtensionRuntimeStatusWidget, this.extensionViewState, activationStatus),
this.instantiationService.createInstance(InstallCountWidget, installCount, true),
this.instantiationService.createInstance(RatingsWidget, ratings, true),
this.instantiationService.createInstance(ExtensionKindIndicatorWidget, extensionKindIndicator, true),
];
const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]);
@@ -153,7 +151,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const disposable = combinedDisposable(...actions, ...widgets, actionbar, actionBarListener, extensionContainers);
return {
root, element, icon, name, installCount, ratings, description, publisherDisplayName, disposables: [disposable], actionbar,
root, element, icon, name, installCount, ratings, description, disposables: [disposable], actionbar,
extensionDisposables: [],
set extension(extension: IExtension) {
extensionContainers.extension = extension;
@@ -170,7 +168,6 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
data.icon.src = '';
data.name.textContent = '';
data.description.textContent = '';
data.publisherDisplayName.textContent = '';
data.installCount.style.display = 'none';
data.ratings.style.display = 'none';
data.extension = null;
@@ -209,12 +206,6 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
data.name.textContent = extension.displayName;
data.description.textContent = extension.description;
const updatePublisher = () => {
data.publisherDisplayName.textContent = !extension.resourceExtension && extension.local?.source !== 'resource' ? extension.publisherDisplayName : '';
};
updatePublisher();
Event.filter(this.extensionsWorkbenchService.onChange, e => !!e && areSameExtensions(e.identifier, extension.identifier))(() => updatePublisher(), this, data.extensionDisposables);
data.installCount.style.display = '';
data.ratings.style.display = '';
data.extension = extension;
@@ -22,7 +22,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common
import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IUserDataSyncEnablementService } from '../../../../platform/userDataSync/common/userDataSync.js';
import { activationTimeIcon, errorIcon, infoIcon, installCountIcon, preReleaseIcon, ratingIcon, remoteIcon, sponsorIcon, starEmptyIcon, starFullIcon, starHalfIcon, syncIgnoredIcon, warningIcon } from './extensionsIcons.js';
import { activationTimeIcon, errorIcon, infoIcon, installCountIcon, preReleaseIcon, privateExtensionIcon, ratingIcon, remoteIcon, sponsorIcon, starEmptyIcon, starFullIcon, starHalfIcon, syncIgnoredIcon, warningIcon } from './extensionsIcons.js';
import { registerColor, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
@@ -46,6 +46,10 @@ import { Registry } from '../../../../platform/registry/common/platform.js';
import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from '../../../services/extensionManagement/common/extensionFeatures.js';
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
import { extensionVerifiedPublisherIconColor, verifiedPublisherIcon } from '../../../services/extensionManagement/common/extensionsIcons.js';
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
import { IExplorerService } from '../../files/browser/files.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
import { VIEW_ID as EXPLORER_VIEW_ID } from '../../files/common/files.js';
export abstract class ExtensionWidget extends Disposable implements IExtensionContainer {
private _extension: IExtension | null = null;
@@ -71,17 +75,26 @@ export function onClick(element: HTMLElement, callback: () => void): IDisposable
export class InstallCountWidget extends ExtensionWidget {
private readonly disposables = this._register(new DisposableStore());
constructor(
private container: HTMLElement,
readonly container: HTMLElement,
private small: boolean,
@IHoverService private readonly hoverService: IHoverService,
) {
super();
container.classList.add('extension-install-count');
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
this.container.innerText = '';
this.disposables.clear();
}
render(): void {
this.container.innerText = '';
this.clear();
if (!this.extension) {
return;
@@ -96,9 +109,18 @@ export class InstallCountWidget extends ExtensionWidget {
return;
}
append(this.container, $('span' + ThemeIcon.asCSSSelector(installCountIcon)));
const count = append(this.container, $('span.count'));
if (!this.small && !this.extension.url) {
return;
}
const parent = this.small ? this.container : append(this.container, $('span.install', { tabIndex: 0 }));
append(parent, $('span' + ThemeIcon.asCSSSelector(installCountIcon)));
const count = append(parent, $('span.count'));
count.textContent = installLabel;
if (!this.small) {
this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.container, localize('install count', "Install count")));
}
}
static getInstallLabel(extension: IExtension, small: boolean): string | undefined {
@@ -129,12 +151,14 @@ export class InstallCountWidget extends ExtensionWidget {
export class RatingsWidget extends ExtensionWidget {
private readonly containerHover: IManagedHover;
private containerHover: IManagedHover | undefined;
private readonly disposables = this._register(new DisposableStore());
constructor(
private container: HTMLElement,
readonly container: HTMLElement,
private small: boolean,
@IHoverService hoverService: IHoverService
@IHoverService private readonly hoverService: IHoverService,
@IOpenerService private readonly openerService: IOpenerService,
) {
super();
container.classList.add('extension-ratings');
@@ -143,13 +167,17 @@ export class RatingsWidget extends ExtensionWidget {
container.classList.add('small');
}
this.containerHover = this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), container, ''));
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
this.container.innerText = '';
this.disposables.clear();
}
render(): void {
this.container.innerText = '';
this.clear();
if (!this.extension) {
return;
@@ -167,51 +195,71 @@ export class RatingsWidget extends ExtensionWidget {
return;
}
if (!this.extension.url) {
return;
}
const rating = Math.round(this.extension.rating * 2) / 2;
this.containerHover.update(localize('ratedLabel', "Average rating: {0} out of 5", rating));
if (this.small) {
append(this.container, $('span' + ThemeIcon.asCSSSelector(starFullIcon)));
const count = append(this.container, $('span.count'));
count.textContent = String(rating);
} else {
const element = append(this.container, $('span.rating.clickable', { tabIndex: 0 }));
for (let i = 1; i <= 5; i++) {
if (rating >= i) {
append(this.container, $('span' + ThemeIcon.asCSSSelector(starFullIcon)));
append(element, $('span' + ThemeIcon.asCSSSelector(starFullIcon)));
} else if (rating >= i - 0.5) {
append(this.container, $('span' + ThemeIcon.asCSSSelector(starHalfIcon)));
append(element, $('span' + ThemeIcon.asCSSSelector(starHalfIcon)));
} else {
append(this.container, $('span' + ThemeIcon.asCSSSelector(starEmptyIcon)));
append(element, $('span' + ThemeIcon.asCSSSelector(starEmptyIcon)));
}
}
if (this.extension.ratingCount) {
const ratingCountElemet = append(this.container, $('span', undefined, ` (${this.extension.ratingCount})`));
const ratingCountElemet = append(element, $('span', undefined, ` (${this.extension.ratingCount})`));
ratingCountElemet.style.paddingLeft = '1px';
}
this.containerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), element, ''));
this.containerHover.update(localize('ratedLabel', "Average rating: {0} out of 5", rating));
element.setAttribute('role', 'link');
if (this.extension.ratingUrl) {
this.disposables.add(onClick(element, () => this.openerService.open(URI.parse(this.extension!.ratingUrl!))));
}
}
}
}
export class VerifiedPublisherWidget extends ExtensionWidget {
export class PublisherWidget extends ExtensionWidget {
private element: HTMLElement | undefined;
private containerHover: IManagedHover | undefined;
private readonly disposables = this._register(new DisposableStore());
private readonly containerHover: IManagedHover;
constructor(
private container: HTMLElement,
readonly container: HTMLElement,
private small: boolean,
@IHoverService hoverService: IHoverService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IHoverService private readonly hoverService: IHoverService,
@IOpenerService private readonly openerService: IOpenerService,
) {
super();
this.containerHover = this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), container, ''));
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
this.element?.remove();
this.disposables.clear();
}
render(): void {
reset(this.container);
this.disposables.clear();
if (!this.extension?.publisherDomain?.verified) {
this.clear();
if (!this.extension) {
return;
}
@@ -223,20 +271,45 @@ export class VerifiedPublisherWidget extends ExtensionWidget {
return;
}
const publisherDomainLink = URI.parse(this.extension.publisherDomain.link);
const verifiedPublisher = append(this.container, $('span.extension-verified-publisher.clickable'));
append(verifiedPublisher, renderIcon(verifiedPublisherIcon));
this.element = append(this.container, $('.publisher'));
const publisherDisplayName = $('.publisher-name.ellipsis');
publisherDisplayName.textContent = this.extension.publisherDisplayName;
if (!this.small) {
verifiedPublisher.tabIndex = 0;
this.containerHover.update(`Verified Domain: ${this.extension.publisherDomain.link}`);
verifiedPublisher.setAttribute('role', 'link');
const verifiedPublisher = $('.verified-publisher');
append(verifiedPublisher, $('span.extension-verified-publisher.clickable'), renderIcon(verifiedPublisherIcon));
append(verifiedPublisher, $('span.extension-verified-publisher-domain', undefined, publisherDomainLink.authority.startsWith('www.') ? publisherDomainLink.authority.substring(4) : publisherDomainLink.authority));
this.disposables.add(onClick(verifiedPublisher, () => this.openerService.open(publisherDomainLink)));
if (this.small) {
if (this.extension.publisherDomain) {
append(this.element, verifiedPublisher);
}
append(this.element, publisherDisplayName);
} else {
this.element.classList.toggle('clickable', !!this.extension.url);
this.element.setAttribute('role', 'button');
this.element.tabIndex = 0;
this.containerHover = this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, localize('publisher', "Publisher ({0})", this.extension.publisherDisplayName)));
append(this.element, publisherDisplayName);
if (this.extension.publisherDomain) {
append(this.element, verifiedPublisher);
const publisherDomainLink = URI.parse(this.extension.publisherDomain.link);
verifiedPublisher.tabIndex = 0;
verifiedPublisher.setAttribute('role', 'button');
this.containerHover.update(localize('verified publisher', "This publisher has verified ownership of {0}", this.extension.publisherDomain.link));
verifiedPublisher.setAttribute('role', 'link');
append(verifiedPublisher, $('span.extension-verified-publisher-domain', undefined, publisherDomainLink.authority.startsWith('www.') ? publisherDomainLink.authority.substring(4) : publisherDomainLink.authority));
this.disposables.add(onClick(verifiedPublisher, () => this.openerService.open(publisherDomainLink)));
}
if (this.extension.url) {
this.disposables.add(onClick(this.element, () => this.extensionsWorkbenchService.openSearch(`publisher:"${this.extension?.publisherDisplayName}"`)));
}
}
}
}
export class SponsorWidget extends ExtensionWidget {
@@ -244,7 +317,7 @@ export class SponsorWidget extends ExtensionWidget {
private readonly disposables = this._register(new DisposableStore());
constructor(
private container: HTMLElement,
readonly container: HTMLElement,
@IHoverService private readonly hoverService: IHoverService,
@IOpenerService private readonly openerService: IOpenerService,
) {
@@ -445,6 +518,73 @@ export class ExtensionPackCountWidget extends ExtensionWidget {
}
}
export class ExtensionKindIndicatorWidget extends ExtensionWidget {
private element: HTMLElement | undefined;
private readonly disposables = this._register(new DisposableStore());
constructor(
readonly container: HTMLElement,
private small: boolean,
@IHoverService private readonly hoverService: IHoverService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IExplorerService private readonly explorerService: IExplorerService,
@IViewsService private readonly viewsService: IViewsService,
) {
super();
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
this.element?.remove();
this.disposables.clear();
}
render(): void {
this.clear();
if (this.small) {
return;
}
if (!this.extension) {
return;
}
if (this.extension?.private) {
this.element = append(this.container, $('.extension-kind-indicator'));
append(this.element, $('span' + ThemeIcon.asCSSSelector(privateExtensionIcon)));
if (!this.small) {
append(this.element, $('span.private-extension-label', undefined, localize('privateExtension', "Private Extension")));
}
return;
}
const location = this.extension.resourceExtension?.location ?? (this.extension.local?.source === 'resource' ? this.extension.local?.location : undefined);
if (!location) {
return;
}
this.element = append(this.container, $('.extension-kind-indicator'));
const workspaceFolder = this.contextService.getWorkspaceFolder(location);
if (workspaceFolder && this.extension.isWorkspaceScoped) {
this.element.textContent = localize('workspace extension', "Workspace Extension");
this.element.classList.add('clickable');
this.element.setAttribute('role', 'button');
this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, this.uriIdentityService.extUri.relativePath(workspaceFolder.uri, location)));
this.disposables.add(onClick(this.element, () => {
this.viewsService.openView(EXPLORER_VIEW_ID, true).then(() => this.explorerService.select(location, true));
}));
} else {
this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, location.path));
this.element.textContent = localize('local extension', "Local Extension");
}
}
}
export class SyncIgnoredWidget extends ExtensionWidget {
private readonly disposables = this._register(new DisposableStore());
@@ -595,8 +735,12 @@ export class ExtensionHoverWidget extends ExtensionWidget {
}
markdown.appendText(`\n`);
let addSeparator = false;
if (this.extension.private) {
markdown.appendMarkdown(`$(${privateExtensionIcon.id}) ${localize('privateExtension', "Private Extension")}`);
addSeparator = true;
}
if (this.extension.state === ExtensionState.Installed) {
let addSeparator = false;
const installLabel = InstallCountWidget.getInstallLabel(this.extension, true);
if (installLabel) {
if (addSeparator) {
@@ -620,9 +764,9 @@ export class ExtensionHoverWidget extends ExtensionWidget {
markdown.appendMarkdown(`$(${sponsorIcon.id}) [${localize('sponsor', "Sponsor")}](${this.extension.publisherSponsorLink})`);
addSeparator = true;
}
if (addSeparator) {
markdown.appendText(`\n`);
}
}
if (addSeparator) {
markdown.appendText(`\n`);
}
const location = this.extension.resourceExtension?.location ?? (this.extension.local?.source === 'resource' ? this.extension.local?.location : undefined);
@@ -862,6 +1006,7 @@ export class ExtensionRecommendationWidget extends ExtensionWidget {
export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hcDark: '#FF8E00', hcLight: textLinkForeground }, localize('extensionIconStarForeground', "The icon color for extension ratings."), false);
export const extensionPreReleaseIconColor = registerColor('extensionIcon.preReleaseForeground', { dark: '#1d9271', light: '#1d9271', hcDark: '#1d9271', hcLight: textLinkForeground }, localize('extensionPreReleaseForeground', "The icon color for pre-release extension."), false);
export const extensionSponsorIconColor = registerColor('extensionIcon.sponsorForeground', { light: '#B51E78', dark: '#D758B3', hcDark: null, hcLight: '#B51E78' }, localize('extensionIcon.sponsorForeground', "The icon color for extension sponsor."), false);
export const extensionPrivateBadgeBackground = registerColor('extensionIcon.privateForeground', { dark: '#ffffff60', light: '#00000060', hcDark: '#ffffff60', hcLight: '#00000060' }, localize('extensionIcon.private', "The icon color for private extensions."));
registerThemingParticipant((theme, collector) => {
const extensionRatingIcon = theme.getColor(extensionRatingIconColor);
@@ -877,4 +1022,9 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(sponsorIcon)} { color: var(--vscode-extensionIcon-sponsorForeground); }`);
collector.addRule(`.extension-editor > .header > .details > .subtitle .sponsor ${ThemeIcon.asCSSSelector(sponsorIcon)} { color: var(--vscode-extensionIcon-sponsorForeground); }`);
const privateBadgeBackground = theme.getColor(extensionPrivateBadgeBackground);
if (privateBadgeBackground) {
collector.addRule(`.extension-private-badge { color: ${privateBadgeBackground}; }`);
}
});
@@ -211,6 +211,10 @@ export class Extension implements IExtension {
return this.local ? this.local.manifest.version : this.latestVersion;
}
get private(): boolean {
return this.local ? this.local.private : this.gallery ? this.gallery.private : false;
}
get pinned(): boolean {
return !!this.local?.pinned;
}
@@ -92,11 +92,17 @@
.extension-list-item > .details > .header-container > .header > .activation-status,
.extension-list-item > .details > .header-container > .header > .install-count,
.extension-list-item > .details > .header-container > .header .extension-kind-indicator,
.extension-list-item > .details > .header-container > .header > .ratings {
display: flex;
align-items: center;
}
.extension-list-item > .details > .header-container > .header .extension-kind-indicator {
font-size: 80%;
margin-left: 2px;
}
.extension-list-item > .details > .header-container > .header > .install-count:not(:empty) {
font-size: 80%;
margin: 0 6px;
@@ -179,24 +185,27 @@
align-items: center;
}
.extension-list-item > .details > .footer > .author {
.extension-list-item > .details > .footer > .publisher-container {
flex: 1;
display: flex;
align-items: center;
line-height: 24px;
}
.extension-list-item > .details > .footer > .author > .publisher-name {
.extension-list-item > .details > .footer .publisher {
display: flex;
align-items: center;
}
.extension-list-item > .details > .footer .publisher > .publisher-name {
font-size: 90%;
color: var(--vscode-descriptionForeground);
font-weight: 600;
}
.monaco-list-row.selected .extension-list-item > .details > .footer > .author > .publisher-name{
.monaco-list-row.selected .extension-list-item > .details > .footer .publisher > .publisher-name{
color: unset;
}
.extension-list-item > .details > .footer > .author > .publisher-name:not(:first-child) {
.extension-list-item > .details > .footer .publisher > .publisher-name:not(:first-child) {
padding-left: 1px;
}
@@ -227,7 +236,7 @@
min-width: 0;
}
.monaco-list-row.disabled:not(.selected) .extension-list-item > .details > .footer > .author > .publisher-name {
.monaco-list-row.disabled:not(.selected) .extension-list-item > .details > .footer .publisher > .publisher-name {
color: var(--vscode-disabledForeground);
}
@@ -148,6 +148,7 @@
}
.extension-editor > .header > .details > .subtitle,
.extension-editor > .header > .details > .subtitle .extension-kind-indicator,
.extension-editor > .header > .details > .subtitle .install,
.extension-editor > .header > .details > .subtitle .rating,
.extension-editor > .header > .details > .subtitle .sponsor {
@@ -163,10 +164,14 @@
margin-left: 6px;
}
.extension-editor > .header > .details > .subtitle > div:not(:first-child):not(:empty):not(.resource) {
border-left: 1px solid rgba(128, 128, 128, 0.7);
margin-left: 14px;
padding-left: 14px;
.extension-editor > .header > .details > .subtitle .extension-kind-indicator > .codicon {
margin-right: 6px;
}
.extension-editor > .header > .details > .subtitle > .subtitle-entry:not(:empty):not(.last-non-empty) {
border-right: 1px solid rgba(128, 128, 128, 0.7);
margin-right: 14px;
padding-right: 14px;
}
.extension-editor > .header > .details > .description {
@@ -734,7 +739,7 @@
padding-top: 5px;
}
.extension-editor .subcontent .monaco-list-row .extension > .details > .footer > .author {
.extension-editor .subcontent .monaco-list-row .extension > .details > .footer .publisher {
font-size: 90%;
font-weight: 600;
opacity: 0.6;
@@ -31,12 +31,12 @@
opacity: .75;
}
.extension-verified-publisher {
.verified-publisher {
display: flex;
align-items: center;
}
.extension-verified-publisher > .extension-verified-publisher-domain {
.verified-publisher > .extension-verified-publisher-domain {
padding-left: 2px;
color: var(--vscode-extensionIcon-verifiedForeground);
text-decoration: var(--text-link-decoration);
@@ -68,6 +68,7 @@ export interface IExtension {
readonly publisherSponsorLink?: URI;
readonly pinned: boolean;
readonly version: string;
readonly private: boolean;
readonly latestVersion: string;
readonly preRelease: boolean;
readonly isPreReleaseVersion: boolean;
@@ -1409,6 +1409,7 @@ class WorkspaceExtensionsManagementService extends Disposable {
updated: !!extension.metadata?.updated,
pinned: !!extension.metadata?.pinned,
isWorkspaceScoped: true,
private: false,
source: 'resource',
size: extension.metadata?.size ?? 0,
};
@@ -237,6 +237,7 @@ function toLocalExtension(extension: IExtension): ILocalExtension {
targetPlatform: TargetPlatform.WEB,
updated: !!metadata.updated,
pinned: !!metadata?.pinned,
private: !!metadata.private,
isWorkspaceScoped: false,
source: metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'resource'),
size: metadata.size ?? 0,
@@ -291,6 +292,7 @@ class InstallExtensionTask extends AbstractExtensionTask<ILocalExtension> implem
metadata.isSystem = existingExtension?.type === ExtensionType.System ? true : undefined;
metadata.updated = !!existingExtension;
metadata.isApplicationScoped = this.options.isApplicationScoped || metadata.isApplicationScoped;
metadata.private = this.extension.private;
metadata.preRelease = isBoolean(this.options.preRelease)
? this.options.preRelease
: this.options.installPreReleaseVersion || this.extension.properties.isPreReleaseVersion || metadata.preRelease;