mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-23 02:53:29 +00:00
Change api for registering components
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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' }));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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 +1 @@
|
||||
# `gitbook-plugin`
|
||||
|
||||
CLI utility to build and test plugins.
|
||||
See [GitBook: Create a plugin](toolchain.gitbook.com/api/).
|
||||
|
||||
Reference in New Issue
Block a user