Skip to content

Commit 92df7a0

Browse files
authored
Merge pull request microsoft#90830 from microsoft/joao/notifications-linked-text
Adopt LinkedText in notifications
2 parents 33545a2 + d9e5fe6 commit 92df7a0

4 files changed

Lines changed: 29 additions & 85 deletions

File tree

src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class NotificationsAlerts extends Disposable {
3737
if (e.item.message.original instanceof Error) {
3838
console.error(e.item.message.original);
3939
} else {
40-
console.error(toErrorMessage(e.item.message.value, true));
40+
console.error(toErrorMessage(e.item.message.linkedText.toString(), true));
4141
}
4242
}
4343
}
@@ -60,11 +60,11 @@ export class NotificationsAlerts extends Disposable {
6060
private doTriggerAriaAlert(notifiation: INotificationViewItem): void {
6161
let alertText: string;
6262
if (notifiation.severity === Severity.Error) {
63-
alertText = localize('alertErrorMessage', "Error: {0}", notifiation.message.value);
63+
alertText = localize('alertErrorMessage', "Error: {0}", notifiation.message.linkedText.toString());
6464
} else if (notifiation.severity === Severity.Warning) {
65-
alertText = localize('alertWarningMessage', "Warning: {0}", notifiation.message.value);
65+
alertText = localize('alertWarningMessage', "Warning: {0}", notifiation.message.linkedText.toString());
6666
} else {
67-
alertText = localize('alertInfoMessage', "Info: {0}", notifiation.message.value);
67+
alertText = localize('alertInfoMessage', "Info: {0}", notifiation.message.linkedText.toString());
6868
}
6969

7070
alert(alertText);

src/vs/workbench/browser/parts/notifications/notificationsViewer.ts

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
7-
import { clearNode, addClass, removeClass, toggleClass, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
7+
import { clearNode, addClass, removeClass, toggleClass, addDisposableListener, EventType, EventHelper, $ } from 'vs/base/browser/dom';
88
import { IOpenerService } from 'vs/platform/opener/common/opener';
99
import { URI } from 'vs/base/common/uri';
1010
import { localize } from 'vs/nls';
@@ -23,6 +23,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
2323
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
2424
import { Severity } from 'vs/platform/notification/common/notification';
2525
import { isNonEmptyArray } from 'vs/base/common/arrays';
26+
import { startsWith } from 'vs/base/common/strings';
2627

2728
export class NotificationsListDelegate implements IListVirtualDelegate<INotificationViewItem> {
2829

@@ -136,39 +137,25 @@ class NotificationMessageRenderer {
136137
static render(message: INotificationMessage, actionHandler?: IMessageActionHandler): HTMLElement {
137138
const messageContainer = document.createElement('span');
138139

139-
// Message has no links
140-
if (message.links.length === 0) {
141-
messageContainer.textContent = message.value;
142-
}
143-
144-
// Message has links
145-
else {
146-
let index = 0;
147-
for (const link of message.links) {
140+
for (const node of message.linkedText.nodes) {
141+
if (typeof node === 'string') {
142+
messageContainer.appendChild(document.createTextNode(node));
143+
} else {
144+
let title = node.title;
148145

149-
const textBefore = message.value.substring(index, link.offset);
150-
if (textBefore) {
151-
messageContainer.appendChild(document.createTextNode(textBefore));
146+
if (!title && startsWith(node.href, 'command:')) {
147+
title = localize('executeCommand', "Click to execute command '{0}'", node.href.substr('command:'.length));
148+
} else if (!title) {
149+
title = node.href;
152150
}
153151

154-
const anchor = document.createElement('a');
155-
anchor.textContent = link.name;
156-
anchor.title = link.title;
157-
anchor.href = link.href;
152+
const anchor = $('a', { href: node.href, title: title, }, node.label);
158153

159154
if (actionHandler) {
160-
actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, () => actionHandler.callback(link.href)));
155+
actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, () => actionHandler.callback(node.href)));
161156
}
162157

163158
messageContainer.appendChild(anchor);
164-
165-
index = link.offset + link.length;
166-
}
167-
168-
// Add text after links if any
169-
const textAfter = message.value.substring(index);
170-
if (textAfter) {
171-
messageContainer.appendChild(document.createTextNode(textAfter));
172159
}
173160
}
174161

src/vs/workbench/common/notifications.ts

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'
1010
import { isPromiseCanceledError } from 'vs/base/common/errors';
1111
import { Action } from 'vs/base/common/actions';
1212
import { isErrorWithActions } from 'vs/base/common/errorsWithActions';
13-
import { startsWith } from 'vs/base/common/strings';
14-
import { localize } from 'vs/nls';
1513
import { find, equals } from 'vs/base/common/arrays';
14+
import { parseLinkedText, LinkedText } from 'vs/base/common/linkedText';
1615

1716
export interface INotificationsModel {
1817

@@ -393,18 +392,13 @@ export interface IMessageLink {
393392
export interface INotificationMessage {
394393
raw: string;
395394
original: NotificationMessage;
396-
value: string;
397-
links: IMessageLink[];
395+
linkedText: LinkedText;
398396
}
399397

400398
export class NotificationViewItem extends Disposable implements INotificationViewItem {
401399

402400
private static readonly MAX_MESSAGE_LENGTH = 1000;
403401

404-
// Example link: "Some message with [link text](http://link.href)."
405-
// RegEx: [, anything not ], ], (, http://|https://|command:, no whitespace)
406-
private static readonly LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi;
407-
408402
private _expanded: boolean | undefined;
409403

410404
private _actions: INotificationActions | undefined;
@@ -469,23 +463,9 @@ export class NotificationViewItem extends Disposable implements INotificationVie
469463
message = message.replace(/(\r\n|\n|\r)/gm, ' ').trim();
470464

471465
// Parse Links
472-
const links: IMessageLink[] = [];
473-
message.replace(NotificationViewItem.LINK_REGEX, (matchString: string, name: string, href: string, title: string, offset: number) => {
474-
let massagedTitle: string;
475-
if (title && title.length > 0) {
476-
massagedTitle = title;
477-
} else if (startsWith(href, 'command:')) {
478-
massagedTitle = localize('executeCommand', "Click to execute command '{0}'", href.substr('command:'.length));
479-
} else {
480-
massagedTitle = href;
481-
}
482-
483-
links.push({ name, href, title: massagedTitle, offset, length: matchString.length });
484-
485-
return matchString;
486-
});
466+
const linkedText = parseLinkedText(message);
487467

488-
return { raw, value: message, links, original: input };
468+
return { raw, linkedText, original: input };
489469
}
490470

491471
private constructor(
@@ -649,7 +629,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
649629
return false;
650630
}
651631

652-
if (this._message.value !== other.message.value) {
632+
if (this._message.raw !== other.message.raw) {
653633
return false;
654634
}
655635

src/vs/workbench/test/common/notifications.test.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -105,29 +105,6 @@ suite('Notifications', () => {
105105
let item6 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!;
106106
assert.equal(item6.actions!.primary!.length, 1);
107107

108-
// Links
109-
let item7 = NotificationViewItem.create({ severity: Severity.Info, message: 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and [Link 3](command:without.title) and [Invalid Link4](ftp://link4.com)' })!;
110-
111-
const links = item7.message.links;
112-
assert.equal(links.length, 3);
113-
assert.equal(links[0].name, 'Link 1');
114-
assert.equal(links[0].href, 'http://link1.com');
115-
assert.equal(links[0].title, 'http://link1.com');
116-
assert.equal(links[0].length, '[Link 1](http://link1.com)'.length);
117-
assert.equal(links[0].offset, 'Unable to '.length);
118-
119-
assert.equal(links[1].name, 'Link 2');
120-
assert.equal(links[1].href, 'command:open.me');
121-
assert.equal(links[1].title, 'Open This');
122-
assert.equal(links[1].length, '[Link 2](command:open.me "Open This")'.length);
123-
assert.equal(links[1].offset, 'Unable to [Link 1](http://link1.com) open '.length);
124-
125-
assert.equal(links[2].name, 'Link 3');
126-
assert.equal(links[2].href, 'command:without.title');
127-
assert.equal(links[2].title, 'Click to execute command \'without.title\'');
128-
assert.equal(links[2].length, '[Link 3](command:without.title)'.length);
129-
assert.equal(links[2].offset, 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and '.length);
130-
131108
// Filter
132109
let item8 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.SILENT)!;
133110
assert.equal(item8.silent, true);
@@ -162,19 +139,19 @@ suite('Notifications', () => {
162139

163140
let item1Handle = model.addNotification(item1);
164141
assert.equal(lastNotificationEvent.item.severity, item1.severity);
165-
assert.equal(lastNotificationEvent.item.message.value, item1.message);
142+
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item1.message);
166143
assert.equal(lastNotificationEvent.index, 0);
167144
assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD);
168145

169146
let item2Handle = model.addNotification(item2);
170147
assert.equal(lastNotificationEvent.item.severity, item2.severity);
171-
assert.equal(lastNotificationEvent.item.message.value, item2.message);
148+
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2.message);
172149
assert.equal(lastNotificationEvent.index, 0);
173150
assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD);
174151

175152
model.addNotification(item3);
176153
assert.equal(lastNotificationEvent.item.severity, item3.severity);
177-
assert.equal(lastNotificationEvent.item.message.value, item3.message);
154+
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item3.message);
178155
assert.equal(lastNotificationEvent.index, 0);
179156
assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD);
180157

@@ -189,27 +166,27 @@ suite('Notifications', () => {
189166
assert.equal(called, 1);
190167
assert.equal(model.notifications.length, 2);
191168
assert.equal(lastNotificationEvent.item.severity, item1.severity);
192-
assert.equal(lastNotificationEvent.item.message.value, item1.message);
169+
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item1.message);
193170
assert.equal(lastNotificationEvent.index, 2);
194171
assert.equal(lastNotificationEvent.kind, NotificationChangeType.REMOVE);
195172

196173
model.addNotification(item2Duplicate);
197174
assert.equal(model.notifications.length, 2);
198175
assert.equal(lastNotificationEvent.item.severity, item2Duplicate.severity);
199-
assert.equal(lastNotificationEvent.item.message.value, item2Duplicate.message);
176+
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2Duplicate.message);
200177
assert.equal(lastNotificationEvent.index, 0);
201178
assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD);
202179

203180
item2Handle.close();
204181
assert.equal(model.notifications.length, 1);
205182
assert.equal(lastNotificationEvent.item.severity, item2Duplicate.severity);
206-
assert.equal(lastNotificationEvent.item.message.value, item2Duplicate.message);
183+
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2Duplicate.message);
207184
assert.equal(lastNotificationEvent.index, 0);
208185
assert.equal(lastNotificationEvent.kind, NotificationChangeType.REMOVE);
209186

210187
model.notifications[0].expand();
211188
assert.equal(lastNotificationEvent.item.severity, item3.severity);
212-
assert.equal(lastNotificationEvent.item.message.value, item3.message);
189+
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item3.message);
213190
assert.equal(lastNotificationEvent.index, 0);
214191
assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE);
215192

0 commit comments

Comments
 (0)