diff options
Diffstat (limited to 'packages/gitbook-core/src/lib')
-rw-r--r-- | packages/gitbook-core/src/lib/bootstrap.js | 29 | ||||
-rw-r--r-- | packages/gitbook-core/src/lib/composeReducer.js | 16 | ||||
-rw-r--r-- | packages/gitbook-core/src/lib/connect.js | 70 | ||||
-rw-r--r-- | packages/gitbook-core/src/lib/createContext.js | 76 | ||||
-rw-r--r-- | packages/gitbook-core/src/lib/createPlugin.js | 27 | ||||
-rw-r--r-- | packages/gitbook-core/src/lib/createReducer.js | 27 | ||||
-rw-r--r-- | packages/gitbook-core/src/lib/getPayload.js | 19 | ||||
-rw-r--r-- | packages/gitbook-core/src/lib/renderWithContext.js | 55 |
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; |