summaryrefslogtreecommitdiffstats
path: root/packages/gitbook-core/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'packages/gitbook-core/src/lib')
-rw-r--r--packages/gitbook-core/src/lib/bootstrap.js29
-rw-r--r--packages/gitbook-core/src/lib/composeReducer.js16
-rw-r--r--packages/gitbook-core/src/lib/connect.js70
-rw-r--r--packages/gitbook-core/src/lib/createContext.js76
-rw-r--r--packages/gitbook-core/src/lib/createPlugin.js27
-rw-r--r--packages/gitbook-core/src/lib/createReducer.js27
-rw-r--r--packages/gitbook-core/src/lib/getPayload.js19
-rw-r--r--packages/gitbook-core/src/lib/renderWithContext.js55
8 files changed, 319 insertions, 0 deletions
diff --git a/packages/gitbook-core/src/lib/bootstrap.js b/packages/gitbook-core/src/lib/bootstrap.js
new file mode 100644
index 0000000..f3c99b7
--- /dev/null
+++ b/packages/gitbook-core/src/lib/bootstrap.js
@@ -0,0 +1,29 @@
+const ReactDOM = require('react-dom');
+
+const getPayload = require('./getPayload');
+const createContext = require('./createContext');
+const renderWithContext = require('./renderWithContext');
+
+/**
+ * Bootstrap GitBook on the browser (this function should not be called on the server side).
+ * @param {Object} matching
+ */
+function bootstrap(matching) {
+ const initialState = getPayload(window.document);
+ const plugins = window.gitbookPlugins;
+
+ const mountNode = document.getElementById('content');
+
+ // Create the redux store
+ const context = createContext(plugins, initialState);
+
+ window.gitbookContext = context;
+
+ // Render with the store
+ const el = renderWithContext(context, matching);
+
+ ReactDOM.render(el, mountNode);
+}
+
+
+module.exports = bootstrap;
diff --git a/packages/gitbook-core/src/lib/composeReducer.js b/packages/gitbook-core/src/lib/composeReducer.js
new file mode 100644
index 0000000..fa2a589
--- /dev/null
+++ b/packages/gitbook-core/src/lib/composeReducer.js
@@ -0,0 +1,16 @@
+
+/**
+ * Compose multiple reducers into one
+ * @param {Function} reducers
+ * @return {Function}
+ */
+function composeReducer(...reducers) {
+ return (state, action) => {
+ return reducers.reduce(
+ (newState, reducer) => reducer(newState, action),
+ state
+ );
+ };
+}
+
+module.exports = composeReducer;
diff --git a/packages/gitbook-core/src/lib/connect.js b/packages/gitbook-core/src/lib/connect.js
new file mode 100644
index 0000000..a34299d
--- /dev/null
+++ b/packages/gitbook-core/src/lib/connect.js
@@ -0,0 +1,70 @@
+const React = require('react');
+const ReactRedux = require('react-redux');
+const { injectIntl } = require('react-intl');
+
+const ContextShape = require('../propTypes/Context');
+
+/**
+ * Use the GitBook context provided by ContextProvider to map actions to props
+ * @param {ReactComponent} Component
+ * @param {Function} mapActionsToProps
+ * @return {ReactComponent}
+ */
+function connectToActions(Component, mapActionsToProps) {
+ if (!mapActionsToProps) {
+ return Component;
+ }
+
+ return React.createClass({
+ displayName: `ConnectActions(${Component.displayName})`,
+ propTypes: {
+ children: React.PropTypes.node
+ },
+
+ contextTypes: {
+ gitbook: ContextShape.isRequired
+ },
+
+ render() {
+ const { gitbook } = this.context;
+ const { children, ...props } = this.props;
+ const { actions, store } = gitbook;
+
+ const actionsProps = mapActionsToProps(actions, store.dispatch);
+
+ return <Component {...props} {...actionsProps}>{children}</Component>;
+ }
+ });
+}
+
+/**
+ * Connect to i18n
+ * @param {ReactComponent} Component
+ * @return {ReactComponent}
+ */
+function connectToI18n(Component) {
+ return injectIntl(({intl, children, ...props}) => {
+ const i18n = {
+ t: (id, values) => intl.formatMessage({ id }, values)
+ };
+
+ return <Component {...props} i18n={i18n}>{children}</Component>;
+ });
+}
+
+/**
+ * Connect a component to the GitBook context (store and actions).
+ *
+ * @param {ReactComponent} Component
+ * @param {Function} mapStateToProps
+ * @return {ReactComponent}
+ */
+function connect(Component, mapStateToProps, mapActionsToProps) {
+ Component = ReactRedux.connect(mapStateToProps)(Component);
+ Component = connectToI18n(Component);
+ Component = connectToActions(Component, mapActionsToProps);
+
+ return Component;
+}
+
+module.exports = connect;
diff --git a/packages/gitbook-core/src/lib/createContext.js b/packages/gitbook-core/src/lib/createContext.js
new file mode 100644
index 0000000..ba0c7e1
--- /dev/null
+++ b/packages/gitbook-core/src/lib/createContext.js
@@ -0,0 +1,76 @@
+/* eslint-disable no-console */
+const Redux = require('redux');
+const ReduxThunk = require('redux-thunk').default;
+
+const Plugin = require('../models/Plugin');
+const Context = require('../models/Context');
+const coreReducers = require('../reducers');
+const composeReducer = require('./composeReducer');
+
+const Components = require('../actions/components');
+const I18n = require('../actions/i18n');
+const History = require('../actions/history');
+
+const isBrowser = (typeof window !== 'undefined');
+
+/**
+ * The core plugin defines the defualt behaviour of GitBook and provides
+ * actions to other plugins.
+ * @type {Plugin}
+ */
+const corePlugin = new Plugin({
+ reduce: coreReducers,
+ actions: {
+ Components, I18n, History
+ }
+});
+
+/**
+ * Create a new context containing redux store from an initial state and a list of plugins.
+ * Each plugin entry is the result of {createPlugin}.
+ *
+ * @param {Array<Plugin>} plugins
+ * @param {Object} initialState
+ * @return {Context} context
+ */
+function createContext(plugins, initialState) {
+ plugins = [corePlugin].concat(plugins);
+
+ // Compose the reducer from core with plugins
+ const pluginReducers = plugins.map(plugin => plugin.reduce);
+ const reducer = composeReducer(...pluginReducers);
+
+ // Get actions from all plugins
+ const actions = plugins.reduce((accu, plugin) => {
+ return Object.assign(accu, plugin.actions);
+ }, {});
+
+ // Create thunk middleware which include actions
+ const thunk = ReduxThunk.withExtraArgument(actions);
+
+ // Create the redux store
+ const store = Redux.createStore(
+ (state, action) => {
+ if (isBrowser) {
+ console.log('[store]', action.type);
+ }
+ return reducer(state, action);
+ },
+ initialState,
+ Redux.compose(Redux.applyMiddleware(thunk))
+ );
+
+ // Create the context
+ const context = new Context({
+ store,
+ plugins,
+ actions
+ });
+
+ // Initialize the plugins
+ context.activate();
+
+ return context;
+}
+
+module.exports = createContext;
diff --git a/packages/gitbook-core/src/lib/createPlugin.js b/packages/gitbook-core/src/lib/createPlugin.js
new file mode 100644
index 0000000..cb5d2be
--- /dev/null
+++ b/packages/gitbook-core/src/lib/createPlugin.js
@@ -0,0 +1,27 @@
+const Plugin = require('../models/Plugin');
+
+/**
+ * Create a plugin to extend the state and the views.
+ *
+ * @param {Function(dispatch, state)} plugin.init
+ * @param {Function(state, action)} plugin.reduce
+ * @param {Object} plugin.actions
+ * @return {Plugin}
+ */
+function createPlugin({ activate, deactivate, reduce, actions }) {
+ const plugin = new Plugin({
+ activate,
+ deactivate,
+ reduce,
+ actions
+ });
+
+ if (typeof window !== 'undefined') {
+ window.gitbookPlugins = window.gitbookPlugins || [];
+ window.gitbookPlugins.push(plugin);
+ }
+
+ return plugin;
+}
+
+module.exports = createPlugin;
diff --git a/packages/gitbook-core/src/lib/createReducer.js b/packages/gitbook-core/src/lib/createReducer.js
new file mode 100644
index 0000000..2ebecfb
--- /dev/null
+++ b/packages/gitbook-core/src/lib/createReducer.js
@@ -0,0 +1,27 @@
+
+/**
+ * Helper to create a reducer that extend the store.
+ *
+ * @param {String} property
+ * @param {Function(state, action): state} reduce
+ * @return {Function(state, action): state}
+ */
+function createReducer(name, reduce) {
+ return (state, action) => {
+ const value = state[name];
+ const newValue = reduce(value, action);
+
+ if (newValue === value) {
+ return state;
+ }
+
+ const newState = {
+ ...state,
+ [name]: newValue
+ };
+
+ return newState;
+ };
+}
+
+module.exports = createReducer;
diff --git a/packages/gitbook-core/src/lib/getPayload.js b/packages/gitbook-core/src/lib/getPayload.js
new file mode 100644
index 0000000..2d54b9e
--- /dev/null
+++ b/packages/gitbook-core/src/lib/getPayload.js
@@ -0,0 +1,19 @@
+
+/**
+ * Get the payload for a GitBook page
+ * @param {String|DOMDocument} html
+ * @return {Object}
+ */
+function getPayload(html) {
+ if (typeof html === 'string') {
+ const parser = new DOMParser();
+ html = parser.parseFromString(html, 'text/html');
+ }
+
+ const script = html.querySelector('script[type="application/payload+json"]');
+ const payload = JSON.parse(script.innerHTML);
+
+ return payload;
+}
+
+module.exports = getPayload;
diff --git a/packages/gitbook-core/src/lib/renderWithContext.js b/packages/gitbook-core/src/lib/renderWithContext.js
new file mode 100644
index 0000000..dc7e1f2
--- /dev/null
+++ b/packages/gitbook-core/src/lib/renderWithContext.js
@@ -0,0 +1,55 @@
+const React = require('react');
+
+const { InjectedComponent } = require('../components/InjectedComponent');
+const PJAXWrapper = require('../components/PJAXWrapper');
+const I18nProvider = require('../components/I18nProvider');
+const ContextProvider = require('../components/ContextProvider');
+const History = require('../actions/history');
+const contextShape = require('../propTypes/context');
+
+const GitBookApplication = React.createClass({
+ propTypes: {
+ context: contextShape,
+ matching: React.PropTypes.object
+ },
+
+ componentDidMount() {
+ const { context } = this.props;
+ context.dispatch(History.activate());
+ },
+
+ componentWillUnmount() {
+ const { context } = this.props;
+ context.dispatch(History.deactivate());
+ },
+
+ render() {
+ const { context, matching } = this.props;
+
+ return (
+ <ContextProvider context={context}>
+ <PJAXWrapper>
+ <I18nProvider>
+ <InjectedComponent matching={matching} />
+ </I18nProvider>
+ </PJAXWrapper>
+ </ContextProvider>
+ );
+ }
+});
+
+
+/**
+ * Render the application for a GitBook context.
+ *
+ * @param {GitBookContext} context
+ * @param {Object} matching
+ * @return {React.Element} element
+ */
+function renderWithContext(context, matching) {
+ return (
+ <GitBookApplication context={context} matching={matching} />
+ );
+}
+
+module.exports = renderWithContext;