diff --git a/src/vs/workbench/browser/parts/notifications/notificationList.ts b/src/vs/workbench/browser/parts/notifications/notificationList.ts index 4e5846b3f8117fddec204709dbcd00439e19ce9d..972dc0d363083a614d82f83a2257508b67809d8a 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationList.ts @@ -18,6 +18,8 @@ import { INotification, INotificationHandle } from 'vs/platform/notification/com import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/common/notifications'; import { INotificationHandler } from 'vs/workbench/services/notification/common/notificationService'; import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationViewer'; +import { Severity } from 'vs/platform/message/common/message'; +import { alert } from 'vs/base/browser/ui/aria/aria'; export class NotificationList extends Themable implements INotificationHandler { @@ -52,17 +54,22 @@ export class NotificationList extends Themable implements INotificationHandler { this.listContainer = document.createElement('div'); addClass(this.listContainer, 'notifications-list-container'); + // Notification Renderer + const renderer = this.instantiationService.createInstance(NotificationRenderer); + this.toUnbind.push(renderer); + // List this.list = this.instantiationService.createInstance( WorkbenchList, this.listContainer, new NotificationsListDelegate(this.listContainer), - [this.instantiationService.createInstance(NotificationRenderer)], + [renderer], { ariaLabel: localize('notificationsList', "Notifications List"), openController: { shouldOpen: e => this.shouldExpand(e) } } as IListOptions ); + this.toUnbind.push(this.list); // Expand/Collapse this.list.onOpen(e => { @@ -105,6 +112,9 @@ export class NotificationList extends Themable implements INotificationHandler { return NotificationList.NO_OP_NOTIFICATION; } + // Support in Screen Readers too + this.ariaAlert(viewItem); + viewItem.onDidExpansionChange(() => { // TODO expand/collapse using model index }); // TODO@Notification dispose @@ -114,7 +124,20 @@ export class NotificationList extends Themable implements INotificationHandler { this.list.splice(0, 0, [viewItem]); this.list.layout(); - return { dispose: () => void 0 }; + return viewItem; // TODO@notification what if message replaced with other? + } + + private ariaAlert(notifiation: INotificationViewItem): void { + let alertText: string; + if (notifiation.severity === Severity.Error) { + alertText = localize('alertErrorMessage', "Error: {0}", notifiation.message.value); + } else if (notifiation.severity === Severity.Warning) { + alertText = localize('alertWarningMessage', "Warning: {0}", notifiation.message.value); + } else { + alertText = localize('alertInfoMessage', "Info: {0}", notifiation.message.value); + } + + alert(alertText); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationViewer.ts index cb9c69cd93ac141d2076af142226fdd2e0acd6ec..78ff957098712129f3ad2a388a56bef59cf78be3 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationViewer.ts @@ -26,6 +26,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/common/notifications'; import { CloseNotificationAction, ExpandNotificationAction, CollapseNotificationAction, DoNotShowNotificationAgainAction, ConfigureNotificationAction } from 'vs/workbench/browser/parts/notifications/notificationActions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Promise } from 'vs/base/common/winjs.base'; export class NotificationsListDelegate implements IDelegate { @@ -82,7 +84,7 @@ export class NotificationsListDelegate implements IDelegate text || ''; private static readonly MARKED_NOOP_TARGETS = [ @@ -133,7 +135,7 @@ class NotificationMarkdownRenderer { public static render(markdown: IMarkdownString, actionCallback?: (content: string) => void): HTMLElement { return renderMarkdown(markdown, { inline: true, - joinRendererConfiguration: renderer => NotificationMarkdownRenderer.MARKED_NOOP_TARGETS.forEach(fn => renderer[fn] = NotificationMarkdownRenderer.MARKED_NOOP), + joinRendererConfiguration: renderer => NotificationMessageMarkdownRenderer.MARKED_NOOP_TARGETS.forEach(fn => renderer[fn] = NotificationMessageMarkdownRenderer.MARKED_NOOP), actionCallback }); } @@ -145,6 +147,8 @@ export class NotificationRenderer implements IRenderer { - const domAction = element.severity === this.toSeverity(severity) ? addClass : removeClass; + const domAction = notification.severity === this.toSeverity(severity) ? addClass : removeClass; domAction(data.icon, `icon-${severity}`); }); // Message (simple markdown with links support) clearNode(data.message); - data.message.appendChild(NotificationMarkdownRenderer.render(element.message, (content: string) => this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError))); + data.message.appendChild(NotificationMessageMarkdownRenderer.render(notification.message, (content: string) => this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError))); // Actions const actions: IAction[] = []; @@ -265,39 +274,66 @@ export class NotificationRenderer implements IRenderer { - const button = new Button(data.actionsContainer); - data.toDispose.push(attachButtonStyler(button, this.themeService)); - - button.label = action.label; - button.onDidClick(() => action.run()); - }); + if (notification.expanded) { + notification.actions.forEach(action => this.createButton(notification, action, data)); } } + private createButton(notification: INotificationViewItem, action: IAction, data: INotificationTemplateData): Button { + const button = new Button(data.actionsContainer); + data.toDispose.push(attachButtonStyler(button, this.themeService)); + + button.label = action.label; + button.onDidClick(() => { + + // Forward to action + const result = action.run(); + if (Promise.is(result)) { + result.done(null, onUnexpectedError); + } + + // Telemetry + /* __GDPR__ + "workbenchActionExecuted" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'message' }); + + // Dispose notification + notification.dispose(); + }); + + return button; + } + public disposeTemplate(templateData: INotificationTemplateData): void { templateData.toolbar.dispose(); templateData.toDispose = dispose(templateData.toDispose); } + + public dispose(): void { + this.toDispose = dispose(this.toDispose); + } } \ No newline at end of file diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index cdafbe4f6f1cbd3aa3ecb2adf7aca7cd4fe439a2..4c41cbd8b0be03ee1a26dc542dd2b95b72edc25e 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -12,6 +12,7 @@ import { INotification } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { localize } from 'vs/nls'; import Event, { Emitter } from 'vs/base/common/event'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; export class INotificationsModel { @@ -42,11 +43,17 @@ export class NotificationViewItem implements INotificationViewItem { private static DEFAULT_SOURCE = localize('product', "Product"); private _expanded: boolean; - - private _onDidExpansionChange = new Emitter(); + private toDispose: IDisposable[]; + private _onDidExpansionChange; constructor(private _severity: Severity, private _message: IMarkdownString, private _source: string, private _actions: IAction[]) { + this.toDispose = []; this._expanded = _actions.length > 0; + + this._onDidExpansionChange = new Emitter(); + this.toDispose.push(this._onDidExpansionChange); + + this.toDispose.push(..._actions); } public get onDidExpansionChange(): Event { @@ -109,6 +116,6 @@ export class NotificationViewItem implements INotificationViewItem { } public dispose(): void { - this._onDidExpansionChange.dispose(); + this.toDispose = dispose(this.toDispose); } } \ No newline at end of file