summaryrefslogtreecommitdiffstats
path: root/packages/gitbook-core/src
diff options
context:
space:
mode:
authorSamy Pesse <samypesse@gmail.com>2016-09-26 17:26:36 +0200
committerSamy Pesse <samypesse@gmail.com>2016-09-26 17:26:36 +0200
commitff42b58f7702b101bf71feb09a83396e7b51dd09 (patch)
tree52193bcc3cc766cbb2de6f032f326e235dcc4db9 /packages/gitbook-core/src
parent7fa3e34f9fe433ee99f797c745ef0f54b5efd89a (diff)
downloadgitbook-ff42b58f7702b101bf71feb09a83396e7b51dd09.zip
gitbook-ff42b58f7702b101bf71feb09a83396e7b51dd09.tar.gz
gitbook-ff42b58f7702b101bf71feb09a83396e7b51dd09.tar.bz2
Add basic for pjax navigation
Diffstat (limited to 'packages/gitbook-core/src')
-rw-r--r--packages/gitbook-core/src/actions/navigation.js106
-rw-r--r--packages/gitbook-core/src/components/Link.js3
-rw-r--r--packages/gitbook-core/src/components/PJAXWrapper.js112
-rw-r--r--packages/gitbook-core/src/reducers/index.js2
-rw-r--r--packages/gitbook-core/src/reducers/page.js13
-rw-r--r--packages/gitbook-core/src/renderWithStore.js5
6 files changed, 237 insertions, 4 deletions
diff --git a/packages/gitbook-core/src/actions/navigation.js b/packages/gitbook-core/src/actions/navigation.js
index 0d00687..af0fdff 100644
--- a/packages/gitbook-core/src/actions/navigation.js
+++ b/packages/gitbook-core/src/actions/navigation.js
@@ -1,13 +1,116 @@
+const ACTION_TYPES = require('./TYPES');
+const getPayload = require('../lib/getPayload');
+const SUPPORTED = (
+ typeof window !== 'undefined' &&
+ window.history && window.history.pushState && window.history.replaceState &&
+ // pushState isn't reliable on iOS until 5.
+ !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
+);
+
+let PUSH_ID = 0;
+
+/**
+ * Generate a new state to be pushed or replaced
+ * @param {Object}
+ */
+function genState() {
+ return {
+ id: (PUSH_ID++)
+ };
+}
+
+/**
+ * Push a new url into the navigation
+ * @param {String} uri
+ * @return {Action} action
+ */
+function pushURI(uri) {
+ return () => {
+ const state = genState();
+
+ if (SUPPORTED) {
+ window.history.pushState(state, '', uri);
+ } else {
+ redirect(uri);
+ }
+ };
+}
+
+/**
+ * Replace current state in navigation
+ * @param {String} uri
+ * @return {Action} action
+ */
+function replaceURI(uri) {
+ return () => {
+ const state = genState();
+
+ if (SUPPORTED) {
+ window.history.replaceState(state, '', uri);
+ } else {
+ redirect(uri);
+ }
+ };
+}
+
+/**
+ * Hard redirection
+ * @param {String} uri
+ * @return {Action} action
+ */
+function redirect(uri) {
+ return () => {
+ window.location.href = uri;
+ };
+}
/**
* Fetch a new page and update the store accordingly
* @param {String} uri
+ * @param {Boolean} options.replace
* @return {Action} action
*/
-function fetchPage(uri) {
+function fetchPage(uri, options) {
+ const { replace } = options;
+
return (dispatch, getState) => {
+ const prevURI = location.href;
+ dispatch({ type: ACTION_TYPES.PAGE_FETCH_START });
+
+ if (replace) {
+ dispatch(replaceURI(uri));
+ } else {
+ dispatch(pushURI(uri));
+ }
+
+ window.fetch(uri, {
+ credentials: 'include'
+ })
+ .then(
+ response => {
+ return response.text();
+ }
+ )
+ .then(
+ html => {
+ const payload = getPayload(html);
+
+ if (!payload) {
+ throw new Error('No payload found in page');
+ }
+
+ dispatch({ type: ACTION_TYPES.PAGE_FETCH_END, payload });
+ }
+ )
+ .catch(
+ error => {
+ dispatch(replaceURI(prevURI));
+ dispatch(redirect(uri));
+ dispatch({ type: ACTION_TYPES.PAGE_FETCH_ERROR, error });
+ }
+ );
};
}
@@ -21,6 +124,7 @@ function fetchArticle(article) {
}
module.exports = {
+ pushURI,
fetchPage,
fetchArticle
};
diff --git a/packages/gitbook-core/src/components/Link.js b/packages/gitbook-core/src/components/Link.js
index 2f909de..ade7461 100644
--- a/packages/gitbook-core/src/components/Link.js
+++ b/packages/gitbook-core/src/components/Link.js
@@ -1,4 +1,5 @@
const React = require('react');
+const ReactRedux = require('react-redux');
const SummaryArticleShape = require('../shapes/SummaryArticle');
const Link = React.createClass({
@@ -35,4 +36,4 @@ const Link = React.createClass({
}
});
-module.exports = Link;
+module.exports = ReactRedux.connect()(Link);
diff --git a/packages/gitbook-core/src/components/PJAXWrapper.js b/packages/gitbook-core/src/components/PJAXWrapper.js
new file mode 100644
index 0000000..f35a554
--- /dev/null
+++ b/packages/gitbook-core/src/components/PJAXWrapper.js
@@ -0,0 +1,112 @@
+const React = require('react');
+const ReactRedux = require('react-redux');
+const navigation = require('../actions/navigation');
+
+/**
+ * Check if an element is inside a link
+ * @param {DOMElement} el
+ * @param {String} name
+ * @return {DOMElement|undefined
+ */
+function findParentByTagName(el, name) {
+ while (el && el !== document) {
+ if (el.tagName && el.tagName.toUpperCase() === name) {
+ return el;
+ }
+ el = el.parentNode;
+ }
+
+ return false;
+}
+
+/**
+ * Internal: Return the `href` component of given URL object with the hash
+ * portion removed.
+ *
+ * @param {Location|HTMLAnchorElement} location
+ * @return {String}
+ */
+function stripHash(location) {
+ return location.href.replace(/#.*/, '');
+}
+
+/**
+ * Test if a click event should be handled,
+ * return the new url if it's a normal lcick
+ */
+function getHrefForEvent(event) {
+ const link = findParentByTagName(event.target, 'A');
+
+ if (!link)
+ return;
+
+ // Middle click, cmd click, and ctrl click should open
+ // links in a new tab as normal.
+ if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
+ return;
+
+ // Ignore cross origin links
+ if (location.protocol !== link.protocol || location.hostname !== link.hostname)
+ return;
+
+ // Ignore case when a hash is being tacked on the current URL
+ if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location))
+ return;
+
+ // Ignore event with default prevented
+ if (event.defaultPrevented)
+ return;
+
+ // Explicitly ignored
+ if (link.getAttribute('data-nopjax'))
+ return;
+
+ return link.href;
+}
+
+/*
+ Wrapper to bind all navigation events to fetch pages.
+ */
+
+const PJAXWrapper = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node,
+ dispatch: React.PropTypes.func
+ },
+
+ onClick(event) {
+ const { dispatch } = this.props;
+ const href = getHrefForEvent(event);
+
+ if (!href) {
+ return;
+ }
+
+ event.preventDefault();
+ dispatch(navigation.fetchPage(href));
+
+ },
+
+ onPopState(event) {
+ const { dispatch } = this.props;
+ event.preventDefault();
+
+ dispatch(navigation.fetchPage(location.href, { replace: true }));
+ },
+
+ componentDidMount() {
+ document.addEventListener('click', this.onClick, false);
+ window.addEventListener('popstate', this.onPopState, false);
+ },
+
+ componentWillUnmount() {
+ document.removeEventListener('click', this.onClick, false);
+ window.removeEventListener('popstate', this.onPopState, false);
+ },
+
+ render() {
+ return React.Children.only(this.props.children);
+ }
+});
+
+module.exports = ReactRedux.connect()(PJAXWrapper);
diff --git a/packages/gitbook-core/src/reducers/index.js b/packages/gitbook-core/src/reducers/index.js
index 49ea489..4785c1c 100644
--- a/packages/gitbook-core/src/reducers/index.js
+++ b/packages/gitbook-core/src/reducers/index.js
@@ -1,3 +1,5 @@
+const ACTION_TYPES = require('../actions/TYPES');
+
const composeReducer = require('../composeReducer');
const createReducer = require('../createReducer');
diff --git a/packages/gitbook-core/src/reducers/page.js b/packages/gitbook-core/src/reducers/page.js
index 98764c0..275fce7 100644
--- a/packages/gitbook-core/src/reducers/page.js
+++ b/packages/gitbook-core/src/reducers/page.js
@@ -1,4 +1,5 @@
const { Record } = require('immutable');
+const ACTION_TYPES = require('../actions/TYPES');
const DEFAULTS = {
title: '',
@@ -16,5 +17,15 @@ class PageState extends Record(DEFAULTS) {
}
module.exports = (state, action) => {
- return PageState.create(state);
+ state = PageState.create(state);
+
+ switch (action.type) {
+
+ case ACTION_TYPES.PAGE_FETCH_END:
+ return state.merge(action.payload.page);
+
+ default:
+ return state;
+
+ }
};
diff --git a/packages/gitbook-core/src/renderWithStore.js b/packages/gitbook-core/src/renderWithStore.js
index af86704..48fff89 100644
--- a/packages/gitbook-core/src/renderWithStore.js
+++ b/packages/gitbook-core/src/renderWithStore.js
@@ -1,6 +1,7 @@
const React = require('react');
const { Provider } = require('react-redux');
const { InjectedComponent } = require('./components/InjectedComponent');
+const PJAXWrapper = require('./components/PJAXWrapper');
/**
* Render the application for a store
@@ -10,7 +11,9 @@ const { InjectedComponent } = require('./components/InjectedComponent');
function renderWithStore(store) {
return (
<Provider store={store}>
- <InjectedComponent matching={{ role: 'Body' }} />
+ <PJAXWrapper>
+ <InjectedComponent matching={{ role: 'Body' }} />
+ </PJAXWrapper>
</Provider>
);
}