diff --git a/src/sql/workbench/browser/modelComponents/text.component.ts b/src/sql/workbench/browser/modelComponents/text.component.ts index ced557dde4..04bd3299a8 100644 --- a/src/sql/workbench/browser/modelComponents/text.component.ts +++ b/src/sql/workbench/browser/modelComponents/text.component.ts @@ -6,39 +6,48 @@ import 'vs/css!./media/text'; import { Component, Input, Inject, ChangeDetectorRef, forwardRef, - OnDestroy, AfterViewInit, ElementRef, SecurityContext + OnDestroy, AfterViewInit, ElementRef, ViewChild } from '@angular/core'; import * as azdata from 'azdata'; -import { SafeHtml, DomSanitizer } from '@angular/platform-browser'; import { TitledComponent } from 'sql/workbench/browser/modelComponents/titledComponent'; import { IComponentDescriptor, IComponent, IModelStore } from 'sql/platform/dashboard/browser/interfaces'; -import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { textLinkForeground, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Link } from 'vs/platform/opener/browser/link'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as DOM from 'vs/base/browser/dom'; +import { ILogService } from 'vs/platform/log/common/log'; +import { attachLinkStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; @Component({ selector: 'modelview-text', template: `
-

-

*

+

+ +

*

-

+

+ +

` }) export default class TextComponent extends TitledComponent implements IComponent, OnDestroy, AfterViewInit { @Input() descriptor: IComponentDescriptor; @Input() modelStore: IModelStore; + @ViewChild('textContainer', { read: ElementRef }) textContainer: ElementRef; constructor( @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ElementRef)) el: ElementRef, - @Inject(forwardRef(() => DomSanitizer)) private _domSanitizer: DomSanitizer) { + @Inject(IInstantiationService) private instantiationService: IInstantiationService, + @Inject(ILogService) private logService: ILogService, + @Inject(IThemeService) private themeService: IThemeService) { super(changeRef, el); } @@ -47,6 +56,7 @@ export default class TextComponent extends TitledComponent implements IComponent } ngAfterViewInit(): void { + this.updateText(); } ngOnDestroy(): void { @@ -84,41 +94,55 @@ export default class TextComponent extends TitledComponent implements IComponent return this.getPropertyOrDefault((props) => props.requiredIndicator, false); } - public getValue(): SafeHtml { - let links = this.getPropertyOrDefault((props) => props.links, []); - let text = this._domSanitizer.sanitize(SecurityContext.HTML, this.value); - if (links.length !== 0) { - for (let i: number = 0; i < links.length; i++) { - let link = links[i]; - let linkTag = `${this._domSanitizer.sanitize(SecurityContext.HTML, link.text)}`; - text = text.replace(`{${i}}`, linkTag); + public setProperties(properties: { [key: string]: any; }): void { + super.setProperties(properties); + this.updateText(); + this._changeRef.detectChanges(); + } + + public updateText(): void { + DOM.clearNode((this.textContainer.nativeElement)); + const links = this.getPropertyOrDefault((props) => props.links, []); + // The text may contain link placeholders so go through and create those and insert them as needed now + let text = this.value; + for (let i: number = 0; i < links.length; i++) { + const placeholderIndex = text.indexOf(`{${i}}`); + if (placeholderIndex < 0) { + this.logService.warn(`Could not find placeholder text {${i}} in text ${this.value}`); + // Just continue on so we at least show the rest of the text if just one was missed or something + continue; } + + // First insert any text from the start of the current string fragment up to the placeholder + let curText = text.slice(0, placeholderIndex); + if (curText) { + const textSpan = DOM.$('span'); + textSpan.innerText = text.slice(0, placeholderIndex); + (this.textContainer.nativeElement).appendChild(textSpan); + } + + // Now insert the link element + const link = links[i]; + const linkElement = this._register(this.instantiationService.createInstance(Link, { + label: link.text, + href: link.url + })); + this._register(attachLinkStyler(linkElement, this.themeService)); + (this.textContainer.nativeElement).appendChild(linkElement.el); + + // And finally update the text to remove the text up through the placeholder we just added + text = text.slice(placeholderIndex + 3); + } + + // If we have any text left over now insert that in directly + if (text) { + const textSpan = DOM.$('span'); + textSpan.innerText = text; + (this.textContainer.nativeElement).appendChild(textSpan); } - return text; } public get showDiv(): boolean { return this.requiredIndicator || !!this.description; } } - -registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { - const linkForeground = theme.getColor(textLinkForeground); - if (linkForeground) { - collector.addRule(` - a.modelview-text-link:link, - a.modelview-text-link:visited { - color: ${linkForeground}; - } - `); - } - - const activeForeground = theme.getColor(textLinkActiveForeground); - if (activeForeground) { - collector.addRule(` - a.modelview-text-link:hover { - color: ${activeForeground}; - } - `); - } -});