summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Gaborit <soreine.plume@gmail.com>2016-10-13 12:56:46 +0200
committerSamy Pessé <samypesse@gmail.com>2016-10-13 12:56:46 +0200
commit3aea3e5d88384822440517c9a2b722c405547155 (patch)
tree84ef0448c7e2e7ed95941f2795e86ca65f7cd809
parent95b3b4ebb7277f7a96ee79e5d75baafb3b5aab1e (diff)
downloadgitbook-3aea3e5d88384822440517c9a2b722c405547155.zip
gitbook-3aea3e5d88384822440517c9a2b722c405547155.tar.gz
gitbook-3aea3e5d88384822440517c9a2b722c405547155.tar.bz2
Adapt plugin sharing (#1553)
* Reuse old package config * Add plugin config shape * Add ButtonGroup to core components * List all sharing sites * Displaying buttons from config * First iteration of Dropdown component (need CSS) * Using Dropdown for sharing button * Create HotKeys component * Move Backdrop to its own file * Trying a cleaner API for Dropdown * Add README.md * livereload: Add missing gitbook-plugin dependency * sharing: Now use Immutable state * sharing: Adapt quickly to new Dropdown * sharing: Fix sharing from dropdown
-rw-r--r--packages/gitbook-core/package.json1
-rw-r--r--packages/gitbook-core/src/components/Backdrop.js38
-rw-r--r--packages/gitbook-core/src/components/ButtonGroup.js23
-rw-r--r--packages/gitbook-core/src/components/Dropdown.js141
-rw-r--r--packages/gitbook-core/src/components/HotKeys.js56
-rw-r--r--packages/gitbook-core/src/index.js6
-rw-r--r--packages/gitbook-plugin-livereload/package.json3
-rw-r--r--packages/gitbook-plugin-sharing/README.md38
-rw-r--r--packages/gitbook-plugin-sharing/package.json53
-rw-r--r--packages/gitbook-plugin-sharing/src/SITES.js72
-rw-r--r--packages/gitbook-plugin-sharing/src/index.js132
-rw-r--r--packages/gitbook-plugin-sharing/src/optionsShape.js20
12 files changed, 569 insertions, 14 deletions
diff --git a/packages/gitbook-core/package.json b/packages/gitbook-core/package.json
index b5b2d3f..7aea7fb 100644
--- a/packages/gitbook-core/package.json
+++ b/packages/gitbook-core/package.json
@@ -10,6 +10,7 @@
"history": "^4.3.0",
"html-tags": "^1.1.1",
"immutable": "^3.8.1",
+ "mousetrap": "1.6.0",
"react": "^15.3.1",
"react-dom": "^15.3.1",
"react-helmet": "^3.1.0",
diff --git a/packages/gitbook-core/src/components/Backdrop.js b/packages/gitbook-core/src/components/Backdrop.js
new file mode 100644
index 0000000..18f3c4d
--- /dev/null
+++ b/packages/gitbook-core/src/components/Backdrop.js
@@ -0,0 +1,38 @@
+const React = require('react');
+
+/**
+ * Backdrop for modals, dropdown, etc. that covers the whole screen
+ * and handles click.
+ *
+ * <Backdrop onClick={onCloseModal} />
+ */
+const Backdrop = React.createClass({
+ propTypes: {
+ // Callback when backdrop is clicked
+ onClick: React.PropTypes.func.isRequired,
+ // Z-index for the backdrop
+ zIndex: React.PropTypes.number
+ },
+
+ getDefaultProps() {
+ return {
+ zIndex: 200
+ };
+ },
+
+ render() {
+ const { zIndex, onClick } = this.props;
+ const style = {
+ zIndex,
+ position: 'fixed',
+ top: 0,
+ right: 0,
+ width: '100%',
+ height: '100%'
+ };
+
+ return <div style={style} onClick={onClick}></div>;
+ }
+});
+
+module.exports = Backdrop;
diff --git a/packages/gitbook-core/src/components/ButtonGroup.js b/packages/gitbook-core/src/components/ButtonGroup.js
new file mode 100644
index 0000000..4c20b68
--- /dev/null
+++ b/packages/gitbook-core/src/components/ButtonGroup.js
@@ -0,0 +1,23 @@
+const React = require('react');
+const classNames = require('classnames');
+
+const ButtonGroup = React.createClass({
+ propTypes: {
+ className: React.PropTypes.string,
+ children: React.PropTypes.node,
+ onClick: React.PropTypes.func
+ },
+
+ render() {
+ let { className, children } = this.props;
+
+ className = classNames(
+ 'GitBook-ButtonGroup',
+ className
+ );
+
+ return <div className={className}>{children}</div>;
+ }
+});
+
+module.exports = ButtonGroup;
diff --git a/packages/gitbook-core/src/components/Dropdown.js b/packages/gitbook-core/src/components/Dropdown.js
new file mode 100644
index 0000000..f43a6fc
--- /dev/null
+++ b/packages/gitbook-core/src/components/Dropdown.js
@@ -0,0 +1,141 @@
+const React = require('react');
+const classNames = require('classnames');
+
+const Backdrop = require('./Backdrop');
+
+/**
+ * Dropdown to display a menu
+ *
+ * <Dropdown.Container>
+ *
+ * <Button />
+ *
+ * <Dropdown.Menu open={this.state.open}>
+ * <Dropdown.Item href={...}> ... </Dropdown.Item>
+ * <Dropdown.Item onClick={...}> ... </Dropdown.Item>
+ *
+ * <Dropdown.Item> A submenu
+ * <Dropdown.Menu>
+ * <Dropdown.Item href={...}> Subitem </Dropdown.Item>
+ * </Dropdown.Menu>
+ * </Dropdown.Item>
+ *
+ * </Dropdown.Menu>
+ * </Dropdown.Container>
+ */
+
+const DropdownContainer = React.createClass({
+ propTypes: {
+ className: React.PropTypes.string,
+ span: React.PropTypes.bool,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ let { className, span, children } = this.props;
+
+ className = classNames(
+ 'GitBook-Dropdown',
+ className
+ );
+
+ return span ?
+ <span className={className}>{children}</span>
+ : <div className={className}>{children}</div>;
+ }
+});
+
+/**
+ * A dropdown item, which is always a link, and can contain a nested
+ * DropdownMenu.
+ */
+const DropdownItem = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node,
+ onClick: React.PropTypes.func,
+ href: React.PropTypes.string
+ },
+
+ onClick(e) {
+ if (!this.props.href) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (this.props.onClick) this.props.onClick();
+ }
+ },
+
+ render() {
+ const {
+ children, href,
+ onClick, // eslint-disable-line no-unused-vars
+ ...otherProps
+ } = this.props;
+
+ let inner = [], submenu = [];
+ submenu = filterChildren(children, isDropdownMenu);
+ inner = filterChildren(children, (child) => !isDropdownMenu(child));
+
+ return (
+ <li className="GitBook-DropdownItem">
+ <a href={href || '#'}
+ onClick={this.onClick}
+ {...otherProps} >
+ {inner}
+ </a>
+ {submenu}
+ </li>
+ );
+ }
+});
+
+/**
+ * @param {Node} children
+ * @param {Function} predicate
+ * @return {Node} children that pass the predicate
+ */
+function filterChildren(children, predicate) {
+ return React.Children.map(
+ children,
+ (child) => predicate(child) ? child : null
+ );
+}
+
+/**
+ * A DropdownMenu to display DropdownItems. Must be inside a
+ * DropdownContainer.
+ */
+const DropdownMenu = React.createClass({
+ propTypes: {
+ className: React.PropTypes.string,
+ children: React.PropTypes.node,
+ open: React.PropTypes.bool
+ },
+
+ render() {
+ const { open } = this.props;
+ const className = classNames(
+ 'GitBook-DropdownMenu',
+ { 'GitBook-DropdownMenu-open': open }
+ );
+
+ return (
+ <ul className={className}>
+ {this.props.children}
+ </ul>
+ );
+ }
+});
+
+function isDropdownMenu(child) {
+ return (child && child.type && child.type.displayName === 'DropdownMenu');
+}
+
+const Dropdown = {
+ Item: DropdownItem,
+ Menu: DropdownMenu,
+ Container: DropdownContainer,
+ Backdrop
+};
+
+module.exports = Dropdown;
diff --git a/packages/gitbook-core/src/components/HotKeys.js b/packages/gitbook-core/src/components/HotKeys.js
new file mode 100644
index 0000000..c2c67c8
--- /dev/null
+++ b/packages/gitbook-core/src/components/HotKeys.js
@@ -0,0 +1,56 @@
+const React = require('react');
+const Mousetrap = require('mousetrap');
+const { string, node, func, shape, arrayOf } = React.PropTypes;
+
+const bindingShape = shape({
+ // A key "escape", a combination of key "mod+s", or a key sequence "ctrl+x ctrl+s"
+ key: string.isRequired,
+ // function (event) {}
+ handler: func.isRequired
+});
+
+/**
+ * Defines hotkeys globally when this component is mounted.
+ *
+ * keymap = [{
+ * key: 'escape',
+ * handler: (e) => quit()
+ * }, {
+ * key: 'mod+s',
+ * handler: (e) => save()
+ * }]
+ *
+ * <HotKeys keymap={keymap}>
+ * < ... />
+ * </HotKeys>
+ */
+
+const HotKeys = React.createClass({
+ propTypes: {
+ children: node.isRequired,
+ keymap: arrayOf(bindingShape)
+ },
+
+ getDefaultProps() {
+ return { keymap: [] };
+ },
+
+ componentDidMount() {
+ this.props.keymap.forEach((binding) => {
+ Mousetrap.bind(binding.key, binding.handler);
+ });
+ },
+
+ componentWillUnmount() {
+ this.props.keymap.forEach((binding) => {
+ Mousetrap.unbind(binding.key, binding.handler);
+ });
+ },
+
+ render() {
+ // Simply render the only child
+ return React.Children.only(this.props.children);
+ }
+});
+
+module.exports = HotKeys;
diff --git a/packages/gitbook-core/src/index.js b/packages/gitbook-core/src/index.js
index d51d073..f820599 100644
--- a/packages/gitbook-core/src/index.js
+++ b/packages/gitbook-core/src/index.js
@@ -12,7 +12,10 @@ const { ImportLink, ImportScript, ImportCSS } = require('./components/Import');
const HTMLContent = require('./components/HTMLContent');
const Link = require('./components/Link');
const Icon = require('./components/Icon');
+const HotKeys = require('./components/HotKeys');
const Button = require('./components/Button');
+const ButtonGroup = require('./components/ButtonGroup');
+const Dropdown = require('./components/Dropdown');
const I18nProvider = require('./components/I18nProvider');
const ACTIONS = require('./actions/TYPES');
@@ -49,7 +52,10 @@ module.exports = {
FlexBox: Box,
Link,
Icon,
+ HotKeys,
Button,
+ ButtonGroup,
+ Dropdown,
// Utilities
Shapes,
// Librairies
diff --git a/packages/gitbook-plugin-livereload/package.json b/packages/gitbook-plugin-livereload/package.json
index 58ab612..d5aa912 100644
--- a/packages/gitbook-plugin-livereload/package.json
+++ b/packages/gitbook-plugin-livereload/package.json
@@ -10,6 +10,9 @@
"dependencies": {
"gitbook-core": "4.0.0"
},
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0"
+ },
"scripts": {
"build-js": "gitbook-plugin build ./src/index.js ./_assets/plugin.js",
"prepublish": "npm run build-js"
diff --git a/packages/gitbook-plugin-sharing/README.md b/packages/gitbook-plugin-sharing/README.md
new file mode 100644
index 0000000..28ae0d4
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/README.md
@@ -0,0 +1,38 @@
+# plugin-sharing
+
+This plugin adds sharing buttons in the GitBook website toolbar to share book on social networks.
+
+### Disable this plugin
+
+This is a default plugin and it can be disabled using a `book.json` configuration:
+
+```
+{
+ plugins: ["-sharing"]
+}
+```
+
+### Configuration
+
+This plugin can be configured in the `book.json`:
+
+Default configuration is:
+
+```js
+{
+ "pluginsConfig": {
+ "sharing": {
+ "facebook": true,
+ "twitter": true,
+ "google": false,
+ "weibo": false,
+ "instapaper": false,
+ "vk": false,
+ "all": [
+ "facebook", "google", "twitter",
+ "weibo", "instapaper"
+ ]
+ }
+ }
+}
+```
diff --git a/packages/gitbook-plugin-sharing/package.json b/packages/gitbook-plugin-sharing/package.json
index 7f85575..b0540e8 100644
--- a/packages/gitbook-plugin-sharing/package.json
+++ b/packages/gitbook-plugin-sharing/package.json
@@ -4,6 +4,54 @@
"main": "index.js",
"browser": "./_assets/plugin.js",
"version": "4.0.0",
+ "gitbook": {
+ "properties": {
+ "facebook": {
+ "type": "boolean",
+ "default": true,
+ "title": "Facebook"
+ },
+ "twitter": {
+ "type": "boolean",
+ "default": true,
+ "title": "Twitter"
+ },
+ "google": {
+ "type": "boolean",
+ "default": false,
+ "title": "Google"
+ },
+ "weibo": {
+ "type": "boolean",
+ "default": false,
+ "description": "Weibo"
+ },
+ "instapaper": {
+ "type": "boolean",
+ "default": false,
+ "description": "Instapaper"
+ },
+ "vk": {
+ "type": "boolean",
+ "default": false,
+ "description": "VK"
+ },
+ "all": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "default": [
+ "facebook",
+ "google",
+ "twitter",
+ "weibo",
+ "instapaper"
+ ],
+ "uniqueItems": true
+ }
+ }
+ },
"dependencies": {
"gitbook-core": "4.0.0"
},
@@ -24,5 +72,6 @@
},
"bugs": {
"url": "https://github.com/GitbookIO/gitbook/issues"
- }
-} \ No newline at end of file
+ },
+ "license": "Apache-2.0"
+}
diff --git a/packages/gitbook-plugin-sharing/src/SITES.js b/packages/gitbook-plugin-sharing/src/SITES.js
new file mode 100644
index 0000000..86eae74
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/SITES.js
@@ -0,0 +1,72 @@
+// All the sharing platforms
+const SITES = {
+
+ // One sharing platform
+ 'facebook': {
+ // Displayed name
+ label: 'Facebook',
+
+ // Font-awesome icon id
+ icon: 'facebook',
+
+ /**
+ * Share a page on this platform
+ * @param {String} url The url to share
+ * @param {String} title The title of the url page
+ */
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ window.open(`http://www.facebook.com/sharer/sharer.php?s=100&p[url]=${url}`);
+ }
+ },
+
+ 'twitter': {
+ label: 'Twitter',
+ icon: 'twitter',
+ onShare(url, title) {
+ const status = encodeURIComponent(title + ' ' + url);
+ window.open(`http://twitter.com/home?status=${status}`);
+ }
+ },
+
+ 'google': {
+ label: 'Google+',
+ icon: 'google-plus',
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ window.open(`https://plus.google.com/share?url=${url}`);
+ }
+ },
+
+ 'weibo': {
+ label: 'Weibo',
+ icon: 'weibo',
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ title = encodeURIComponent(title);
+ window.open(`http://service.weibo.com/share/share.php?content=utf-8&url=${url}&title=${title}`);
+ }
+ },
+
+ 'instapaper': {
+ label: 'Instapaper',
+ icon: 'instapaper',
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ window.open(`http://www.instapaper.com/text?u=${url}`);
+ }
+ },
+
+ 'vk': {
+ label: 'VK',
+ icon: 'vk',
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ window.open(`http://vkontakte.ru/share.php?url=${url}`);
+ }
+ }
+};
+
+SITES.ALL = Object.keys(SITES);
+
+module.exports = SITES;
diff --git a/packages/gitbook-plugin-sharing/src/index.js b/packages/gitbook-plugin-sharing/src/index.js
index 4821de2..357ccbb 100644
--- a/packages/gitbook-plugin-sharing/src/index.js
+++ b/packages/gitbook-plugin-sharing/src/index.js
@@ -1,10 +1,22 @@
const GitBook = require('gitbook-core');
-const { React } = GitBook;
+const {
+ React,
+ Dropdown
+} = GitBook;
+const { string, arrayOf, shape, func } = React.PropTypes;
+
+const SITES = require('./SITES');
+const optionsShape = require('./optionsShape');
+const siteShape = shape({
+ label: string.isRequired,
+ icon: string.isRequired,
+ onShare: func.isRequired
+});
module.exports = GitBook.createPlugin({
activate: (dispatch, getState, { Components }) => {
// Dispatch initialization actions
- dispatch(Components.registerComponent(SharingButton, { role: 'toolbar:buttons:right' }))
+ dispatch(Components.registerComponent(Sharing, { role: 'toolbar:buttons:right' }));
},
deactivate: (dispatch, getState) => {
// Dispatch cleanup actions
@@ -12,23 +24,119 @@ module.exports = GitBook.createPlugin({
reduce: (state, action) => state
});
-let SharingButton = React.createClass({
+/**
+ * Displays the group of sharing buttons
+ */
+let Sharing = React.createClass({
+ propTypes: {
+ options: optionsShape.isRequired,
+ page: GitBook.Shapes.Page.isRequired
+ },
+
+ onShare(site) {
+ site.onShare(location.href, this.props.page.title);
+ },
+
+ render() {
+ const { options } = this.props;
+
+ // Highlighted sites
+ const mainButtons = SITES
+ .ALL
+ .filter(id => options[id])
+ .map(id => <SiteButton key={id} onShare={this.onShare} site={SITES[id]} />);
+
+ // Other sites
+ let shareButton = undefined;
+ if (options.all.length > 0) {
+ shareButton = (
+ <ShareButton siteIds={options.all}
+ onShare={this.onShare} />
+ );
+ }
+
+ return (
+ <GitBook.ButtonGroup>
+ { mainButtons }
+ { shareButton }
+ </GitBook.ButtonGroup>
+ );
+ }
+});
+
+function mapStateToProps(state) {
+ let options = state.config.getIn(['pluginsConfig', 'sharing']);
+ if (options) {
+ options = options.toJS();
+ } else {
+ options = { all: [] };
+ }
+
+ return {
+ page: state.page,
+ options
+ };
+}
+
+Sharing = GitBook.connect(Sharing, mapStateToProps);
+
+// An individual site sharing button
+const SiteButton = React.createClass({
propTypes: {
- page: GitBook.Shapes.Page
+ site: siteShape.isRequired,
+ onShare: func.isRequired
},
- onClick() {
- alert(this.props.page.title)
+ onClick(e) {
+ e.preventDefault();
+ this.props.onShare(this.props.site);
},
render() {
+ const { site } = this.props;
+
return (
<GitBook.Button onClick={this.onClick}>
- <GitBook.Icon id="facebook"/>
+ <GitBook.Icon id={site.icon}/>
</GitBook.Button>
- )
+ );
+ }
+});
+
+// Share button with dropdown list of sites
+const ShareButton = React.createClass({
+ propTypes: {
+ siteIds: arrayOf(string).isRequired,
+ onShare: func.isRequired
+ },
+
+ getInitialState() {
+ return { open: false };
+ },
+
+ onToggle() {
+ this.setState({ open: !this.state.open });
+ },
+
+ render() {
+ const { siteIds, onShare } = this.props;
+
+ const items = siteIds.map((id) => (
+ <Dropdown.Item onClick={() => onShare(SITES[id])} key={id}>
+ {SITES[id].label}
+ </Dropdown.Item>
+ ));
+
+ return (
+ <Dropdown.Container>
+ <GitBook.Button onClick={this.onToggle}>
+ <GitBook.Icon id="share-alt" />
+ </GitBook.Button>
+
+ <Dropdown.Menu open={this.state.open}>
+ {items}
+ </Dropdown.Menu>
+ </Dropdown.Container>
+ );
}
-})
-SharingButton = GitBook.connect(SharingButton, function mapStateToProps(state) {
- return { page: state.page }
-})
+});
diff --git a/packages/gitbook-plugin-sharing/src/optionsShape.js b/packages/gitbook-plugin-sharing/src/optionsShape.js
new file mode 100644
index 0000000..dd51016
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/optionsShape.js
@@ -0,0 +1,20 @@
+const {
+ bool,
+ arrayOf,
+ oneOf,
+ shape
+} = require('gitbook-core').React.PropTypes;
+
+const { ALL } = require('./SITES');
+
+const optionsShape = shape({
+ facebook: bool,
+ twitter: bool,
+ google: bool,
+ weibo: bool,
+ instapaper: bool,
+ vk: bool,
+ all: arrayOf(oneOf(ALL)).isRequired
+});
+
+module.exports = optionsShape;