-
Notifications
You must be signed in to change notification settings - Fork 395
feat(toc): add table of contents to overview and guides #230
Conversation
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); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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"> |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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; | ||
} | ||
|
There was a problem hiding this comment.
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
src/styles/_markdown-theme.scss
Outdated
.docs-markdown-h4 { | ||
position: relative; | ||
padding-left: 30px; | ||
margin-left: -30px; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
src/styles/_markdown-theme.scss
Outdated
} | ||
|
||
&:hover { | ||
.material-icons { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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
@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> |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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(() => { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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)
@jelbourn - ready for second review. |
@amcdnl could you rebase? |
Done :) |
{{link.name}} | ||
</a> | ||
</nav> | ||
</div> |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private _scrollSubscription: Subscription; | ||
private _routeSubscription: Subscription; | ||
private _fragmentObserver: MutationObserver; | ||
private _headerObserver: MutationObserver; |
There was a problem hiding this comment.
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, { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(() => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this 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", |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
@jelbourn - good to go. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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.