Skip to content

Commit

Permalink
feat: add button to copy link to example (#922)
Browse files Browse the repository at this point in the history
Adds the ability to copy an anchor link to a specific example. Has a bit of extra logic
to ensure that the browser actually scrolls the element into view.

Fixes #21884.
  • Loading branch information
crisbeto authored Feb 15, 2021
1 parent 4e67c59 commit 5c5903e
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
<div class="docs-example-viewer-title" *ngIf="view !== 'snippet'">
<div class="docs-example-viewer-title-spacer">{{exampleData?.title}}</div>

<button mat-icon-button type="button" (click)="toggleCompactView()" [matTooltip]="'View snippet only'"
<button
mat-icon-button
type="button"
[attr.aria-label]="'Copy link to ' + exampleData?.title + ' example to the clipboard'"
matTooltip="Copy link to example"
(click)="_copyLink()">
<mat-icon>link</mat-icon>
</button>

<button mat-icon-button type="button" (click)="toggleCompactView()" matTooltip="View snippet only"
aria-label="View less" *ngIf="showCompactToggle">
<mat-icon>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" focusable="false">
Expand All @@ -13,7 +22,7 @@
</button>

<button mat-icon-button type="button" (click)="toggleSourceView()"
[matTooltip]="view==='demo' ? 'View code' : 'Hide code'" aria-label="View source">
[matTooltip]="view === 'demo' ? 'View code' : 'Hide code'" aria-label="View source">
<mat-icon>code</mat-icon>
</button>

Expand All @@ -25,7 +34,7 @@
<mat-tab *ngFor="let tabName of _getExampleTabNames()" [label]="tabName">
<div class="button-bar">
<button mat-icon-button type="button" (click)="copySource(snippet.toArray()[selectedTab].viewer.textContent)"
class="docs-example-source-copy docs-example-button" [matTooltip]="'Copy example source'"
class="docs-example-source-copy docs-example-button" matTooltip="Copy example source"
title="Copy example source" aria-label="Copy example source to clipboard">
<mat-icon>content_copy</mat-icon>
</button>
Expand All @@ -38,12 +47,12 @@
<div class="docs-example-viewer-source-compact" *ngIf="view === 'snippet'">
<div class="button-bar">
<button mat-icon-button type="button" (click)="copySource(snippet.first.viewer.textContent)"
class="docs-example-source-copy docs-example-button" [matTooltip]="'Copy snippet'"
class="docs-example-source-copy docs-example-button" matTooltip="Copy snippet"
title="Copy example source" aria-label="Copy example source to clipboard">
<mat-icon>content_copy</mat-icon>
</button>
<button mat-icon-button type="button" (click)="toggleCompactView()"
class="docs-example-compact-toggle docs-example-button" [matTooltip]="'View full example'"
class="docs-example-compact-toggle docs-example-button" matTooltip="View full example"
aria-label="View less">
<mat-icon>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" focusable="false">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {
Component,
ElementRef,
HostBinding,
Input,
NgModuleFactory, OnInit, QueryList,
NgModuleFactory,
OnInit,
QueryList,
Type,
ViewChildren,
ɵNgModuleFactory
Expand Down Expand Up @@ -53,6 +57,7 @@ export class ExampleViewer implements OnInit {
@Input() showCompactToggle = false;

/** String key of the currently displayed example. */
@HostBinding('attr.id')
@Input()
get example() { return this._example; }
set example(exampleName: string) {
Expand All @@ -75,7 +80,8 @@ export class ExampleViewer implements OnInit {

constructor(
private readonly snackbar: MatSnackBar,
private readonly clipboard: Clipboard) {}
private readonly clipboard: Clipboard,
private readonly elementRef: ElementRef<HTMLElement>) {}

ngOnInit() {
if (this.file) {
Expand Down Expand Up @@ -152,6 +158,17 @@ export class ExampleViewer implements OnInit {
});
}

_copyLink() {
// Reconstruct the URL using `origin + pathname` so we drop any pre-existing hash.
const fullUrl = location.origin + location.pathname + '#' + this._example;

if (this.clipboard.copy(fullUrl)) {
this.snackbar.open('Link copied', '', {duration: 2500});
} else {
this.snackbar.open('Link copy failed. Please try again!', '', {duration: 2500});
}
}

/** Loads the component and module factory for the currently selected example. */
private async _loadExampleComponent() {
const {componentName, module} = EXAMPLE_COMPONENTS[this._example];
Expand All @@ -170,6 +187,13 @@ export class ExampleViewer implements OnInit {
// class symbol to Ivy's module factory constructor. There is no equivalent for View Engine,
// where factories are stored in separate files. Hence the API is currently Ivy-only.
this._exampleModuleFactory = new ɵNgModuleFactory(moduleExports[module.name]);

// Since the data is loaded asynchronously, we can't count on the native behavior
// that scrolls the element into view automatically. We do it ourselves while giving
// the page some time to render.
if (typeof location !== 'undefined' && location.hash.slice(1) === this._example) {
setTimeout(() => this.elementRef.nativeElement.scrollIntoView(), 300);
}
}

private _generateExampleTabs() {
Expand Down

0 comments on commit 5c5903e

Please sign in to comment.