diff --git a/frontend/components/Editor/components/Minimap.js b/frontend/components/Editor/components/Minimap.js index 3d4e096e4..9178468d6 100644 --- a/frontend/components/Editor/components/Minimap.js +++ b/frontend/components/Editor/components/Minimap.js @@ -1,6 +1,9 @@ // @flow import React, { Component } from 'react'; +import { observable } from 'mobx'; +import { observer } from 'mobx-react'; import { List } from 'immutable'; +import { color } from 'styles/constants'; import headingToSlug from '../headingToSlug'; import type { State, Block } from '../types'; import styled from 'styled-components'; @@ -9,44 +12,104 @@ type Props = { state: State, }; -class Minimap extends Component { +@observer class Minimap extends Component { 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 { const { state } = this.props; return state.document.nodes.filter((node: Block) => { if (!node.text) return false; + if (node.type === 'heading1') return false; return node.type.match(/^heading/); }); } render() { + // If there are one or less headings in the document no need for a minimap + if (this.headings.size <= 1) return null; + return ( - - {this.headings.map(heading => ( -
  • - - {heading.text} - -
  • - ))} -
    + + {this.headings.map(heading => { + const slug = headingToSlug(heading.type, heading.text); + + return ( + + + {heading.text} + + + ); + })} +
    ); } } -const Headings = styled.ol` - margin: 0; +const Anchor = styled.a` + 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; + 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` position: fixed; - left: 0; - top: 50%; + right: 0; + top: 160px; + padding-right: 20px; + background: ${color.white}; `; export default Minimap;