Skip to content
This repository was archived by the owner on Dec 18, 2024. It is now read-only.

feat(toc): add table of contents to overview and guides #230

Merged
merged 10 commits into from
Aug 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,338 changes: 3,816 additions & 522 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/_app-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@import './app/shared/example-viewer/example-viewer-theme';
@import './app/shared/footer/footer-theme';
@import './app/shared/navbar/navbar-theme';
@import './app/shared/table-of-contents/table-of-contents-theme';
@import './styles/api-theme';
@import './styles/markdown-theme';
@import './styles/svg-theme';
Expand Down Expand Up @@ -53,4 +54,5 @@
@include guide-list-theme($theme);
@include home-page-theme($theme);
@include nav-bar-theme($theme);
@include table-of-contents-theme($theme);
}
5 changes: 4 additions & 1 deletion src/app/pages/component-viewer/component-overview.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<doc-viewer
documentUrl="/assets/documents/overview/{{componentViewer.componentDocItem.id}}.html"
class="docs-component-view-text-content"></doc-viewer>
class="docs-component-view-text-content docs-component-overview"
(contentLoaded)="toc.updateScrollPosition()">
</doc-viewer>
<table-of-contents #toc container=".mat-sidenav-content"></table-of-contents>
25 changes: 25 additions & 0 deletions src/app/pages/component-viewer/component-viewer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,30 @@ app-component-viewer {
}

.docs-component-viewer-content {
position: relative;
min-height: 500px;

table-of-contents {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be nested? Nesting for organization should be avoided
https://github.com/angular/material2/blob/master/CODING_STANDARDS.md#use-lowest-specificity-possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it needs to override the host top position.

display: inline-flex;
top: 35px;

@media (max-width: $small-breakpoint-width) {
display: none;
}
}
}

.docs-component-view-text-content {
display: inline-flex;
flex-grow: 1;
}

.docs-component-overview {
width: 80%;
margin-right: 20px;

@media (max-width: $small-breakpoint-width) {
width: 100%;
margin-right: 0;
}
}
3 changes: 3 additions & 0 deletions src/app/pages/component-viewer/component-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const exampleKey = 'button-types';

const mockActivatedRoute = {
snapshot: {},
fragment: Observable.create(observer => {
observer.complete();
}),
params: Observable.create(observer => {
observer.next({id: docItemsId});
observer.complete();
Expand Down
12 changes: 8 additions & 4 deletions src/app/pages/component-viewer/component-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {ComponentPageTitle} from '../page-title/page-title';
import {MdTabsModule} from '@angular/material';
import {DocViewerModule} from '../../shared/doc-viewer/doc-viewer-module';
import {CommonModule} from '@angular/common';

import {TableOfContentsModule} from '../../shared/table-of-contents/table-of-contents.module';

@Component({
selector: 'app-component-viewer',
Expand Down Expand Up @@ -55,10 +55,14 @@ export class ComponentApi extends ComponentOverview {}
})
export class ComponentExamples extends ComponentOverview {}



@NgModule({
imports: [MdTabsModule, RouterModule, DocViewerModule, CommonModule],
imports: [
MdTabsModule,
RouterModule,
DocViewerModule,
CommonModule,
TableOfContentsModule
],
exports: [ComponentViewer],
declarations: [ComponentViewer, ComponentOverview, ComponentApi, ComponentExamples],
providers: [DocumentationItems, ComponentPageTitle],
Expand Down
7 changes: 6 additions & 1 deletion src/app/pages/guide-viewer/guide-viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ <h1>{{guide.name}}</h1>
</div>

<div class="docs-guide-wrapper">
<doc-viewer class="docs-guide-content" [documentUrl]="guide.document"></doc-viewer>
<div class="docs-guide-toc-and-content">
<doc-viewer class="docs-guide-content"
(contentLoaded)="toc.updateScrollPosition()"
[documentUrl]="guide.document"></doc-viewer>
<table-of-contents #toc></table-of-contents>
</div>
</div>

<app-footer></app-footer>
31 changes: 29 additions & 2 deletions src/app/pages/guide-viewer/guide-viewer.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
@import "../../../../node_modules/@angular/material/theming";
@import '../../../styles/constants';

/* For desktop, the content should be aligned with the page title. */
$guide-content-margin-side: 70px;
$guide-content-margin-side-xs: 15px;

:host {
display: flex;
flex-direction: column;
Expand All @@ -8,15 +13,37 @@

.docs-guide-wrapper {
padding: 20px $content-padding-side 0;
display: block;
text-align: center;

@media ($mat-xsmall) {
padding-left: $content-padding-side-xs;
padding-right: $content-padding-side-xs;
}
}

.docs-guide-toc-and-content {
display: inline-block;
text-align: left;
max-width: 940px;
}

.docs-guide-content {
display: block;
margin: 0 auto;
max-width: 940px;
float: left;
width: 80%;

@media (max-width: $small-breakpoint-width) {
width: 100%;
}
}

table-of-contents {
top: 35px;
float: right;
width: 15%;

@media (max-width: $small-breakpoint-width) {
display: none;
}
}
3 changes: 3 additions & 0 deletions src/app/pages/guide-viewer/guide-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {DocsAppTestingModule} from '../../testing/testing-module';
const guideItemsId = 'getting-started';

const mockActivatedRoute = {
fragment: Observable.create(observer => {
observer.complete();
}),
params: Observable.create(observer => {
observer.next({id: guideItemsId});
observer.complete();
Expand Down
3 changes: 2 additions & 1 deletion src/app/pages/guide-viewer/guide-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {ActivatedRoute, RouterModule, Router} from '@angular/router';
import {GuideItem, GuideItems} from '../../shared/guide-items/guide-items';
import {FooterModule} from '../../shared/footer/footer';
import {DocViewerModule} from '../../shared/doc-viewer/doc-viewer-module';
import {TableOfContentsModule} from '../../shared/table-of-contents/table-of-contents.module';
import {ComponentPageTitle} from '../page-title/page-title';

@Component({
Expand Down Expand Up @@ -32,7 +33,7 @@ export class GuideViewer implements OnInit {
}

@NgModule({
imports: [DocViewerModule, FooterModule, RouterModule],
imports: [DocViewerModule, FooterModule, RouterModule, TableOfContentsModule],
exports: [GuideViewer],
declarations: [GuideViewer],
providers: [GuideItems, ComponentPageTitle],
Expand Down
7 changes: 4 additions & 3 deletions src/app/shared/doc-viewer/doc-viewer-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@angular/material';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {HeaderLink} from './header-link';


// ExampleViewer is included in the DocViewerModule because they have a circular dependency.
Expand All @@ -23,8 +24,8 @@ import {NgModule} from '@angular/core';
PortalModule,
PlunkerButtonModule
],
declarations: [DocViewer, ExampleViewer],
entryComponents: [ExampleViewer],
exports: [DocViewer, ExampleViewer],
declarations: [DocViewer, ExampleViewer, HeaderLink],
entryComponents: [ExampleViewer, HeaderLink],
exports: [DocViewer, ExampleViewer, HeaderLink],
})
export class DocViewerModule { }
27 changes: 17 additions & 10 deletions src/app/shared/doc-viewer/doc-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import {
Component,
ComponentFactoryResolver,
ElementRef,
EventEmitter,
Injector,
Input,
OnDestroy,
ViewContainerRef
ViewContainerRef,
Output,
} from '@angular/core';
import {Http} from '@angular/http';
import {ComponentPortal, DomPortalHost} from '@angular/material';
import {ExampleViewer} from '../example-viewer/example-viewer';

import {HeaderLink} from './header-link';

@Component({
selector: 'doc-viewer',
Expand All @@ -26,6 +28,8 @@ export class DocViewer implements OnDestroy {
this._fetchDocument(url);
}

@Output() contentLoaded = new EventEmitter<void>();

constructor(private _appRef: ApplicationRef,
private _componentFactoryResolver: ComponentFactoryResolver,
private _elementRef: ElementRef,
Expand All @@ -39,9 +43,10 @@ export class DocViewer implements OnDestroy {
response => {
// TODO(mmalerba): Trust HTML.
if (response.ok) {
let docHtml = response.text();
this._elementRef.nativeElement.innerHTML = docHtml;
this._loadLiveExamples();
this._elementRef.nativeElement.innerHTML = response.text();
this._loadComponents('material-docs-example', ExampleViewer);
this._loadComponents('header-link', HeaderLink);
this.contentLoaded.next();
} else {
this._elementRef.nativeElement.innerText =
`Failed to load document: ${url}. Error: ${response.status}`;
Expand All @@ -58,22 +63,24 @@ export class DocViewer implements OnDestroy {
// the wrong place in the DOM after switching tabs. This function is a workaround to
// put the live examples back in the right place.
this._clearLiveExamples();
this._loadLiveExamples();
this._loadComponents('material-docs-example', ExampleViewer);
this._loadComponents('header-link', HeaderLink);
}

/** Instantiate a ExampleViewer for each example. */
private _loadLiveExamples() {
private _loadComponents(componentName: string, componentClass: any) {
let exampleElements =
this._elementRef.nativeElement.querySelectorAll('[material-docs-example]');
this._elementRef.nativeElement.querySelectorAll(`[${componentName}]`);

Array.prototype.slice.call(exampleElements).forEach((element: Element) => {
let example = element.getAttribute('material-docs-example');
let example = element.getAttribute(componentName);

let exampleContainer = document.createElement('div');
element.appendChild(exampleContainer);

let portalHost = new DomPortalHost(
exampleContainer, this._componentFactoryResolver, this._appRef, this._injector);
let examplePortal = new ComponentPortal(ExampleViewer, this._viewContainerRef);
let examplePortal = new ComponentPortal(componentClass, this._viewContainerRef);
let exampleViewer = portalHost.attach(examplePortal);
exampleViewer.instance.example = example;

Expand Down
45 changes: 45 additions & 0 deletions src/app/shared/doc-viewer/header-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {Component, Input, OnInit} from '@angular/core';
import {Router} from '@angular/router';

/**
* Header link is a component that handles normalizing
* the anchor jump tags with the current route url.
*
* For example:
*
* <a href="#foo">Foo</a>
*
* would result in the wrong url, this component
* combines the current route with that jump link:
*
* <a href="/guide#foo">Foo</a>
*/
@Component({
selector: 'header-link',
template: `
<a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this anchor would also benefit from an aria-describedby pointing to the header

title="Link to this heading"
[attr.aria-describedby]="example"
class="docs-markdown-a docs-header-link"
aria-label="Link to this heading"
[href]="url">
<md-icon>link</md-icon>
</a>
`
})
export class HeaderLink implements OnInit {

@Input() example: string;

url: string;
private _rootUrl: string;

constructor(router: Router) {
this._rootUrl = router.url.split('#')[0];
}

ngOnInit(): void {
this.url = `${this._rootUrl}#${this.example}`;
}

}
2 changes: 1 addition & 1 deletion src/app/shared/plunker/plunker-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class PlunkerButton {
* The form creation usually happens extremely quickly, but we handle the case of the
* plunker not yet being ready for people will poor network connections or slow devices.
*/
isDisabled: boolean = false;
isDisabled = false;
plunkerForm: HTMLFormElement;

@Input()
Expand Down
20 changes: 20 additions & 0 deletions src/app/shared/table-of-contents/_table-of-contents-theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@mixin table-of-contents-theme($theme) {
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);

.docs-toc-container {
border-left: solid 4px mat-color($primary);

.docs-link {
color: mat-color($foreground, secondary-text);

&:hover,
&.docs-active {
color: mat-color($primary);
}
}
}
}
11 changes: 11 additions & 0 deletions src/app/shared/table-of-contents/table-of-contents.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div *ngIf="links?.length" class="docs-toc-container">
<div class="docs-toc-heading">Contents</div>
<nav>
<a [href]="_rootUrl + '#' + link.id"
*ngFor="let link of links; let i = index"
class="docs-level-{{link.type}} docs-link"
[class.docs-active]="link.active">
{{link.name}}
</a>
</nav>
</div>
12 changes: 12 additions & 0 deletions src/app/shared/table-of-contents/table-of-contents.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {TableOfContents} from './table-of-contents';
import {RouterModule} from '@angular/router';

@NgModule({
imports: [CommonModule, RouterModule],
declarations: [TableOfContents],
exports: [TableOfContents],
entryComponents: [TableOfContents],
})
export class TableOfContentsModule { }
Loading