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

Conversation

amcdnl
Copy link
Contributor

@amcdnl amcdnl commented Jul 26, 2017

Addresses #167 by adding a table of contents and jump links to the component overview and guide pages.

This PR depends on angular/components#6040 being merged.

img

amcdnl added 2 commits July 25, 2017 21:26
commit b684808
Author: Austin <[email protected]>
Date:   Mon Jul 17 15:35:08 2017 -0600

    feat(PWA): add service worker angular#175 (angular#223)

commit ec7c637
Author: Austin <[email protected]>
Date:   Mon Jul 17 13:52:28 2017 -0600

    feat(docs): add page titles to doc pages angular#189 (angular#220)

commit 6170057
Author: Rafael Santana <[email protected]>
Date:   Wed Jul 12 20:05:29 2017 -0300

    Fix exception being thrown on invalid routes (angular#218)

commit 101aeff
Author: Michael Prentice <[email protected]>
Date:   Tue Jul 11 19:38:17 2017 -0400

    fix(footer): extra w in www.angular.io (angular#217)

    just use https://angular.io since that is where www.angular.io goes

commit 135c38f
Author: Rafael Santana <[email protected]>
Date:   Sun Jul 9 12:37:39 2017 -0300

    Add handling for invalid routes (angular#191)

commit ad1400e
Author: Will Howell <[email protected]>
Date:   Sun Jul 9 11:35:25 2017 -0400

    Set max width for component and guide viewers (angular#207)

commit ac3f30e
Author: Austin <[email protected]>
Date:   Sun Jul 9 10:18:02 2017 -0500

    chore(styles): apply material light / dark styles to highlightjs (angular#213)
}

a {
color: mat-color($foreground, secondary-text);
Copy link
Member

Choose a reason for hiding this comment

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

This is too broad; it's affecting every anchor in the app, including the top nav and examples. Should be scoped to a css class

$background: map-get($theme, background);
$foreground: map-get($theme, foreground);

.toc-container {
Copy link
Member

Choose a reason for hiding this comment

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

Prefix docs styles with docs-

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.

flex-grow: 1;
}

.component-overview {
Copy link
Member

Choose a reason for hiding this comment

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

Prefix with docs-

@@ -3,7 +3,10 @@
</div>

<div class="docs-guide-wrapper">
<doc-viewer class="docs-guide-content" [documentUrl]="guide.document"></doc-viewer>
<div class="docs-guide-inner">
Copy link
Member

Choose a reason for hiding this comment

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

Is there a more specific name than inner? I find class names line this to be hard to understand when coming back to the code later. Better to be verbose with something like docs-guide-toc-and-content

this._scrollSubscription = Observable
.fromEvent(this._scrollContainer, 'scroll')
.debounceTime(10)
.subscribe(this.onScroll.bind(this));
Copy link
Member

Choose a reason for hiding this comment

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

Need to unsubscribe on destroy

}
return false;
}

Copy link
Member

Choose a reason for hiding this comment

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

return this.scrollOffset >= currentLink.top && !(nextLink && nextLink.top < this.scrollOffset);

Also with a comment like A link is considered "active" if the page is scrolled passed it without also being scrolled passed the following link

.docs-markdown-h4 {
position: relative;
padding-left: 30px;
margin-left: -30px;
Copy link
Member

Choose a reason for hiding this comment

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

Why the 30 / -30 here? At the very least needs a comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Want the anchor to position 30px to the left of the header. Using absolutely position makes the anchor link not clickable because its outside the header hover.

Added comments for this.

}

&:hover {
.material-icons {
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this is meant to target the header link but is more broad than that. Also why does it need to be absolute?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See above on re: absolute.

styleUrls: ['./table-of-contents.scss'],
template: `
<div *ngIf="links?.length" class="toc-container">
<h2>Contents</h2>
Copy link
Member

Choose a reason for hiding this comment

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

I believe <h2> isn't appropriate here; h1 - h6 are meant for content section headings, not really navigation

@amcdnl
Copy link
Contributor Author

amcdnl commented Jul 29, 2017

@jelbourn - ready for another review.

class="docs-markdown-a docs-header-link"
aria-label="Link to this heading"
[href]="url">
<i class="material-icons">link</i>
Copy link
Member

Choose a reason for hiding this comment

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

This <i> should be aria-hidden (or use md-icon)

@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

import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/debounceTime';

interface Link {
Copy link
Member

Choose a reason for hiding this comment

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

Add some descriptions on this type? Not immediately obvious exactly what type, top etc. refer to or how Link in general is meant to be used


createLinks(): void {
// content is loading async, the timeout helps delay the query selector being empty
setTimeout(() => {
Copy link
Member

Choose a reason for hiding this comment

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

Would using cdkObserveContent / a MutationObserver be better for this?
(just to see when the content is updated and then can be unsubscribed)

setTimeout(() => {
const headers = this._document.querySelectorAll('.docs-markdown-h3,.docs-markdown-h4');
for (const header of headers) {
const { top } = header.getBoundingClientRect();
Copy link
Member

Choose a reason for hiding this comment

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

Omit spaces inside braces:

const {top} = header.getBoundingClientRect();

(here and elsewhere)

@amcdnl
Copy link
Contributor Author

amcdnl commented Aug 1, 2017

@jelbourn - ready for second review.

@jelbourn
Copy link
Member

jelbourn commented Aug 2, 2017

@amcdnl could you rebase?
(I merged your other PR for inline styles)

@amcdnl
Copy link
Contributor Author

amcdnl commented Aug 5, 2017

Done :)

{{link.name}}
</a>
</nav>
</div>
Copy link
Member

Choose a reason for hiding this comment

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

Move this template into its own file?

private _fragmentObserver: MutationObserver;
private _headerObserver: MutationObserver;

private get scrollOffset(): number {
Copy link
Member

Choose a reason for hiding this comment

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

This would be a better function than a getter.
(getters generate a lot of JS code vs just a function)

private _headerObserver: MutationObserver;

private get scrollOffset(): number {
const {top} = this._element.nativeElement.getBoundingClientRect();
Copy link
Member

Choose a reason for hiding this comment

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

Add comment explaining what this is computing ("scrollOffset" by itself isn't specific enough)


ngOnInit(): void {
this._scrollContainer = this.container ?
this._document.querySelectorAll(this.container)[0] : window;
Copy link
Member

Choose a reason for hiding this comment

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

Why window instead of document.body or documentElement?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the body element doesn't have a height set img

private _scrollSubscription: Subscription;
private _routeSubscription: Subscription;
private _fragmentObserver: MutationObserver;
private _headerObserver: MutationObserver;
Copy link
Member

Choose a reason for hiding this comment

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

Add descriptions for these two observers?

}
});

this._fragmentObserver.observe(document.body, {
Copy link
Member

Choose a reason for hiding this comment

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

Why attach this directly to the body instead of the document viewer host element?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is attached to the body because it could be the document viewer or the guide viewer. In an effort, to keep it generic i attached it to the body. I could add an input if you like to pick a specific element to listen on. Let me know either way.

Copy link
Member

Choose a reason for hiding this comment

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

Both the component pages (component-overview and component-api) and the guide-viewer use doc-viewer in their template. Thinking about it more, though, I realize a much more straightforward approach would be to have the doc-viewer fire an event after appending the content, which the table-of-contents could use to know when to go, e.g.

<doc-viewer
    documentUrl="/assets/documents/overview/{{componentViewer.componentDocItem.id}}.html"
    class="docs-component-view-text-content docs-component-overview"
    (contentLoaded)="toc.doThatThing()">
</doc-viewer>
<table-of-contents #toc
  container=".mat-sidenav-content">
</table-of-contents>

I must have had MutationObserver on my mind the other day

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lets talk through this, I'm curious to your thoughts on integrating the route change and the observer together.


// observe body content changes until the headers
// are painted. this is required because the page can load async
this._headerObserver = new MutationObserver(() => {
Copy link
Member

Choose a reason for hiding this comment

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

I don't quite follow why you need two observers set up at different times

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately the router events do not give me enough information just to attach one to the route event.

For example, I listen to this._route.fragment which returns me the route fragment a user clicked. From that fragment I then ensure its loaded and then scroll into view.

On the this._router.events, I'm listening for when the user navigates to a totally different page. For example, navigating from accordion to autocomplete. In this scenario, the page doesn't actually change since its the same route it just updates the content. In this scenario, I want to update the links only.

If the router events gave me more information, I could easily consolidate these but at this time I need 2 different subscriptions and observers to make the appropriate action.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

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

Just a few last things I noticed

package.json Outdated
@@ -4,7 +4,7 @@
"license": "MIT",
"angular-cli": {},
"scripts": {
"start": "npm run build-themes && ng serve --aot --sourcemaps=false",
"start": "npm run build-themes && ng serve --sourcemaps=false",
Copy link
Member

Choose a reason for hiding this comment

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

This needs to stay --aot

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't mean to commit this but AOT causes lots of issues when developing and is slow.

constructor(private _router: Router,
private _route: ActivatedRoute,
private _element: ElementRef,
@Inject(DOCUMENT) private _document: any) {
Copy link
Member

Choose a reason for hiding this comment

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

Any reason the type here can't be Document?

color: mat-color($foreground, secondary-text);

&:hover,
&.active {
Copy link
Member

Choose a reason for hiding this comment

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

Need to prefix these classes with docs-

font-weight: bold;
}

a {
Copy link
Member

Choose a reason for hiding this comment

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

Change you make this style a css class instead of styling a directly?

@amcdnl
Copy link
Contributor Author

amcdnl commented Aug 15, 2017

@jelbourn - good to go.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

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

LGTM

@jelbourn jelbourn merged commit 7d0297b into angular:master Aug 16, 2017
@amcdnl amcdnl deleted the toc branch August 16, 2017 21:44
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants