import React from 'react'; import Navigation from './navigation'; import Content from './content'; import RoundedToggle from './rounded_toggle'; import PureRenderMixin from 'react-pure-render/mixin'; import GithubSlugger from 'github-slugger'; import debounce from 'lodash.debounce'; import { brandNames, brandClasses } from '../../custom'; import qs from 'querystring'; let slugger = new GithubSlugger(); let slug = title => { slugger.reset(); return slugger.slug(title); }; let languageOptions = ['cURL', 'CLI', 'Python', 'JavaScript']; let defaultLanguage = 'cURL'; let debouncedReplaceState = debounce(hash => { window.history.replaceState('', '', hash); }, 100); var App = React.createClass({ mixins: [PureRenderMixin], propTypes: { content: React.PropTypes.string.isRequired, ast: React.PropTypes.object.isRequired }, getInitialState() { var active = 'Introduction'; if (process.browser) { let hash = window.location.hash.split('#').pop(); let languageFromURL = qs.parse(window.location.search.substring(1)).language; let language = languageOptions.includes(languageFromURL) ? languageFromURL : defaultLanguage; let mqls = { desktop: window.matchMedia('(min-width: 961px)'), tablet: window.matchMedia('(max-width: 960px)'), mobile: window.matchMedia('(max-width: 640px)') }; Object.keys(mqls).forEach(key => { mqls[key].addListener(this.mediaQueryChanged); }); if (hash) { let headingForHash = this.props.ast.children .filter(child => child.type === 'heading') .find(heading => heading.data.id === hash); if (headingForHash) { active = headingForHash.children[0].value; } } return { // media queryMatches mqls: mqls, // object of currently matched queries, like { desktop: true } queryMatches: {}, language: language, columnMode: 2, activeSection: active, // for the toggle-able navigation on mobile showNav: false }; } else { return { mqls: { desktop: true }, queryMatches: { desktop: true }, language: defaultLanguage, activeSection: '', showNav: false }; } }, toggleNav() { this.setState({ showNav: !this.state.showNav }); }, componentDidMount() { this.mediaQueryChanged(); this.onScroll = debounce(this._onScroll, 100); document.addEventListener('scroll', this.onScroll); this._onScroll(); }, _onScroll() { var sections = document.querySelectorAll('div.section'); if (!sections.length) return; for (var i = 0; i < sections.length; i++) { var rect = sections[i].getBoundingClientRect(); if (rect.bottom > 0) { this.setState({ activeSection: sections[i].getAttribute('data-title') }); return; } } }, mediaQueryChanged() { this.setState({ queryMatches: { mobile: this.state.mqls.mobile.matches, tablet: this.state.mqls.tablet.matches, desktop: this.state.mqls.desktop.matches } }); }, componentWillUnmount() { Object.keys(this.state.mqls).forEach(key => this.state.mqls[key].removeListener(this.mediaQueryChanged)); document.body.removeEventListener('scroll', this.onScroll); }, onChangeLanguage(language) { this.setState({ language }, () => { if (window.history) { window.history.pushState(null, null, `?${qs.stringify({ language })}${window.location.hash}`); } }); }, componentDidUpdate(_, prevState) { if (prevState.activeSection !== this.state.activeSection) { // when the section changes, replace the hash debouncedReplaceState(`#${slug(this.state.activeSection)}`); } else if (prevState.language !== this.state.language || prevState.columnMode !== this.state.columnMode) { // when the language changes, use the hash to set scroll window.location.hash = window.location.hash; } }, navigationItemClicked(activeSection) { setTimeout(() => { this.setState({ activeSection }); }, 10); if (!this.state.queryMatches.desktop) { this.toggleNav(); } }, toggleColumnMode() { this.setState({ columnMode: this.state.columnMode === 1 ? 2 : 1 }); }, render() { let { ast } = this.props; let { activeSection, queryMatches, showNav, columnMode } = this.state; let col1 = columnMode === 1 && queryMatches.desktop; return (