summaryrefslogtreecommitdiffstats
path: root/packages/gitbook-plugin-theme-default/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'packages/gitbook-plugin-theme-default/src/components')
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Body.js121
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/LoadingBar.js124
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Page.js30
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Sidebar.js25
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Summary.js111
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Theme.js57
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Toolbar.js43
7 files changed, 511 insertions, 0 deletions
diff --git a/packages/gitbook-plugin-theme-default/src/components/Body.js b/packages/gitbook-plugin-theme-default/src/components/Body.js
new file mode 100644
index 0000000..c61a2f3
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Body.js
@@ -0,0 +1,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))
+ };
+ }
+);
diff --git a/packages/gitbook-plugin-theme-default/src/components/LoadingBar.js b/packages/gitbook-plugin-theme-default/src/components/LoadingBar.js
new file mode 100644
index 0000000..11e1ddb
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/LoadingBar.js
@@ -0,0 +1,124 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+/**
+ * Displays a progress bar (YouTube-like) at the top of container
+ * Based on https://github.com/lonelyclick/react-loading-bar/blob/master/src/Loading.jsx
+ */
+const LoadingBar = React.createClass({
+ propTypes: {
+ show: React.PropTypes.bool
+ },
+
+ getDefaultProps() {
+ return {
+ show: false
+ };
+ },
+
+ getInitialState() {
+ return {
+ size: 0,
+ disappearDelayHide: false, // when dispappear, first transition then display none
+ percent: 0,
+ appearDelayWidth: 0 // when appear, first display block then transition width
+ };
+ },
+
+ componentWillReceiveProps(nextProps) {
+ const { show } = nextProps;
+
+ if (show) {
+ this.show();
+ } else {
+ this.hide();
+ }
+ },
+
+ shouldComponentUpdate(nextProps, nextState) {
+ return true; // !shallowEqual(nextState, this.state)
+ },
+
+ show() {
+ let { size, percent } = this.state;
+
+ const appearDelayWidth = size === 0;
+ percent = calculatePercent(percent);
+
+ this.setState({
+ size: ++size,
+ appearDelayWidth,
+ percent
+ });
+
+ if (appearDelayWidth) {
+ setTimeout(() => {
+ this.setState({
+ appearDelayWidth: false
+ });
+ });
+ }
+ },
+
+ hide() {
+ let { size } = this.state;
+
+ if (--size < 0) {
+ this.setState({ size: 0 });
+ return;
+ }
+
+ this.setState({
+ size: 0,
+ disappearDelayHide: true,
+ percent: 1
+ });
+
+ setTimeout(() => {
+ this.setState({
+ disappearDelayHide: false,
+ percent: 0
+ });
+ }, 500);
+ },
+
+ getBarStyle() {
+ const { disappearDelayHide, appearDelayWidth, percent } = this.state;
+
+ return {
+ width: appearDelayWidth ? 0 : percent * 100 + '%',
+ display: disappearDelayHide || percent > 0 ? 'block' : 'none'
+ };
+ },
+
+ getShadowStyle() {
+ const { percent, disappearDelayHide } = this.state;
+
+ return {
+ display: disappearDelayHide || percent > 0 ? 'block' : 'none'
+ };
+ },
+
+ render() {
+ return (
+ <div className="LoadingBar">
+ <div className="LoadingBar-Bar" style={this.getBarStyle()}>
+ <div className="LoadingBar-Shadow"
+ style={this.getShadowStyle()}>
+ </div>
+ </div>
+ </div>
+ );
+ }
+});
+
+function calculatePercent(percent) {
+ percent = percent || 0;
+
+ // How much of remaining bar we advance
+ const progress = 0.1 + Math.random() * 0.3;
+
+ return percent + progress * (1 - percent);
+}
+
+module.exports = LoadingBar;
diff --git a/packages/gitbook-plugin-theme-default/src/components/Page.js b/packages/gitbook-plugin-theme-default/src/components/Page.js
new file mode 100644
index 0000000..cbce704
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Page.js
@@ -0,0 +1,30 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const Page = React.createClass({
+ propTypes: {
+ page: GitBook.PropTypes.Page
+ },
+
+ render() {
+ const { page } = this.props;
+
+ return (
+ <div className="PageContainer">
+ <GitBook.InjectedComponent matching={{ role: 'search:container:results' }} props={this.props}>
+ <div className="Page">
+ <GitBook.InjectedComponentSet matching={{ role: 'page:header' }} props={this.props} />
+
+ <GitBook.InjectedComponent matching={{ role: 'page:container' }} props={this.props}>
+ <GitBook.HTMLContent html={page.content} />
+ </GitBook.InjectedComponent>
+
+ <GitBook.InjectedComponentSet matching={{ role: 'page:footer' }} props={this.props} />
+ </div>
+ </GitBook.InjectedComponent>
+ </div>
+ );
+ }
+});
+
+module.exports = Page;
diff --git a/packages/gitbook-plugin-theme-default/src/components/Sidebar.js b/packages/gitbook-plugin-theme-default/src/components/Sidebar.js
new file mode 100644
index 0000000..ab628df
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Sidebar.js
@@ -0,0 +1,25 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const Summary = require('./Summary');
+
+const Sidebar = React.createClass({
+ propTypes: {
+ summary: GitBook.PropTypes.Summary
+ },
+
+ render() {
+ const { summary } = this.props;
+
+ return (
+ <div className="Sidebar-Flex">
+ <div className="Sidebar book-summary">
+ <GitBook.InjectedComponent matching={{ role: 'search:container:input' }} />
+ <Summary summary={summary} />
+ </div>
+ </div>
+ );
+ }
+});
+
+module.exports = Sidebar;
diff --git a/packages/gitbook-plugin-theme-default/src/components/Summary.js b/packages/gitbook-plugin-theme-default/src/components/Summary.js
new file mode 100644
index 0000000..ef6ab3f
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Summary.js
@@ -0,0 +1,111 @@
+const classNames = require('classnames');
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+let SummaryArticle = React.createClass({
+ propTypes: {
+ active: React.PropTypes.bool,
+ article: GitBook.PropTypes.SummaryArticle
+ },
+
+ render() {
+ const { article, active } = this.props;
+ const className = classNames('SummaryArticle', {
+ active
+ });
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:article' }} props={this.props}>
+ <li className={className}>
+ {article.ref ?
+ <GitBook.Link to={article}>{article.title}</GitBook.Link>
+ : <span>{article.title}</span>}
+ </li>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+SummaryArticle = GitBook.connect(SummaryArticle, ({page}, {article}) => {
+ return {
+ active: page.level === article.level
+ };
+});
+
+const SummaryArticles = React.createClass({
+ propTypes: {
+ articles: GitBook.PropTypes.listOf(GitBook.PropTypes.SummaryArticle)
+ },
+
+ render() {
+ const { articles } = this.props;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:articles' }} props={this.props}>
+ <ul className="SummaryArticles">
+ {articles.map(article => <SummaryArticle key={article.level} article={article} />)}
+ </ul>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+const SummaryPart = React.createClass({
+ propTypes: {
+ part: GitBook.PropTypes.SummaryPart
+ },
+
+ render() {
+ const { part } = this.props;
+ const { title, articles } = part;
+
+ const titleEL = title ? <h2 className="SummaryPart-Title">{title}</h2> : null;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:part' }} props={this.props}>
+ <div className="SummaryPart">
+ {titleEL}
+ <SummaryArticles articles={articles} />
+ </div>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+const SummaryParts = React.createClass({
+ propTypes: {
+ parts: GitBook.PropTypes.listOf(GitBook.PropTypes.SummaryPart)
+ },
+
+ render() {
+ const { parts } = this.props;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:parts' }} props={this.props}>
+ <div className="SummaryParts">
+ {parts.map((part, i) => <SummaryPart key={i} part={part} />)}
+ </div>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+const Summary = React.createClass({
+ propTypes: {
+ summary: GitBook.PropTypes.Summary
+ },
+
+ render() {
+ const { summary } = this.props;
+ const { parts } = summary;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:container' }} props={this.props}>
+ <div className="Summary book-summary">
+ <SummaryParts parts={parts} />
+ </div>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+module.exports = Summary;
diff --git a/packages/gitbook-plugin-theme-default/src/components/Theme.js b/packages/gitbook-plugin-theme-default/src/components/Theme.js
new file mode 100644
index 0000000..b323fc4
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Theme.js
@@ -0,0 +1,57 @@
+const GitBook = require('gitbook-core');
+const { React, ReactCSSTransitionGroup } = GitBook;
+
+const Sidebar = require('./Sidebar');
+const Body = require('./Body');
+const LoadingBar = require('./LoadingBar');
+
+const Theme = React.createClass({
+ propTypes: {
+ // State
+ page: GitBook.PropTypes.Page,
+ summary: GitBook.PropTypes.Summary,
+ readme: GitBook.PropTypes.Readme,
+ history: GitBook.PropTypes.History,
+ sidebar: React.PropTypes.object,
+ // Other props
+ children: React.PropTypes.node
+ },
+
+ render() {
+ const { page, summary, children, sidebar, readme, history } = this.props;
+
+ return (
+ <GitBook.FlexLayout column className="GitBook book">
+ <LoadingBar show={history.loading} />
+ <GitBook.Head
+ title={page.title}
+ titleTemplate="%s - GitBook" />
+ <GitBook.ImportCSS href="gitbook/theme-default/theme.css" />
+
+ <GitBook.FlexBox>
+ <ReactCSSTransitionGroup
+ component={GitBook.FlexLayout}
+ transitionName="Layout"
+ transitionEnterTimeout={300}
+ transitionLeaveTimeout={300}>
+ {sidebar.open ? (
+ <Sidebar key={0} summary={summary} />
+ ) : null}
+ <div key={1} className="Body-Flex">
+ <Body
+ page={page}
+ readme={readme}
+ history={history}
+ />
+ </div>
+ </ReactCSSTransitionGroup>
+ </GitBook.FlexBox>
+ {children}
+ </GitBook.FlexLayout>
+ );
+ }
+});
+
+module.exports = GitBook.connect(Theme, ({page, summary, sidebar, readme, history}) => {
+ return { page, summary, sidebar, readme, history };
+});
diff --git a/packages/gitbook-plugin-theme-default/src/components/Toolbar.js b/packages/gitbook-plugin-theme-default/src/components/Toolbar.js
new file mode 100644
index 0000000..d426a40
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Toolbar.js
@@ -0,0 +1,43 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const sidebar = require('../actions/sidebar');
+
+const Toolbar = React.createClass({
+ propTypes: {
+ title: React.PropTypes.string.isRequired,
+ dispatch: React.PropTypes.func,
+ readme: GitBook.PropTypes.Readme
+ },
+
+ onToggle() {
+ const { dispatch } = this.props;
+ dispatch(sidebar.toggle());
+ },
+
+ render() {
+ const { title, readme } = this.props;
+
+ return (
+ <GitBook.FlexLayout className="Toolbar">
+ <GitBook.FlexBox className="Toolbar-left">
+ <GitBook.InjectedComponentSet align="flex-end" matching={{ role: 'toolbar:buttons:left' }}>
+ <GitBook.Button onClick={this.onToggle}>
+ <GitBook.Icon id="align-justify" />
+ </GitBook.Button>
+ </GitBook.InjectedComponentSet>
+ </GitBook.FlexBox>
+ <GitBook.FlexBox auto>
+ <h1 className="Toolbar-Title">
+ <GitBook.Link to={readme.file}>{title}</GitBook.Link>
+ </h1>
+ </GitBook.FlexBox>
+ <GitBook.FlexBox className="Toolbar-right">
+ <GitBook.InjectedComponentSet align="flex-end" matching={{ role: 'toolbar:buttons:right' }} />
+ </GitBook.FlexBox>
+ </GitBook.FlexLayout>
+ );
+ }
+});
+
+module.exports = GitBook.connect(Toolbar);