66import { $ , append , EventType , addDisposableListener , EventHelper , disposableWindowInterval , getWindow } from '../../../../../base/browser/dom.js' ;
77import { Gesture , EventType as TouchEventType } from '../../../../../base/browser/touch.js' ;
88import { ActionBar } from '../../../../../base/browser/ui/actionbar/actionbar.js' ;
9+ import { renderLabelWithIcons } from '../../../../../base/browser/ui/iconLabel/iconLabels.js' ;
910import { Button } from '../../../../../base/browser/ui/button/button.js' ;
1011import { Checkbox } from '../../../../../base/browser/ui/toggle/toggle.js' ;
1112import { IAction , toAction , WorkbenchActionExecutedEvent , WorkbenchActionExecutedClassification } from '../../../../../base/common/actions.js' ;
1213import { CancellationToken , cancelOnDispose } from '../../../../../base/common/cancellation.js' ;
1314import { Codicon } from '../../../../../base/common/codicons.js' ;
1415import { safeIntl } from '../../../../../base/common/date.js' ;
1516import { MarkdownString } from '../../../../../base/common/htmlContent.js' ;
16- import { DisposableStore } from '../../../../../base/common/lifecycle.js' ;
17+ import { MutableDisposable , DisposableStore } from '../../../../../base/common/lifecycle.js' ;
18+ import { parseLinkedText } from '../../../../../base/common/linkedText.js' ;
1719import { language } from '../../../../../base/common/platform.js' ;
1820import { ThemeIcon } from '../../../../../base/common/themables.js' ;
1921import { isObject } from '../../../../../base/common/types.js' ;
@@ -29,6 +31,7 @@ import { ICommandService } from '../../../../../platform/commands/common/command
2931import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js' ;
3032import { IHoverService , nativeHoverDelegate } from '../../../../../platform/hover/browser/hover.js' ;
3133import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js' ;
34+ import { Link } from '../../../../../platform/opener/browser/link.js' ;
3235import { IOpenerService } from '../../../../../platform/opener/common/opener.js' ;
3336import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js' ;
3437import { defaultButtonStyles , defaultCheckboxStyles } from '../../../../../platform/theme/browser/defaultStyles.js' ;
@@ -37,6 +40,7 @@ import { EditorResourceAccessor, SideBySideEditor } from '../../../../common/edi
3740import { IChatEntitlementService , ChatEntitlementService , ChatEntitlement , IQuotaSnapshot , getChatPlanName } from '../../../../services/chat/common/chatEntitlementService.js' ;
3841import { IEditorService } from '../../../../services/editor/common/editorService.js' ;
3942import { isNewUser } from './chatStatus.js' ;
43+ import { IChatStatusItemService , ChatStatusEntry } from './chatStatusItemService.js' ;
4044import product from '../../../../../platform/product/common/product.js' ;
4145import { contrastBorder , inputValidationErrorBorder , inputValidationInfoBorder , inputValidationWarningBorder , registerColor , transparent } from '../../../../../platform/theme/common/colorRegistry.js' ;
4246import { Color } from '../../../../../base/common/color.js' ;
@@ -134,6 +138,7 @@ export class ChatStatusDashboard extends DomWidget {
134138 constructor (
135139 private readonly options : IChatStatusDashboardOptions | undefined ,
136140 @IChatEntitlementService private readonly chatEntitlementService : ChatEntitlementService ,
141+ @IChatStatusItemService private readonly chatStatusItemService : IChatStatusItemService ,
137142 @ICommandService private readonly commandService : ICommandService ,
138143 @IConfigurationService private readonly configurationService : IConfigurationService ,
139144 @IEditorService private readonly editorService : IEditorService ,
@@ -257,6 +262,30 @@ export class ChatStatusDashboard extends DomWidget {
257262 this . renderInlineSuggestionsContent ( this . element , token , updatePromise ) ;
258263 }
259264
265+ // Contributions
266+ {
267+ for ( const item of this . chatStatusItemService . getEntries ( ) ) {
268+ this . element . appendChild ( $ ( 'hr' ) ) ;
269+
270+ const itemDisposables = this . _store . add ( new MutableDisposable ( ) ) ;
271+
272+ let rendered = this . renderContributedChatStatusItem ( item ) ;
273+ itemDisposables . value = rendered . disposables ;
274+ this . element . appendChild ( rendered . element ) ;
275+
276+ this . _store . add ( this . chatStatusItemService . onDidChange ( e => {
277+ if ( e . entry . id === item . id ) {
278+ const previousElement = rendered . element ;
279+
280+ rendered = this . renderContributedChatStatusItem ( e . entry ) ;
281+ itemDisposables . value = rendered . disposables ;
282+
283+ previousElement . replaceWith ( rendered . element ) ;
284+ }
285+ } ) ) ;
286+ }
287+ }
288+
260289 // New to Chat / Signed out
261290 {
262291 const newUser = isNewUser ( this . chatEntitlementService ) ;
@@ -460,6 +489,47 @@ export class ChatStatusDashboard extends DomWidget {
460489 }
461490 }
462491
492+ private renderContributedChatStatusItem ( item : ChatStatusEntry ) : { element : HTMLElement ; disposables : DisposableStore } {
493+ const disposables = new DisposableStore ( ) ;
494+
495+ const itemElement = $ ( 'div.contribution' ) ;
496+
497+ const headerLabel = typeof item . label === 'string' ? item . label : item . label . label ;
498+ const headerLink = typeof item . label === 'string' ? undefined : item . label . link ;
499+ this . renderHeader ( itemElement , disposables , headerLabel , headerLink ? toAction ( {
500+ id : 'workbench.action.openChatStatusItemLink' ,
501+ label : localize ( 'learnMore' , "Learn More" ) ,
502+ tooltip : localize ( 'learnMore' , "Learn More" ) ,
503+ class : ThemeIcon . asClassName ( Codicon . linkExternal ) ,
504+ run : ( ) => this . runCommandAndClose ( ( ) => this . openerService . open ( URI . parse ( headerLink ) ) ) ,
505+ } ) : undefined ) ;
506+
507+ const itemBody = itemElement . appendChild ( $ ( 'div.body' ) ) ;
508+
509+ const description = itemBody . appendChild ( $ ( 'span.description' ) ) ;
510+ this . renderTextPlus ( description , item . description , disposables ) ;
511+
512+ if ( item . detail ) {
513+ const separator = itemBody . appendChild ( $ ( 'span.separator' ) ) ;
514+ separator . textContent = '\u2014' ;
515+ const detail = itemBody . appendChild ( $ ( 'span.detail-item' ) ) ;
516+ this . renderTextPlus ( detail , item . detail , disposables ) ;
517+ }
518+
519+ return { element : itemElement , disposables } ;
520+ }
521+
522+ private renderTextPlus ( target : HTMLElement , text : string , store : DisposableStore ) : void {
523+ for ( const node of parseLinkedText ( text ) . nodes ) {
524+ if ( typeof node === 'string' ) {
525+ const parts = renderLabelWithIcons ( node ) ;
526+ target . append ( ...parts ) ;
527+ } else {
528+ store . add ( new Link ( target , node , undefined , this . hoverService , this . openerService ) ) ;
529+ }
530+ }
531+ }
532+
463533 private runCommandAndClose ( commandOrFn : string | ( ( ...args : unknown [ ] ) => void ) , ...args : unknown [ ] ) : void {
464534 if ( typeof commandOrFn === 'function' ) {
465535 commandOrFn ( ...args ) ;
0 commit comments