TOC now has active heading highlighted

This commit is contained in:
Tom Moor
2017-10-15 22:26:23 -07:00
parent 5f4b5f6d33
commit 3d446347f2

View File

@ -1,6 +1,9 @@
// @flow // @flow
import React, { Component } from 'react'; import React, { Component } from 'react';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { List } from 'immutable'; import { List } from 'immutable';
import { color } from 'styles/constants';
import headingToSlug from '../headingToSlug'; import headingToSlug from '../headingToSlug';
import type { State, Block } from '../types'; import type { State, Block } from '../types';
import styled from 'styled-components'; import styled from 'styled-components';
@ -9,44 +12,104 @@ type Props = {
state: State, state: State,
}; };
class Minimap extends Component { @observer class Minimap extends Component {
props: Props; props: Props;
@observable activeHeading: ?string;
componentDidMount() {
window.addEventListener('scroll', this.updateActiveHeading);
this.updateActiveHeading();
}
componentWillUnmount() {
window.removeEventListener('scroll', this.updateActiveHeading);
}
updateActiveHeading = () => {
let activeHeading = this.headingElements[0].id;
for (const element of this.headingElements) {
const bounds = element.getBoundingClientRect();
if (bounds.top <= 0) activeHeading = element.id;
}
this.activeHeading = activeHeading;
};
get headingElements(): HTMLElement[] {
const elements = [];
const tagNames = ['h2', 'h3', 'h4', 'h5', 'h6'];
for (const tagName of tagNames) {
for (const ele of document.getElementsByTagName(tagName)) {
elements.push(ele);
}
}
return elements;
}
get headings(): List<Block> { get headings(): List<Block> {
const { state } = this.props; const { state } = this.props;
return state.document.nodes.filter((node: Block) => { return state.document.nodes.filter((node: Block) => {
if (!node.text) return false; if (!node.text) return false;
if (node.type === 'heading1') return false;
return node.type.match(/^heading/); return node.type.match(/^heading/);
}); });
} }
render() { render() {
// If there are one or less headings in the document no need for a minimap
if (this.headings.size <= 1) return null;
return ( return (
<Wrapper> <Wrapper>
<Headings> <Sections>
{this.headings.map(heading => ( {this.headings.map(heading => {
<li> const slug = headingToSlug(heading.type, heading.text);
<a href={`#${headingToSlug(heading.type, heading.text)}`}>
{heading.text} return (
</a> <ListItem type={heading.type}>
</li> <Anchor href={`#${slug}`} active={this.activeHeading === slug}>
))} {heading.text}
</Headings> </Anchor>
</ListItem>
);
})}
</Sections>
</Wrapper> </Wrapper>
); );
} }
} }
const Headings = styled.ol` const Anchor = styled.a`
margin: 0; color: ${props => (props.active ? color.primary : color.slate)};
font-weight: ${props => (props.active ? 500 : 400)};
`;
const Sections = styled.ol`
margin: 0 0 0 -8px;
padding: 0; padding: 0;
list-style: none;
font-size: 13px;
border-right: 1px solid ${color.slate};
`;
const ListItem = styled.li`
position: relative;
margin-left: ${props => (props.type === 'heading2' ? '8px' : '16px')};
text-align: right;
color: ${color.slate};
padding-right: 10px;
`; `;
const Wrapper = styled.div` const Wrapper = styled.div`
position: fixed; position: fixed;
left: 0; right: 0;
top: 50%; top: 160px;
padding-right: 20px;
background: ${color.white};
`; `;
export default Minimap; export default Minimap;