feat(aio): add LiveExampleComponent (#15544)
This commit is contained in:
parent
aa16ccda79
commit
861953c95c
|
@ -4,9 +4,11 @@
|
|||
|
||||
<p>No linenums at code-tabs level</p>
|
||||
<code-tabs >
|
||||
<code-pane title='TS code file' language='ts'>class {
|
||||
foo(param: string) {}
|
||||
}</code-pane>
|
||||
<code-pane title='TS code file' language='ts'>
|
||||
class {
|
||||
foo(param: string) {}
|
||||
}
|
||||
</code-pane>
|
||||
<code-pane title='HTML content file' language='html'><h1>Heading</h1></code-pane>
|
||||
<code-pane title='JSON data file' language='json' class='is-anti-pattern'>{ "key": "value" }</code-pane>
|
||||
</code-tabs>
|
||||
|
@ -14,9 +16,11 @@
|
|||
|
||||
<p>linenums=true at code-tabs level</p>
|
||||
<code-tabs linenums='true'>
|
||||
<code-pane title='TS code file' language='ts'>class {
|
||||
foo(param: string) {}
|
||||
}</code-pane>
|
||||
<code-pane title='TS code file' language='ts'>
|
||||
class {
|
||||
foo(param: string) {}
|
||||
}
|
||||
</code-pane>
|
||||
<code-pane title='HTML content file' language='html'><h1>Heading</h1></code-pane>
|
||||
<code-pane title='JSON data file' language='json' class='is-anti-pattern'>{ "key": "value" }</code-pane>
|
||||
</code-tabs>
|
||||
|
@ -24,9 +28,11 @@
|
|||
|
||||
<p>No linenums at code-tabs level; linenums=true for HTML pane</p>
|
||||
<code-tabs >
|
||||
<code-pane title='TS code file' language='ts'>class {
|
||||
foo(param: string) {}
|
||||
}</code-pane>
|
||||
<code-pane title='TS code file' language='ts'>
|
||||
class {
|
||||
foo(param: string) {}
|
||||
}
|
||||
</code-pane>
|
||||
<code-pane title='HTML content file' language='html' linenums='true'><h1>Heading</h1></code-pane>
|
||||
<code-pane title='JSON data file' language='json' class='is-anti-pattern'>{ "key": "value" }</code-pane>
|
||||
</code-tabs>
|
||||
|
@ -77,4 +83,27 @@
|
|||
</hero-details>
|
||||
</code-example>
|
||||
|
||||
<p>More text follows ...</p>
|
||||
<h2><live-example></h2>
|
||||
|
||||
<p>Plain live-example</p>
|
||||
Try this <live-example></live-example>.
|
||||
|
||||
<p>live-example with title atty</p>
|
||||
<live-example title="Good Example"></live-example>
|
||||
|
||||
<p>live-example with title body</p>
|
||||
<live-example title="Good Example">Try this great example</live-example>
|
||||
|
||||
<p>live-example with name</p>
|
||||
<live-example name="testy" title="Better Example"></live-example>
|
||||
|
||||
<p>live-example with spacey name and plnkr</p>
|
||||
<live-example name=" testy " plnkr="super-plnkr"></live-example>
|
||||
|
||||
<p>live-example with name and plnkr but no download</p>
|
||||
<live-example name="testy" plnkr="super-plnkr" noDownload></live-example>
|
||||
|
||||
<p>live-example embedded with name and plnkr</p>
|
||||
<live-example embedded name="testy" plnkr="super-plnkr"></live-example>
|
||||
|
||||
<p>More text follows ...</p>
|
||||
|
|
|
@ -17,12 +17,13 @@ import { ApiListComponent } from './api/api-list.component';
|
|||
import { CodeExampleComponent } from './code/code-example.component';
|
||||
import { CodeTabsComponent } from './code/code-tabs.component';
|
||||
import { DocTitleComponent } from './doc-title.component';
|
||||
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example/live-example.component';
|
||||
|
||||
/** Components that can be embedded in docs
|
||||
* such as CodeExampleComponent, LiveExampleComponent,...
|
||||
*/
|
||||
export const embeddedComponents: any[] = [
|
||||
ApiListComponent, CodeExampleComponent, DocTitleComponent, CodeTabsComponent
|
||||
ApiListComponent, CodeExampleComponent, DocTitleComponent, CodeTabsComponent, LiveExampleComponent
|
||||
];
|
||||
|
||||
/** Injectable class w/ property returning components that can be embedded in docs */
|
||||
|
@ -34,7 +35,8 @@ export class EmbeddedComponents {
|
|||
imports: [ CommonModule, MdTabsModule ],
|
||||
declarations: [
|
||||
embeddedComponents,
|
||||
CodeComponent
|
||||
CodeComponent,
|
||||
EmbeddedPlunkerComponent
|
||||
],
|
||||
providers: [
|
||||
EmbeddedComponents,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<span *ngIf="!isEmbedded">
|
||||
<a [href]="plnkr" target="_blank" title="{{title}}">{{title}}</a>
|
||||
<span *ngIf="enableDownload">
|
||||
/ <a [href]="zip" download title="Download example">download example</a>
|
||||
</span>
|
||||
</span>
|
||||
<div *ngIf="isEmbedded">
|
||||
<div *ngIf="showEmbedded" title="{{title}}">
|
||||
<aio-embedded-plunker [src]="plnkr"></aio-embedded-plunker>
|
||||
</div>
|
||||
<img *ngIf="!showEmbedded" (click)="toggleEmbedded()" [src]="plnkrImg" width="100%" height="100%" alt="{{title}}">
|
||||
<p *ngIf="enableDownload">
|
||||
You can also <a [href]="zip" download title="Download example">download this example</a>.
|
||||
</p>
|
||||
</div>
|
|
@ -0,0 +1,265 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Component, DebugElement, ElementRef } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example.component';
|
||||
|
||||
const defaultTestPath = '/test';
|
||||
|
||||
describe('LiveExampleComponent', () => {
|
||||
let hostComponent: HostComponent;
|
||||
let liveExampleDe: DebugElement;
|
||||
let liveExampleComponent: LiveExampleComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
let testPath: string;
|
||||
let liveExampleContent: string;
|
||||
|
||||
//////// test helpers ////////
|
||||
|
||||
@Component({
|
||||
selector: 'aio-host-comp',
|
||||
template: `<live-example></live-example>`
|
||||
})
|
||||
class HostComponent { }
|
||||
|
||||
class TestLocation {
|
||||
path() { return testPath; }
|
||||
}
|
||||
|
||||
function setHostTemplate(template: string) {
|
||||
TestBed.overrideComponent(HostComponent, {set: {template}});
|
||||
}
|
||||
|
||||
function testComponent(testFn: () => void) {
|
||||
return TestBed
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
hostComponent = fixture.componentInstance;
|
||||
liveExampleDe = fixture.debugElement.children[0];
|
||||
liveExampleComponent = liveExampleDe.componentInstance;
|
||||
|
||||
// Copy the LiveExample's innerHTML (content)
|
||||
// into the `liveExampleContent` property as the DocViewer does
|
||||
liveExampleDe.nativeElement.liveExampleContent = liveExampleContent;
|
||||
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.then(testFn);
|
||||
}
|
||||
|
||||
//////// tests ////////
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HostComponent, LiveExampleComponent, EmbeddedPlunkerComponent ],
|
||||
providers: [ {provide: Location, useClass: TestLocation }]
|
||||
})
|
||||
// Disable the <iframe> within the EmbeddedPlunkerComponent
|
||||
.overrideComponent(EmbeddedPlunkerComponent, {set: {template: 'NO IFRAME'}});
|
||||
|
||||
testPath = defaultTestPath;
|
||||
liveExampleContent = undefined;
|
||||
});
|
||||
|
||||
describe('when not embedded', () => {
|
||||
|
||||
function getAnchors() {
|
||||
return liveExampleDe.queryAll(By.css('a')).map(de => de.nativeElement as HTMLAnchorElement);
|
||||
}
|
||||
function getHrefs() { return getAnchors().map(a => a.href); }
|
||||
function getLiveExampleAnchor() { return getAnchors()[0]; }
|
||||
function getDownloadAnchor() { return getAnchors()[1]; }
|
||||
|
||||
it('should create LiveExampleComponent', async(() => {
|
||||
testComponent(() => {
|
||||
expect(liveExampleComponent).toBeTruthy('LiveExampleComponent');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have expected plunker & download hrefs', async(() => {
|
||||
testPath = '/tutorial/toh-pt1';
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain('/toh-pt1/eplnkr.html');
|
||||
expect(hrefs[1]).toContain('/toh-pt1/toh-pt1.zip');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have expected plunker & download hrefs even when path has # frag', async(() => {
|
||||
testPath = '/tutorial/toh-pt1#somewhere';
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain('/toh-pt1/eplnkr.html');
|
||||
expect(hrefs[1]).toContain('/toh-pt1/toh-pt1.zip');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have expected plunker & download hrefs even when path has ? params', async(() => {
|
||||
testPath = '/tutorial/toh-pt1?foo=1&bar="bar"';
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain('/toh-pt1/eplnkr.html');
|
||||
expect(hrefs[1]).toContain('/toh-pt1/toh-pt1.zip');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have expected flat-style plunker when has `flat-style`', async(() => {
|
||||
testPath = '/tutorial/toh-pt1';
|
||||
setHostTemplate('<live-example flat-style></live-example>');
|
||||
testComponent(() => {
|
||||
// The file should be "plnkr.html", not "eplnkr.html"
|
||||
expect(getLiveExampleAnchor().href).toContain('/plnkr.html');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have expected plunker & download hrefs when has example directory (name)', async(() => {
|
||||
testPath = '/guide/somewhere';
|
||||
setHostTemplate('<live-example name="toh-1"></live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain('/toh-1/eplnkr.html');
|
||||
expect(hrefs[1]).toContain('/toh-1/toh-1.zip');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have expected plunker & download hrefs when has `plnkr`', async(() => {
|
||||
testPath = '/testing';
|
||||
setHostTemplate('<live-example plnkr="app-specs"></live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain('/testing/app-specs.eplnkr.html');
|
||||
expect(hrefs[1]).toContain('/testing/app-specs.testing.zip');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have expected plunker & download hrefs when has `name` & `plnkr`', async(() => {
|
||||
testPath = '/guide/somewhere';
|
||||
setHostTemplate('<live-example name="testing" plnkr="app-specs"></live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain('/testing/app-specs.eplnkr.html');
|
||||
expect(hrefs[1]).toContain('/testing/app-specs.testing.zip');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not have a download link when `noDownload` atty present', async(() => {
|
||||
setHostTemplate('<live-example noDownload></live-example>');
|
||||
testComponent(() => {
|
||||
expect(getAnchors().length).toBe(1, 'only the live-example anchor');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have default title when no title attribute or content', async(() => {
|
||||
setHostTemplate('<live-example></live-example>');
|
||||
testComponent(() => {
|
||||
const expectedTitle = 'live example';
|
||||
const anchor = getLiveExampleAnchor();
|
||||
expect(anchor.innerText).toBe(expectedTitle, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add title when set `title` attribute', async(() => {
|
||||
const expectedTitle = 'Great Example';
|
||||
setHostTemplate(`<live-example title="${expectedTitle}"></live-example>`);
|
||||
testComponent(() => {
|
||||
const anchor = getLiveExampleAnchor();
|
||||
expect(anchor.innerText).toBe(expectedTitle, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add title from <live-example> body', async(() => {
|
||||
liveExampleContent = 'The Greatest Example';
|
||||
setHostTemplate('<live-example title="ignore this title"></live-example>');
|
||||
testComponent(() => {
|
||||
const anchor = getLiveExampleAnchor();
|
||||
expect(anchor.innerText).toBe(liveExampleContent, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(liveExampleContent, 'title');
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('when embedded', () => {
|
||||
|
||||
function getDownloadAnchor() {
|
||||
const anchor = liveExampleDe.query(By.css('p > a'));
|
||||
return anchor && anchor.nativeElement as HTMLAnchorElement;
|
||||
}
|
||||
|
||||
function getEmbeddedPlunkerComponent() {
|
||||
const compDe = liveExampleDe.query(By.directive(EmbeddedPlunkerComponent));
|
||||
return compDe && compDe.componentInstance as EmbeddedPlunkerComponent;
|
||||
}
|
||||
|
||||
function getImg() {
|
||||
const img = liveExampleDe.query(By.css('img'));
|
||||
return img && img.nativeElement as HTMLImageElement;
|
||||
}
|
||||
|
||||
describe('before click', () => {
|
||||
|
||||
it('should have hidden, embedded plunker', async(() => {
|
||||
setHostTemplate('<live-example embedded></live-example>');
|
||||
testComponent(() => {
|
||||
expect(liveExampleComponent.isEmbedded).toBe(true, 'component.isEmbedded');
|
||||
expect(liveExampleComponent.showEmbedded).toBe(false, 'component.showEmbedded');
|
||||
expect(getEmbeddedPlunkerComponent()).toBeNull('no EmbeddedPlunkerComponent');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have default plunker placeholder image', async(() => {
|
||||
setHostTemplate('<live-example embedded></live-example>');
|
||||
testComponent(() => {
|
||||
expect(getImg().src).toContain('plunker/placeholder.png');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have specified plunker placeholder image', async(() => {
|
||||
const expectedSrc = 'example/demo.png';
|
||||
setHostTemplate(`<live-example embedded img="${expectedSrc}"></live-example>`);
|
||||
testComponent(() => {
|
||||
expect(getImg().src).toContain(expectedSrc);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should have download paragraph with expected anchor href', async(() => {
|
||||
testPath = '/tutorial/toh-pt1';
|
||||
setHostTemplate('<live-example embedded></live-example>');
|
||||
testComponent(() => {
|
||||
expect(getDownloadAnchor().href).toContain('/toh-pt1/toh-pt1.zip');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not have download paragraph when has `nodownload`', async(() => {
|
||||
testPath = '/tutorial/toh-pt1';
|
||||
setHostTemplate('<live-example embedded nodownload></live-example>');
|
||||
testComponent(() => {
|
||||
expect(getDownloadAnchor()).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('after click', () => {
|
||||
|
||||
function clickImg() {
|
||||
getImg().click();
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
it('should show plunker in the page', async(() => {
|
||||
setHostTemplate('<live-example embedded></live-example>');
|
||||
testComponent(() => {
|
||||
clickImg();
|
||||
expect(liveExampleComponent.isEmbedded).toBe(true, 'component.isEmbedded');
|
||||
expect(liveExampleComponent.showEmbedded).toBe(true, 'component.showEmbedded');
|
||||
expect(getEmbeddedPlunkerComponent()).toBeDefined('has EmbeddedPlunkerComponent');
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,166 @@
|
|||
/* tslint:disable component-selector */
|
||||
import { Component, ElementRef, Input, OnInit, AfterViewInit, ViewChild } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
const defaultPlnkrImg = 'plunker/placeholder.png';
|
||||
const imageBase = 'assets/images/';
|
||||
const liveExampleBase = 'content/live-examples/';
|
||||
const zipBase = 'content/zips/';
|
||||
|
||||
/**
|
||||
* Angular.io Live Example Embedded Component
|
||||
*
|
||||
* Renders a link to a live/host example of the doc page.
|
||||
*
|
||||
* All attributes and the text content are optional
|
||||
*
|
||||
* Usage:
|
||||
* <live-example
|
||||
* [name="..."] // name of the example directory
|
||||
* [plnkr="...""] // name of the plunker file (becomes part of zip file name as well)
|
||||
* [embedded] // embed the plunker in the doc page, else display in new browser tab (external)
|
||||
* [img="..."] // image to display if embedded in doc page
|
||||
* [embedded-style] // show external plnkr in embedded style
|
||||
* [flat-style] // show external plnkr in flat (original) style
|
||||
* [noDownload] // no downloadable zip option
|
||||
* [title="..."]> // text for live example link and tooltip
|
||||
* text // higher precedence way to specify text for live example link and tooltip
|
||||
* </live-example>
|
||||
* Example:
|
||||
* <p>Run <live-example>Try the live example</live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/plnkr.html
|
||||
*
|
||||
* <p>Run <live-example name="toh-1">this example</live-example></p>.
|
||||
* // ~/resources/live-examples/toh-1/plnkr.html
|
||||
*
|
||||
* // Link to the default plunker in the toh-1 sample
|
||||
* // The title overrides default ("live example") with "Tour of Heroes - Part 1"
|
||||
* <p>Run <live-example name="toh-1" title="Tour of Heroes - Part 1"></live-example></p>.
|
||||
* // ~/resources/live-examples/toh-1/plnkr.html
|
||||
*
|
||||
* <p>Run <live-example plnkr="minimal"></live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/minimal.plnkr.html
|
||||
*
|
||||
* // Embed the current page's default plunker
|
||||
* // Text within tag is "live example"
|
||||
* // No title (no tooltip)
|
||||
* <live-example embedded title=""></live-example>
|
||||
* // ~/resources/live-examples/{page}/eplnkr.html
|
||||
*
|
||||
* // Links to a *new* browser tab as an embedded style plunker editor
|
||||
* <live-example embedded-style>this example</live-example>
|
||||
* // ~/resources/live-examples/{page}/eplnkr.html
|
||||
*
|
||||
* // Links to a *new* browser tab in the flat (original editor) style plunker editor
|
||||
* <live-example flat-style>this example</live-example>
|
||||
* // ~/resources/live-examples/{page}/plnkr.html
|
||||
*
|
||||
* // Displays within the document page as an embedded style plunker editor
|
||||
* <live-example name="toh-1" embedded plnkr="minimal" img="toh>Tour of Heroes - Part 1</live-example>
|
||||
* // ~/resources/live-examples/toh-1/minimal.eplnkr.html
|
||||
*/
|
||||
@Component({
|
||||
selector: 'live-example',
|
||||
templateUrl: 'live-example.component.html'
|
||||
})
|
||||
export class LiveExampleComponent implements OnInit {
|
||||
|
||||
enableDownload = true;
|
||||
isEmbedded = false;
|
||||
plnkr: string;
|
||||
plnkrImg: string;
|
||||
showEmbedded = false;
|
||||
title: string;
|
||||
zip: string;
|
||||
|
||||
constructor(private elementRef: ElementRef, location: Location ) {
|
||||
const attrs = this.getAttrs();
|
||||
|
||||
let exampleDir = attrs.name;
|
||||
if (!exampleDir) {
|
||||
// take last segment, excluding hash fragment and query params
|
||||
exampleDir = location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/)[0];
|
||||
}
|
||||
exampleDir = exampleDir.trim();
|
||||
|
||||
let plnkrStyle = 'eplnkr';
|
||||
|
||||
this.isEmbedded = boolFromAtty(attrs.embedded);
|
||||
if (!this.isEmbedded) {
|
||||
// Not embedded in doc page; determine if is embedded- or flat-style in another browser tab.
|
||||
// External plunker is embedded style by default.
|
||||
// Make flat style with `flat-style` or `embedded-style="false`
|
||||
// Must support aliases
|
||||
const flatStyle = getAttrValue(['flat-style', 'flatstyle', 'flatStyle']);
|
||||
const isFlatStyle = boolFromAtty(flatStyle);
|
||||
const embeddedStyle = getAttrValue(['embedded-style', 'embeddedstyle', 'embeddedStyle']);
|
||||
const isEmbeddedStyle = boolFromAtty(embeddedStyle, !isFlatStyle);
|
||||
|
||||
plnkrStyle = isEmbeddedStyle ? 'eplnkr' : 'plnkr';
|
||||
}
|
||||
|
||||
const plnkrName = attrs.plnkr ? attrs.plnkr.trim() + '.' : '';
|
||||
|
||||
this.plnkr = `${liveExampleBase}${exampleDir}/${plnkrName}${plnkrStyle}.html`;
|
||||
this.zip = `${zipBase}${exampleDir}/${plnkrName}${exampleDir}.zip`;
|
||||
|
||||
const noDownload = getAttrValue(['noDownload', 'nodownload']); // noDownload aliases
|
||||
this.enableDownload = !boolFromAtty(noDownload);
|
||||
this.plnkrImg = imageBase + (attrs.img || defaultPlnkrImg);
|
||||
|
||||
this.title = attrs.title || '';
|
||||
|
||||
function getAttrValue(atty: string | string[]) {
|
||||
return attrs[typeof atty === 'string' ? atty : atty.find(a => attrs[a] !== undefined)];
|
||||
}
|
||||
}
|
||||
|
||||
getAttrs(): any {
|
||||
const attrs = this.elementRef.nativeElement.attributes;
|
||||
const attrMap = {};
|
||||
Object.keys(attrs).forEach(key => attrMap[attrs[key].name] = attrs[key].value);
|
||||
return attrMap;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// The `liveExampleContent` property is set by the DocViewer when it builds this component.
|
||||
// It is the original innerHTML of the host element.
|
||||
// Angular will sanitize this title when displayed so should be plain text.
|
||||
const title = this.elementRef.nativeElement.liveExampleContent;
|
||||
this.title = (title || this.title || 'live example').trim();
|
||||
}
|
||||
|
||||
toggleEmbedded () { this.showEmbedded = !this.showEmbedded; }
|
||||
}
|
||||
|
||||
function boolFromAtty(atty: string , def: boolean = false) {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
return atty == undefined ? def : atty.trim() !== 'false';
|
||||
}
|
||||
|
||||
///// EmbeddedPlunkerComponent ///
|
||||
/**
|
||||
* Hides the <iframe> so we can test LiveExampleComponent without actually triggering
|
||||
* a call to plunker to load the iframe
|
||||
*/
|
||||
@Component({
|
||||
selector: 'aio-embedded-plunker',
|
||||
template: `<iframe #iframe frameborder="0" width="100%" height="100%"></iframe>`,
|
||||
styles: [ 'iframe { min-height: 400px; }']
|
||||
})
|
||||
export class EmbeddedPlunkerComponent implements AfterViewInit {
|
||||
@Input() src: string;
|
||||
|
||||
@ViewChild('iframe') iframe: ElementRef;
|
||||
|
||||
ngAfterViewInit() {
|
||||
// DEVELOPMENT TESTING ONLY
|
||||
// this.src = 'https://angular.io/resources/live-examples/quickstart/ts/eplnkr.html';
|
||||
|
||||
if (this.iframe) {
|
||||
// security: the `src` is always authored by the documentation team
|
||||
// and is considered to be safe
|
||||
this.iframe.nativeElement.src = this.src;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -90,8 +90,13 @@ export class LocationService {
|
|||
return true;
|
||||
}
|
||||
|
||||
// check for external link
|
||||
// don't navigate if external link or zip
|
||||
const { pathname, search, hash } = anchor;
|
||||
|
||||
if (anchor.getAttribute('download') != null) {
|
||||
return true; // let the download happen
|
||||
}
|
||||
|
||||
const relativeUrl = pathname + search + hash;
|
||||
this.urlParser.href = relativeUrl;
|
||||
if (anchor.href !== this.urlParser.href) {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.4 KiB |
Loading…
Reference in New Issue