blob: 47907b94de066f80189f5a4f046ccddce5ad8eba [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var _ = require('lodash');
var React = require('react');
var h = require('./util').h;
// Expects props {title, headings []Heading}, where Heading is
// {id, text, level, isAboveWindow, isBelowWindow}.
module.exports = React.createClass({
displayName: 'Toc',
getInitialState: function() {
return _.assign({
minLevel: _.min(_.map(this.props.headings, 'level'))
}, this.props);
},
handleHashChange: function(e) {
this.setState(function(state) {
_.forEach(state.headings, function(heading) {
heading.active = ('#' + heading.id) === window.location.hash;
});
});
},
handleScroll: function(e) {
this.setState(function(state) {
var i = 0, l = state.headings.length;
for (; i < l; i++) {
if (!state.headings[i].isAboveWindow()) {
break;
}
if ((i + 1 === l) || state.headings[i + 1].isBelowWindow()) {
break;
}
}
for (var j = 0; j < l; j++) {
state.headings[j].active = i === j;
}
});
},
componentDidMount: function() {
window.addEventListener('hashchange', this.handleHashChange);
window.addEventListener('resize', this.handleScroll);
window.addEventListener('scroll', this.handleScroll);
// Initialize active heading.
this.handleScroll();
},
componentWillUnmount: function() {
window.removeEventListener('hashchange', this.handleHashChange);
window.removeEventListener('resize', this.handleScroll);
window.removeEventListener('scroll', this.handleScroll);
},
render: function() {
var props = this.props, state = this.state;
return h('div', [
h('div.title', props.title),
h('div', _.map(props.headings, function(heading) {
var gap = heading.level - state.minLevel;
return h('a.heading' + (heading.active ? '.active' : ''), {
href: '#' + heading.id,
style: {
marginLeft: (12 * gap) + 'px',
fontSize: gap > 0 ? '13px' : '14px'
}
}, heading.text);
}))
]);
}
});