mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
Rename "navigation" to "history" and add loading bar
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
|
||||
module.exports = {
|
||||
// Components
|
||||
REGISTER_COMPONENT: 'components/register',
|
||||
UNREGISTER_COMPONENT: 'components/unregister',
|
||||
REGISTER_COMPONENT: 'components/register',
|
||||
UNREGISTER_COMPONENT: 'components/unregister',
|
||||
// Navigation
|
||||
NAVIGATION_ACTIVATE: 'navigation/activate',
|
||||
NAVIGATION_DEACTIVATE: 'navigation/deactivate',
|
||||
NAVIGATION_LISTEN: 'navigation/listen',
|
||||
NAVIGATION_UPDATE: 'navigation/update',
|
||||
PAGE_FETCH_START: 'navigation/fetch:start',
|
||||
PAGE_FETCH_END: 'navigation/fetch:end',
|
||||
PAGE_FETCH_ERROR: 'navigation/fetch:error',
|
||||
HISTORY_ACTIVATE: 'history/activate',
|
||||
HISTORY_DEACTIVATE: 'history/deactivate',
|
||||
HISTORY_LISTEN: 'history/listen',
|
||||
HISTORY_UPDATE: 'history/update',
|
||||
PAGE_FETCH_START: 'history/fetch:start',
|
||||
PAGE_FETCH_END: 'history/fetch:end',
|
||||
PAGE_FETCH_ERROR: 'history/fetch:error',
|
||||
// i18n
|
||||
I18N_REGISTER_LOCALE: 'i18n/register:locale'
|
||||
I18N_REGISTER_LOCALE: 'i18n/register:locale'
|
||||
};
|
||||
|
||||
+16
-17
@@ -10,16 +10,16 @@ const SUPPORTED = (
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize the navigation
|
||||
* Initialize the history
|
||||
*/
|
||||
function activate() {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch({
|
||||
type: ACTION_TYPES.NAVIGATION_ACTIVATE,
|
||||
type: ACTION_TYPES.HISTORY_ACTIVATE,
|
||||
listener: (location) => {
|
||||
location = Location.fromNative(location);
|
||||
const prevLocation = getState().navigation.location;
|
||||
const prevLocation = getState().history.location;
|
||||
|
||||
// Fetch page if required
|
||||
if (!prevLocation || location.pathname !== prevLocation.pathname) {
|
||||
@@ -31,7 +31,7 @@ function activate() {
|
||||
|
||||
// Update the location
|
||||
dispatch({
|
||||
type: ACTION_TYPES.NAVIGATION_UPDATE,
|
||||
type: ACTION_TYPES.HISTORY_UPDATE,
|
||||
location
|
||||
});
|
||||
}
|
||||
@@ -48,13 +48,13 @@ function activate() {
|
||||
*/
|
||||
function emit(to) {
|
||||
return (dispatch, getState) => {
|
||||
const { listeners, history } = getState().navigation;
|
||||
const { listeners, client } = getState().history;
|
||||
|
||||
if (!history) {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const location = Location.fromNative(history.location);
|
||||
const location = Location.fromNative(client.location);
|
||||
|
||||
to = to || listeners;
|
||||
|
||||
@@ -65,24 +65,24 @@ function emit(to) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the navigation
|
||||
* Cleanup the history
|
||||
*/
|
||||
function deactivate() {
|
||||
return { type: ACTION_TYPES.NAVIGATION_DEACTIVATE };
|
||||
return { type: ACTION_TYPES.HISTORY_DEACTIVATE };
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a new url into the navigation
|
||||
* Push a new url into the history
|
||||
* @param {String|Location} location
|
||||
* @return {Action} action
|
||||
*/
|
||||
function push(location) {
|
||||
return (dispatch, getState) => {
|
||||
const { history } = getState().navigation;
|
||||
const { client } = getState().history;
|
||||
location = Location.fromNative(location);
|
||||
|
||||
if (SUPPORTED) {
|
||||
history.push(location.toNative());
|
||||
client.push(location.toNative());
|
||||
} else {
|
||||
redirect(location.toString());
|
||||
}
|
||||
@@ -90,17 +90,17 @@ function push(location) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace current state in navigation
|
||||
* Replace current state in history
|
||||
* @param {String|Location} location
|
||||
* @return {Action} action
|
||||
*/
|
||||
function replace(location) {
|
||||
return (dispatch, getState) => {
|
||||
const { history } = getState().navigation;
|
||||
const { client } = getState().history;
|
||||
location = Location.fromNative(location);
|
||||
|
||||
if (SUPPORTED) {
|
||||
history.replace(location.toNative());
|
||||
client.replace(location.toNative());
|
||||
} else {
|
||||
redirect(location.toString());
|
||||
}
|
||||
@@ -125,7 +125,7 @@ function redirect(uri) {
|
||||
*/
|
||||
function listen(listener) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ACTION_TYPES.NAVIGATION_LISTEN, listener });
|
||||
dispatch({ type: ACTION_TYPES.HISTORY_LISTEN, listener });
|
||||
|
||||
// Trigger for existing listeners
|
||||
dispatch(emit([ listener ]));
|
||||
@@ -163,7 +163,6 @@ function fetchPage(pathname) {
|
||||
.catch(
|
||||
error => {
|
||||
// dispatch(redirect(pathname));
|
||||
console.error(error);
|
||||
dispatch({ type: ACTION_TYPES.PAGE_FETCH_ERROR, error });
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
const React = require('react');
|
||||
const ReactRedux = require('react-redux');
|
||||
const Navigation = require('../actions/navigation');
|
||||
const History = require('../actions/history');
|
||||
|
||||
/**
|
||||
* Check if an element is inside a link
|
||||
@@ -83,7 +83,7 @@ const PJAXWrapper = React.createClass({
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
dispatch(Navigation.push(href));
|
||||
dispatch(History.push(href));
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
@@ -9,7 +9,7 @@ const composeReducer = require('./composeReducer');
|
||||
|
||||
const Components = require('../actions/components');
|
||||
const I18n = require('../actions/i18n');
|
||||
const Navigation = require('../actions/navigation');
|
||||
const History = require('../actions/history');
|
||||
|
||||
const isBrowser = (typeof window !== 'undefined');
|
||||
|
||||
@@ -21,7 +21,7 @@ const isBrowser = (typeof window !== 'undefined');
|
||||
const corePlugin = new Plugin({
|
||||
reduce: coreReducers,
|
||||
actions: {
|
||||
Components, I18n, Navigation
|
||||
Components, I18n, History
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const { InjectedComponent } = require('../components/InjectedComponent');
|
||||
const PJAXWrapper = require('../components/PJAXWrapper');
|
||||
const I18nProvider = require('../components/I18nProvider');
|
||||
const ContextProvider = require('../components/ContextProvider');
|
||||
const Navigation = require('../actions/navigation');
|
||||
const History = require('../actions/history');
|
||||
const contextShape = require('../shapes/context');
|
||||
|
||||
const GitBookApplication = React.createClass({
|
||||
@@ -15,12 +15,12 @@ const GitBookApplication = React.createClass({
|
||||
|
||||
componentDidMount() {
|
||||
const { context } = this.props;
|
||||
context.dispatch(Navigation.activate());
|
||||
context.dispatch(History.activate());
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
const { context } = this.props;
|
||||
context.dispatch(Navigation.deactivate());
|
||||
context.dispatch(History.deactivate());
|
||||
},
|
||||
|
||||
render() {
|
||||
|
||||
+14
-14
@@ -4,7 +4,7 @@ const ACTION_TYPES = require('../actions/TYPES');
|
||||
|
||||
const isServerSide = (typeof window === 'undefined');
|
||||
|
||||
const NavigationState = Record({
|
||||
const HistoryState = Record({
|
||||
// Current location
|
||||
location: null,
|
||||
// Are we loading a new page
|
||||
@@ -15,12 +15,12 @@ const NavigationState = Record({
|
||||
listeners: List(),
|
||||
// Function to call to stop listening
|
||||
unlisten: null,
|
||||
// History instance
|
||||
history: null
|
||||
// HistoryJS instance
|
||||
client: null
|
||||
});
|
||||
|
||||
function reduceNavigation(state, action) {
|
||||
state = state || NavigationState();
|
||||
function reduceHistory(state, action) {
|
||||
state = state || HistoryState();
|
||||
switch (action.type) {
|
||||
|
||||
case ACTION_TYPES.PAGE_FETCH_START:
|
||||
@@ -39,33 +39,33 @@ function reduceNavigation(state, action) {
|
||||
error: action.error
|
||||
});
|
||||
|
||||
case ACTION_TYPES.NAVIGATION_ACTIVATE:
|
||||
const history = isServerSide ? createMemoryHistory() : createBrowserHistory();
|
||||
const unlisten = history.listen(action.listener);
|
||||
case ACTION_TYPES.HISTORY_ACTIVATE:
|
||||
const client = isServerSide ? createMemoryHistory() : createBrowserHistory();
|
||||
const unlisten = client.listen(action.listener);
|
||||
|
||||
// We can't use .merge since it convert history to an immutable
|
||||
const newState = state
|
||||
.set('history', history)
|
||||
.set('client', client)
|
||||
.set('unlisten', unlisten);
|
||||
|
||||
return newState;
|
||||
|
||||
case ACTION_TYPES.NAVIGATION_DEACTIVATE:
|
||||
case ACTION_TYPES.HISTORY_DEACTIVATE:
|
||||
if (state.unlisten) {
|
||||
state.unlisten();
|
||||
}
|
||||
|
||||
return state.merge({
|
||||
history: null,
|
||||
client: null,
|
||||
unlisten: null
|
||||
});
|
||||
|
||||
case ACTION_TYPES.NAVIGATION_UPDATE:
|
||||
case ACTION_TYPES.HISTORY_UPDATE:
|
||||
return state.merge({
|
||||
location: action.location
|
||||
});
|
||||
|
||||
case ACTION_TYPES.NAVIGATION_LISTEN:
|
||||
case ACTION_TYPES.HISTORY_LISTEN:
|
||||
return state.merge({
|
||||
listeners: state.listeners.push(action.listener)
|
||||
});
|
||||
@@ -76,4 +76,4 @@ function reduceNavigation(state, action) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = reduceNavigation;
|
||||
module.exports = reduceHistory;
|
||||
@@ -3,7 +3,7 @@ const createReducer = require('../lib/createReducer');
|
||||
|
||||
module.exports = composeReducer(
|
||||
createReducer('components', require('./components')),
|
||||
createReducer('navigation', require('./navigation')),
|
||||
createReducer('history', require('./history')),
|
||||
createReducer('i18n', require('./i18n')),
|
||||
// GitBook JSON
|
||||
createReducer('config', require('./config')),
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
const React = require('react');
|
||||
const locationShape = require('./Location');
|
||||
const {
|
||||
bool,
|
||||
shape
|
||||
} = React.PropTypes;
|
||||
|
||||
module.exports = shape({
|
||||
loading: bool,
|
||||
location: locationShape
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
const React = require('react');
|
||||
const { map } = require('react-immutable-proptypes');
|
||||
const {
|
||||
string,
|
||||
shape
|
||||
} = React.PropTypes;
|
||||
|
||||
module.exports = shape({
|
||||
pathname: string,
|
||||
hash: string,
|
||||
query: map
|
||||
});
|
||||
@@ -8,6 +8,8 @@ module.exports = {
|
||||
Context: require('./Context'),
|
||||
Page: require('./Page'),
|
||||
File: require('./File'),
|
||||
History: require('./History'),
|
||||
Location: require('./Location'),
|
||||
Readme: require('./Readme'),
|
||||
Summary: require('./Summary'),
|
||||
SummaryPart: require('./SummaryPart'),
|
||||
|
||||
@@ -18,9 +18,9 @@ const onLocationChange = (location, dispatch) => {
|
||||
};
|
||||
|
||||
module.exports = GitBook.createPlugin({
|
||||
activate: (dispatch, getState, { Navigation, Components }) => {
|
||||
activate: (dispatch, getState, { History, Components }) => {
|
||||
// Register the navigation handler
|
||||
dispatch(Navigation.listen(onLocationChange));
|
||||
dispatch(History.listen(onLocationChange));
|
||||
|
||||
// Register components
|
||||
dispatch(Components.registerComponent(SearchInput, { role: 'search:input' }));
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
.LoadingBar {
|
||||
pointer-events: none;
|
||||
transition: 400ms linear all;
|
||||
|
||||
.LoadingBar-Bar {
|
||||
background: @color-primary;
|
||||
height: 2px;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
width: 100%;
|
||||
border-radius: 0 1px 1px 0;
|
||||
transition: width 350ms;
|
||||
}
|
||||
|
||||
.LoadingBar-Shadow {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 70px;
|
||||
height: 2px;
|
||||
border-radius: 50%;
|
||||
opacity: .45;
|
||||
box-shadow: @color-primary 1px 0 6px 1px;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
@import "Search.less";
|
||||
@import "Body.less";
|
||||
@import "Dropdown.less";
|
||||
@import "LoadingBar.less";
|
||||
|
||||
* {
|
||||
.box-sizing(border-box);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Colors
|
||||
@color-primary: hsl(207, 100%, 50%); // rgb(44, 106, 254);
|
||||
// Fonts
|
||||
@font-family-serif: Georgia, serif;
|
||||
@font-family-sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
@@ -16,7 +18,7 @@
|
||||
@summary-article-padding-v: 10px;
|
||||
@summary-article-padding-h: 15px;
|
||||
@summary-article-color: hsl(207, 15%, 25%);
|
||||
@summary-article-hover-color: hsl(207, 100%, 50%);
|
||||
@summary-article-hover-color: @color-primary;
|
||||
@summary-article-active-color: @summary-article-color;
|
||||
@summary-article-active-background: #f5f5f5;
|
||||
// Page
|
||||
|
||||
@@ -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;
|
||||
@@ -3,6 +3,7 @@ const { React } = GitBook;
|
||||
|
||||
const Sidebar = require('./Sidebar');
|
||||
const Body = require('./Body');
|
||||
const LoadingBar = require('./LoadingBar');
|
||||
|
||||
const Theme = React.createClass({
|
||||
propTypes: {
|
||||
@@ -10,16 +11,18 @@ const Theme = React.createClass({
|
||||
page: GitBook.Shapes.Page,
|
||||
summary: GitBook.Shapes.Summary,
|
||||
readme: GitBook.Shapes.Readme,
|
||||
history: GitBook.Shapes.History,
|
||||
sidebar: React.PropTypes.object,
|
||||
// Other props
|
||||
children: React.PropTypes.node
|
||||
},
|
||||
|
||||
render() {
|
||||
const { page, summary, children, sidebar, readme } = this.props;
|
||||
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" />
|
||||
@@ -43,6 +46,6 @@ const Theme = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = GitBook.connect(Theme, ({page, summary, sidebar, readme}) => {
|
||||
return { page, summary, sidebar, readme };
|
||||
module.exports = GitBook.connect(Theme, ({page, summary, sidebar, readme, history}) => {
|
||||
return { page, summary, sidebar, readme, history };
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user