1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
const debounce = require('debounce');
const GitBook = require('gitbook-core');
const { React } = GitBook;
const Page = require('./Page');
const Toolbar = require('./Toolbar');
const HEADINGS_SELECTOR = 'h1[id],h2[id],h3[id],h4[id]';
/**
* Get offset of an element relative to a parent container.
* @param {DOMElement} container
* @param {DOMElement} element
* @return {Number} offset
*/
function getOffset(container, element, type = 'Top') {
const parent = element.parentElement;
let base = 0;
if (parent != container) {
base = getOffset(container, parent, type);
}
return base + element[`offset${type}`];
}
/**
* Find the current heading anchor for a scroll position.
* @param {DOMElement} container
* @param {Number} top
* @return {String}
*/
function getHeadingID(container, top) {
let id;
const headings = container.querySelectorAll(HEADINGS_SELECTOR);
headings.forEach(heading => {
if (id) {
return;
}
const offset = getOffset(container, heading);
if (offset > top) {
id = heading.getAttribute('id');
}
});
return id;
}
const Body = React.createClass({
propTypes: {
page: GitBook.PropTypes.Page,
readme: GitBook.PropTypes.Readme,
history: GitBook.PropTypes.History,
updateURI: React.PropTypes.func
},
getInitialState() {
this.debouncedOnScroll = debounce(this.onScroll, 300);
return {};
},
/**
* User is scrolling the page, update the location with current section's ID.
*/
onScroll() {
const { scrollContainer } = this;
const { history, updateURI } = this.props;
const { location } = history;
// Find the id matching the current scroll position
const hash = getHeadingID(scrollContainer, scrollContainer.scrollTop);
// Update url if changed
if (hash !== location.hash) {
updateURI(location.merge({ hash }));
}
},
/**
* Component has been updated with a new location,
* scroll to the right anchor.
*/
componentDidUpdate() {
},
render() {
const { page, readme } = this.props;
return (
<GitBook.InjectedComponent matching={{ role: 'body:wrapper' }}>
<div
className="Body page-wrapper"
onScroll={this.debouncedOnScroll}
ref={div => this.scrollContainer = div}
>
<GitBook.InjectedComponent matching={{ role: 'toolbar:wrapper' }}>
<Toolbar title={page.title} readme={readme} />
</GitBook.InjectedComponent>
<GitBook.InjectedComponent matching={{ role: 'page:wrapper' }}>
<Page page={page} />
</GitBook.InjectedComponent>
</div>
</GitBook.InjectedComponent>
);
}
});
module.exports = GitBook.connect(Body,
() => {
return {};
},
({ History }, dispatch) => {
return {
updateURI: (location) => dispatch(History.replace(location))
};
}
);
|