Change api for registering components

This commit is contained in:
Samy Pesse
2016-10-01 14:14:30 +02:00
parent 2cf507a807
commit da83d9e91d
23 changed files with 291 additions and 105 deletions
+44
View File
@@ -0,0 +1,44 @@
# Internationalize your plugin
GitBook has built-in support for internationalization. Plugins can register new languages and provide the right messages for different languages.
### Register locale and messages
The first step is to register messages for a language:
```js
module.exports = GitBook.createPlugin({
init: (dispatch) => {
dispatch(GitBook.registerLocale('en-US', {
MY_PLUGIN_MESSAGE: 'Hello World'
}));
}
});
```
### Render a message in a component
`GitBook.connect` adds a `i18n` prop to access the internationalization:
```js
const GitBook = require('gitbook-core');
const { React } = GitBook;
const MyButton = React.createClass({
propTypes: {
i18n: GitBook.Shapes.i18n
},
render() {
const { i18n } = this.props;
return (
<GitBook.Button>
{i18.t('MY_PLUGIN_MESSAGE')}
</GitBook.Button>
);
}
});
module.exports = GitBook.connect(MyButton);
```
@@ -1,25 +1,27 @@
const React = require('react');
const { Provider } = require('react-redux');
const ContextShape = require('../shapes/Context');
/**
* React component to provide a GitBook context to children components.
*/
const ContextProvider = React.createClass({
propTypes: {
context: React.PropTypes.object.isRequired,
context: ContextShape.isRequired,
children: React.PropTypes.node
},
childContextTypes: {
gitbookContext: React.PropTypes.object.isRequired
gitbook: ContextShape
},
getChildContext() {
const { context } = this.props;
return {
gitbookContext: context
gitbook: context
};
},
@@ -19,4 +19,8 @@ const I18nProvider = React.createClass({
}
});
module.exports = ReactRedux.connect()(I18nProvider);
const mapStateToProps = state => {
return { messages: state.i18n.messages };
};
module.exports = ReactRedux.connect(mapStateToProps)(I18nProvider);
+19 -11
View File
@@ -1,8 +1,10 @@
const React = require('react');
const ReactRedux = require('react-redux');
const path = require('path');
const url = require('url');
const File = require('../models/File');
const SummaryArticle = require('../models/SummaryArticle');
const SummaryArticleShape = require('../shapes/SummaryArticle');
const ContextShape = require('../shapes/Context');
const Link = React.createClass({
propTypes: {
@@ -14,25 +16,31 @@ const Link = React.createClass({
])
},
contextTypes: {
gitbook: ContextShape.isRequired
},
getHref() {
const { to, href } = this.props;
const { gitbook } = this.context;
let { to, href } = this.props;
if (href) {
return href;
}
if (SummaryArticle.is(to)) {
return to.toURL(gitbook);
}
if (typeof to === 'string') {
return to;
to = new File(to);
}
// Article
if (typeof to.ref === 'string') {
const parts = url.parse(to.ref);
const ext = path.extname(parts.pathname);
const pathname = path.basename(parts.pathname, ext) + '.html';
return pathname + (parts.hash || '');
if (File.is(to)) {
return to.toURL(gitbook);
}
throw new Error('Invalid format for prop "to"');
},
render() {
-2
View File
@@ -15,7 +15,6 @@ const Icon = require('./components/Icon');
const Button = require('./components/Button');
const I18nProvider = require('./components/I18nProvider');
const { registerComponent } = require('./actions/components');
const ACTIONS = require('./actions/TYPES');
const Shapes = require('./shapes');
@@ -36,7 +35,6 @@ module.exports = {
createReducer,
createContext,
composeReducer,
registerComponent,
// React Components
I18nProvider,
InjectedComponent,
+5 -3
View File
@@ -2,6 +2,8 @@ const React = require('react');
const ReactRedux = require('react-redux');
const { injectIntl } = require('react-intl');
const ContextShape = require('../shapes/Context');
/**
* Use the GitBook context provided by ContextProvider to map actions to props
* @param {ReactComponent} Component
@@ -20,13 +22,13 @@ function connectToActions(Component, mapActionsToProps) {
},
contextTypes: {
gitbookContext: React.PropTypes.object.isRequired
gitbook: ContextShape.isRequired
},
render() {
const { gitbookContext } = this.context;
const { gitbook } = this.context;
const { children, ...props } = this.props;
const { actions, store } = gitbookContext;
const { actions, store } = gitbook;
const actionsProps = mapActionsToProps(actions, store.dispatch);
+17 -8
View File
@@ -1,15 +1,22 @@
/* eslint-disable no-console */
const { Record } = require('immutable');
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 GitBookContext = Record({
store: null,
actions: {}
}, 'GitBookContext');
const Components = require('./actions/components');
const I18n = require('./actions/i18n');
const Navigation = require('./actions/navigation');
const corePlugin = Plugin({
reduce: coreReducers,
actions: {
Components, I18n, Navigation
}
});
/**
* Create a new context containing redux store from an initial state and a list of plugins.
@@ -17,12 +24,14 @@ const GitBookContext = Record({
*
* @param {Array<Plugin>} plugins
* @param {Object} initialState
* @return {GitBookContext} context
* @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(...[coreReducers].concat(pluginReducers));
const reducer = composeReducer(pluginReducers);
// Get actions from all plugins
const actions = plugins.reduce((accu, plugin) => {
@@ -43,7 +52,7 @@ function createContext(plugins, initialState) {
plugin.init(store.dispatch, store.getState, actions);
});
return GitBookContext({
return Context({
store,
actions
});
+6 -11
View File
@@ -1,24 +1,19 @@
const defaultInit = ((dispatch, getState) => {});
const defaultReduce = ((state, action) => state);
const Plugin = require('../models/Plugin');
/**
* Create a plugin to extend the state and the views.
*
* @param {Function(dispatch, state)} onInitialState
* @param {Funciton(state, action)} onReduceState
* @param {Function(dispatch, state)} plugin.init
* @param {Function(state, action)} plugin.reduce
* @param {Object} plugin.actions
* @return {Plugin}
*/
function createPlugin({ init, reduce, actions }) {
init = init || defaultInit;
reduce = reduce || defaultReduce;
actions = actions || {};
const plugin = {
const plugin = Plugin({
init,
reduce,
actions
};
});
if (typeof window !== 'undefined') {
window.gitbookPlugins = window.gitbookPlugins || [];
@@ -0,0 +1,29 @@
const { Record } = require('immutable');
const DEFAULTS = {
store: null,
actions: {}
};
class Context extends Record(DEFAULTS) {
/**
* Return current state
* @return {Object}
*/
getState() {
const { store } = this;
return store.getState();
}
/**
* Dispatch an action
* @param {Action} action
*/
dispatch(action) {
const { store } = this;
return store.dispatch(action);
}
}
module.exports = Context;
+43
View File
@@ -0,0 +1,43 @@
const path = require('path');
const { Record } = require('immutable');
const DEFAULTS = {
type: '',
mtime: '',
path: ''
};
class File extends Record(DEFAULTS) {
constructor(file) {
if (typeof file === 'string') {
file = { path: file };
}
super(file);
}
/**
* Return url for a file in a GitBook context.
* @param {Context} context
* @return {String} url
*/
toURL(context) {
const { file } = context.getState();
return path.relative(
path.dirname(file.path),
this.path
);
}
static is(file) {
return (file instanceof File);
}
static create(file) {
return file instanceof File ?
file : new File(file);
}
}
module.exports = File;
@@ -0,0 +1,13 @@
const { Record } = require('immutable');
const DEFAULTS = {
init: ((dispatch, getState) => {}),
reduce: ((state, action) => state),
actions: {}
};
class Plugin extends Record(DEFAULTS) {
}
module.exports = Plugin;
@@ -0,0 +1,57 @@
const url = require('url');
const path = require('path');
const { Record, List } = require('immutable');
const File = require('./File');
const OUTPUT_EXTENSION = '.html';
const DEFAULTS = {
title: '',
depth: 0,
path: '',
ref: '',
level: '',
articles: List()
};
class SummaryArticle extends Record(DEFAULTS) {
constructor(article) {
super({
...article,
articles: (new List(article.articles))
.map(art => new SummaryArticle(art))
});
}
/**
* Return url for a file in a GitBook context.
* @param {Context} context
* @return {String} url
*/
toURL(context) {
const { readme } = context.getState();
const fileReadme = readme.file;
const parts = url.parse(this.ref);
if (parts.protocol) {
return this.ref;
}
const file = new File(parts.pathname);
let filePath = file.toURL(context);
// Change extension and resolve to .html
if (
path.basename(filePath, path.extname(filePath)) == 'README' ||
(fileReadme && filePath == fileReadme.path)
) {
filePath = path.join(path.dirname(filePath), 'index' + OUTPUT_EXTENSION);
} else {
filePath = path.basename(filePath, path.extname(filePath)) + OUTPUT_EXTENSION;
}
return filePath + (parts.hash || '');
}
}
module.exports = SummaryArticle;
@@ -0,0 +1,17 @@
const { Record, List } = require('immutable');
const SummaryArticle = require('./SummaryArticle');
class SummaryPart extends Record({
title: '',
articles: List()
}) {
constructor(state) {
super({
...state,
articles: (new List(state.articles))
.map(article => new SummaryArticle(article))
});
}
}
module.exports = SummaryPart;
@@ -1,16 +0,0 @@
const { Record } = require('immutable');
const DEFAULTS = {
type: '',
mtime: '',
path: ''
};
class FileState extends Record(DEFAULTS) {
static create(state) {
return state instanceof FileState ?
state : new FileState(state);
}
}
module.exports = FileState;
+3 -3
View File
@@ -1,12 +1,12 @@
const { Record } = require('immutable');
const FileState = require('./file');
const File = require('../models/file');
class ReadmeState extends Record({
file: new FileState()
file: new File()
}) {
constructor(state = {}) {
super({
file: new FileState(state.file)
file: new File(state.file)
});
}
+4 -32
View File
@@ -1,45 +1,17 @@
const { Record, List } = require('immutable');
const FileState = require('./file');
class SummaryArticle extends Record({
title: '',
depth: 0,
path: '',
ref: '',
level: '',
articles: List()
}) {
constructor(state) {
super({
...state,
articles: (new List(state.articles))
.map(article => new SummaryArticle(article))
});
}
}
class SummaryPart extends Record({
title: '',
articles: List()
}) {
constructor(state) {
super({
...state,
articles: (new List(state.articles))
.map(article => new SummaryArticle(article))
});
}
}
const File = require('../models/File');
const SummaryPart = require('../models/SummaryPart');
class SummaryState extends Record({
file: new FileState(),
file: new File(),
parts: List()
}) {
constructor(state = {}) {
super({
...state,
file: new FileState(state.file),
file: new File(state.file),
parts: (new List(state.parts))
.map(article => new SummaryPart(article))
});
@@ -0,0 +1,11 @@
const React = require('react');
const {
object,
shape
} = React.PropTypes;
module.exports = shape({
store: object,
actions: object
});
@@ -5,6 +5,7 @@ module.exports = {
...ImmutablePropTypes,
dispatch: React.PropTypes.func,
i18n: require('./i18n'),
Context: require('./Context'),
Page: require('./Page'),
File: require('./File'),
Summary: require('./Summary'),
+2 -2
View File
@@ -34,7 +34,7 @@ const HintAlert = React.createClass({
});
module.exports = GitBook.createPlugin({
init: (dispatch, getState) => {
dispatch(GitBook.registerComponent(HintAlert, { role: 'block:hint' }));
init: (dispatch, getState, { Components }) => {
dispatch(Components.registerComponent(HintAlert, { role: 'block:hint' }));
}
});
+2 -3
View File
@@ -13,9 +13,8 @@ function searchHandler(query) {
}
module.exports = GitBook.createPlugin({
init: (dispatch, getState, actions) => {
const { search } = actions;
dispatch(search.registerHandler('lunr', searchHandler));
init: (dispatch, getState, { Search }) => {
dispatch(Search.registerHandler('lunr', searchHandler));
},
reduce: (state, action) => state
});
+5 -5
View File
@@ -3,15 +3,15 @@ const GitBook = require('gitbook-core');
const SearchInput = require('./components/Input');
const SearchResults = require('./components/Results');
const reducers = require('./reducers');
const search = require('./actions/search');
const Search = require('./actions/search');
module.exports = GitBook.createPlugin({
init: (dispatch, getState) => {
dispatch(GitBook.registerComponent(SearchInput, { role: 'search:input' }));
dispatch(GitBook.registerComponent(SearchResults, { role: 'search:results' }));
init: (dispatch, getState, { Components }) => {
dispatch(Components.registerComponent(SearchInput, { role: 'search:input' }));
dispatch(Components.registerComponent(SearchResults, { role: 'search:results' }));
},
reduce: reducers,
actions: {
search
Search
}
});
@@ -49,8 +49,8 @@ ThemeBody = GitBook.connect(ThemeBody, ({page, summary, sidebar}) => {
});
module.exports = GitBook.createPlugin({
init: (dispatch, state) => {
dispatch(GitBook.registerComponent(ThemeBody, { role: 'Body' }));
init: (dispatch, state, { Components }) => {
dispatch(Components.registerComponent(ThemeBody, { role: 'Body' }));
},
reduce: reduceState
});
+1 -3
View File
@@ -1,3 +1 @@
# `gitbook-plugin`
CLI utility to build and test plugins.
See [GitBook: Create a plugin](toolchain.gitbook.com/api/).