diff --git a/aio/src/app/custom-elements/live-example/live-example.component.html b/aio/src/app/custom-elements/live-example/live-example.component.html index a1537bfdc4..6c21e3b6fd 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.html +++ b/aio/src/app/custom-elements/live-example/live-example.component.html @@ -2,7 +2,6 @@ - {{title}} (not available on this device)
@@ -17,7 +16,7 @@ {{title}} - / download example + / download example diff --git a/aio/src/app/custom-elements/live-example/live-example.component.spec.ts b/aio/src/app/custom-elements/live-example/live-example.component.spec.ts index ea6209ebba..8974c4a5d3 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.spec.ts +++ b/aio/src/app/custom-elements/live-example/live-example.component.spec.ts @@ -43,10 +43,6 @@ describe('LiveExampleComponent', () => { // Trigger `ngAfterContentInit()`. fixture.detectChanges(); - // Ensure wide-screen by default. - liveExampleComponent.onResize(1033); - fixture.detectChanges(); - testFn(); } @@ -100,15 +96,6 @@ describe('LiveExampleComponent', () => { }); }); - it('should have expected flat-style stackblitz when has `flat-style`', () => { - testPath = '/tutorial/toh-pt1'; - setHostTemplate(''); - testComponent(() => { - // The file should be "stackblitz.html", not "stackblitz.html" - expect(getLiveExampleAnchor().href).toContain('/stackblitz.html'); - }); - }); - it('should have expected stackblitz & download hrefs when has example directory (name)', () => { testPath = '/guide/somewhere'; setHostTemplate(''); @@ -147,15 +134,7 @@ describe('LiveExampleComponent', () => { }); }); - it('should be flat style when flat-style requested', () => { - setHostTemplate(''); - testComponent(() => { - const hrefs = getHrefs(); - expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html'); - }); - }); - - it('should not have a download link when `noDownload` atty present', () => { + it('should not have a download link when `noDownload` attr present', () => { setHostTemplate(''); testComponent(() => { const hrefs = getHrefs(); @@ -164,7 +143,7 @@ describe('LiveExampleComponent', () => { }); }); - it('should only have a download link when `downloadOnly` atty present', () => { + it('should only have a download link when `downloadOnly` attr present', () => { setHostTemplate('download this'); testComponent(() => { const hrefs = getHrefs(); @@ -248,27 +227,4 @@ describe('LiveExampleComponent', () => { }); }); }); - - describe('when narrow display (mobile)', () => { - - it('should be embedded style when no style defined', () => { - setHostTemplate(''); - testComponent(() => { - liveExampleComponent.onResize(600); // narrow - fixture.detectChanges(); - const hrefs = getHrefs(); - expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html'); - }); - }); - - it('should be embedded style even when flat-style requested', () => { - setHostTemplate(''); - testComponent(() => { - liveExampleComponent.onResize(600); // narrow - fixture.detectChanges(); - const hrefs = getHrefs(); - expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html'); - }); - }); - }); }); diff --git a/aio/src/app/custom-elements/live-example/live-example.component.ts b/aio/src/app/custom-elements/live-example/live-example.component.ts index cd4efedf58..bc52ef32d3 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.ts +++ b/aio/src/app/custom-elements/live-example/live-example.component.ts @@ -1,131 +1,131 @@ /* tslint:disable component-selector */ -import { AfterContentInit, AfterViewInit, Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core'; +import { AfterContentInit, AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'; import { Location } from '@angular/common'; import { CONTENT_URL_PREFIX } from 'app/documents/document.service'; +import { AttrMap, boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils'; -import { boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils'; -const liveExampleBase = CONTENT_URL_PREFIX + 'live-examples/'; -const zipBase = CONTENT_URL_PREFIX + 'zips/'; +const LIVE_EXAMPLE_BASE = CONTENT_URL_PREFIX + 'live-examples/'; +const ZIP_BASE = CONTENT_URL_PREFIX + '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: -* // text for live example link and tooltip -* text // higher precedence way to specify text for live example link and tooltip -* -* Example: -*

Run Try the live example

. -* // ~/resources/live-examples/{page}/stackblitz.json -* -*

Run this example

. -* // ~/resources/live-examples/toh-pt1/stackblitz.json -* -* // Link to the default stackblitz in the toh-pt1 sample -* // The title overrides default ("live example") with "Tour of Heroes - Part 1" -*

Run

. -* // ~/resources/live-examples/toh-pt1/stackblitz.json -* -*

Run

. -* // ~/resources/live-examples/{page}/minimal.stackblitz.json -* -* // Embed the current page's default stackblitz -* // Text within tag is "live example" -* // No title (no tooltip) -* -* // ~/resources/live-examples/{page}/stackblitz.json -* -* // Displays within the document page as an embedded style stackblitz editor -* Tour of Heroes - Part 1 -* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json -*/ + * 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: + * // text for live example link and tooltip + * text // higher precedence way to specify text for live example link and tooltip + * + * Example: + *

Run Try the live example

. + * // ~/resources/live-examples/{page}/stackblitz.json + * + *

Run this example

. + * // ~/resources/live-examples/toh-pt1/stackblitz.json + * + * // Link to the default stackblitz in the toh-pt1 sample + * // The title overrides default ("live example") with "Tour of Heroes - Part 1" + *

Run

. + * // ~/resources/live-examples/toh-pt1/stackblitz.json + * + *

Run

. + * // ~/resources/live-examples/{page}/minimal.stackblitz.json + * + * // Embed the current page's default stackblitz + * // Text within tag is "live example" + * // No title (no tooltip) + * + * // ~/resources/live-examples/{page}/stackblitz.json + * + * // Displays within the document page as an embedded style stackblitz editor + * Tour of Heroes - Part 1 + * // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json + */ @Component({ selector: 'live-example', templateUrl: 'live-example.component.html' }) export class LiveExampleComponent implements AfterContentInit { - // Will force to embedded-style when viewport width is narrow - // "narrow" value was picked based on phone dimensions from http://screensiz.es/phone - readonly narrowWidth = 1000; - - attrs: any; - enableDownload = true; - exampleDir: string; - isEmbedded = false; - mode = 'disabled'; - stackblitz: string; - stackblitzName: string; + readonly mode: 'default' | 'embedded' | 'downloadOnly'; + readonly enableDownload: boolean; + readonly stackblitz: string; + readonly zip: string; title: string; - zip: string; - zipName: string; @ViewChild('content') private content: ElementRef; - constructor( - private elementRef: ElementRef, - location: Location ) { + constructor(elementRef: ElementRef, location: Location) { + const attrs = getAttrs(elementRef); + const exampleDir = this.getExampleDir(attrs, location.path(false)); + const stackblitzName = this.getStackblitzName(attrs); - const attrs = this.attrs = getAttrs(this.elementRef); - let exampleDir = attrs.name; - if (!exampleDir) { - // take last segment, excluding hash fragment and query params - exampleDir = (location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/) || [])[0]; - } - this.exampleDir = exampleDir.trim(); - this.zipName = exampleDir.indexOf('/') === -1 ? this.exampleDir : exampleDir.split('/')[0]; - this.stackblitzName = attrs.stackblitz ? attrs.stackblitz.trim() + '.' : ''; - this.zip = `${zipBase}${exampleDir}/${this.stackblitzName}${this.zipName}.zip`; - - this.enableDownload = !boolFromValue(getAttrValue(attrs, 'nodownload')); - - if (boolFromValue(getAttrValue(attrs, 'downloadonly'))) { - this.mode = 'downloadOnly'; - } - } - - calcStackblitzLink(width: number) { - - const attrs = this.attrs; - const exampleDir = this.exampleDir; - let urlQuery = ''; - - this.mode = 'default'; // display in another browser tab by default - - this.isEmbedded = boolFromValue(attrs.embedded); - - if (this.isEmbedded) { - this.mode = 'embedded'; // display embedded in the doc - urlQuery = '?ctl=1'; - } - - this.stackblitz = `${liveExampleBase}${exampleDir}/${this.stackblitzName}stackblitz.html${urlQuery}`; + this.mode = this.getMode(attrs); + this.enableDownload = this.getEnableDownload(attrs); + this.stackblitz = this.getStackblitz(exampleDir, stackblitzName, this.mode === 'embedded'); + this.zip = this.getZip(exampleDir, stackblitzName); + this.title = this.getTitle(attrs); } ngAfterContentInit() { - // Angular will sanitize this title when displayed so should be plain text. - const title = this.content.nativeElement.textContent; - this.title = (title || this.attrs.title || 'live example').trim(); - this.onResize(window.innerWidth); + // Angular will sanitize this title when displayed, so it should be plain text. + const textContent = this.content.nativeElement.textContent.trim(); + if (textContent) { + this.title = textContent; + } } - @HostListener('window:resize', ['$event.target.innerWidth']) - onResize(width: number) { - if (this.mode !== 'downloadOnly') { - this.calcStackblitzLink(width); + private getEnableDownload(attrs: AttrMap) { + const downloadDisabled = boolFromValue(getAttrValue(attrs, 'noDownload')); + return !downloadDisabled; + } + + private getExampleDir(attrs: AttrMap, path: string) { + let exampleDir = getAttrValue(attrs, 'name'); + if (!exampleDir) { + // Take the last path segment, excluding query params and hash fragment. + const match = path.match(/[^/?#]+(?=\/?(?:\?|#|$))/); + exampleDir = match ? match[0] : 'index'; } + return exampleDir.trim(); + } + + private getMode(this: LiveExampleComponent, attrs: AttrMap): typeof this.mode { + const downloadOnly = boolFromValue(getAttrValue(attrs, 'downloadOnly')); + const isEmbedded = boolFromValue(getAttrValue(attrs, 'embedded')); + + return downloadOnly ? 'downloadOnly' + : isEmbedded ? 'embedded' : + 'default'; + } + + private getStackblitz(exampleDir: string, stackblitzName: string, isEmbedded: boolean) { + const urlQuery = isEmbedded ? '?ctl=1' : ''; + return `${LIVE_EXAMPLE_BASE}${exampleDir}/${stackblitzName}stackblitz.html${urlQuery}`; + } + + private getStackblitzName(attrs: AttrMap) { + const attrValue = (getAttrValue(attrs, 'stackblitz') || '').trim(); + return attrValue && `${attrValue}.`; + } + + private getTitle(attrs: AttrMap) { + return (getAttrValue(attrs, 'title') || 'live example').trim(); + } + + private getZip(exampleDir: string, stackblitzName: string) { + const zipName = exampleDir.split('/')[0]; + return `${ZIP_BASE}${exampleDir}/${stackblitzName}${zipName}.zip`; } } @@ -137,7 +137,7 @@ export class LiveExampleComponent implements AfterContentInit { @Component({ selector: 'aio-embedded-stackblitz', template: ``, - styles: [ 'iframe { min-height: 400px; }'] + styles: [ 'iframe { min-height: 400px; }' ] }) export class EmbeddedStackblitzComponent implements AfterViewInit { @Input() src: string;