Skip to content

Commit

Permalink
Merge pull request Automattic#338 from Automattic/update/sharing-redux
Browse files Browse the repository at this point in the history
Sharing: Implement Redux-based Publicize reducers and actions
  • Loading branch information
aduth committed Nov 24, 2015
2 parents 6a20952 + c97533c commit 20792e1
Show file tree
Hide file tree
Showing 24 changed files with 951 additions and 43 deletions.
41 changes: 41 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1172,3 +1172,44 @@ Copyright © 2013 Habla, Inc. Habla Inc. (dba Olark)
Permission kindly granted by Ben Congleton, of Olark.
```

### https://github.com/rackt/redux/
#### shared/lib/create-redux-store
```text
The MIT License (MIT)
Copyright (c) 2015 Dan Abramov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```

### https://github.com/gaearon/redux-thunk
#### shared/lib/create-redux-store
```text
The MIT License (MIT)
Copyright (c) 2015 Dan Abramov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```

### https://github.com/rackt/react-redux
```text
The MIT License (MIT)
Copyright (c) 2015 Dan Abramov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
4 changes: 4 additions & 0 deletions client/boot/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var config = require( 'config' ),
touchDetect = require( 'lib/touch-detect' ),
accessibleFocus = require( 'lib/accessible-focus' ),
TitleStore = require( 'lib/screen-title/store' ),
createReduxStore = require( 'lib/create-redux-store' ),
// The following mixins require i18n content, so must be required after i18n is initialized
Layout,
LoggedOutLayout;
Expand Down Expand Up @@ -78,12 +79,15 @@ function init() {
}

function setUpContext( layout ) {
var reduxStore = createReduxStore();

// Pass the layout so that it is available to all page handlers
// and add query and hash objects onto context object
page( '*', function( context, next ) {
var parsed = url.parse( location.href, true );

context.layout = layout;
context.reduxStore = reduxStore;

// Break routing and do full page load for logout link in /me
if ( context.pathname === '/wp-login.php' ) {
Expand Down
29 changes: 24 additions & 5 deletions client/my-sites/sharing/connections/service-connections.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,28 @@ module.exports = {
return this.filter( 'getConnectionStatus', serviceName, status, arguments );
},

/**
* Given a service name and array of connection objects, returns the subset
* of connections that are available for use by the current user.
*
* @param {string} serviceName The name of the service
* @param {Array} siteId Connection objects
* @return {Array} Connections available to user
*/
getConnectionsAvailableToCurrentUser: function( serviceName, connections ) {
var currentUser = user.get();

if ( ! currentUser ) {
return [];
}

return connections.filter( function( connection ) {
// Only include connections of the specified service, filtered by
// those owned by the current user or shared.
return connection.service === serviceName && ( connection.keyring_connection_user_ID === currentUser.ID || connection.shared );
} );
},

/**
* Given a service name and optional site ID, returns the available
* connections for the current user.
Expand All @@ -157,11 +179,8 @@ module.exports = {
siteId = null;
}

connections = connectionsList.get( siteId ).filter( function( connection ) {
// Only include connections of the specified service, filtered by
// those owned by the current user or shared.
return connection.service === serviceName && ( connection.keyring_connection_user_ID === currentUser.ID || connection.shared );
} );
connections = connectionsList.get( siteId );
connections = this.getConnectionsAvailableToCurrentUser( serviceName, connections );
} else {
connections = [];
}
Expand Down
15 changes: 9 additions & 6 deletions client/post-editor/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
var React = require( 'react' ),
page = require( 'page' ),
ReduxProvider = require( 'react-redux' ).Provider,
startsWith = require( 'lodash/string/startsWith' ),
qs = require( 'querystring' );

Expand Down Expand Up @@ -37,12 +38,14 @@ function renderEditor( context, postType ) {

React.unmountComponentAtNode( document.getElementById( 'secondary' ) );
React.render(
React.createElement( PreferencesData, null,
React.createElement( PostEditor, {
sites: sites,
type: postType
} )
),
React.createElement( ReduxProvider, { store: context.reduxStore }, () => {
return React.createElement( PreferencesData, null,
React.createElement( PostEditor, {
sites: sites,
type: postType
} )
)
} ),
document.getElementById( 'primary' )
);
}
Expand Down
21 changes: 3 additions & 18 deletions client/post-editor/editor-drawer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ var Accordion = require( 'components/accordion' ),
CategoryListData = require( 'components/data/category-list-data' ),
TagListData = require( 'components/data/tag-list-data' ),
FeaturedImage = require( 'post-editor/editor-featured-image' ),
SharingConnectionsData = require( 'components/data/sharing-connections-data' ),
SharingAccordion = require( 'post-editor/editor-sharing/accordion' ),
EditorSharingContainer = require( 'post-editor/editor-sharing/container' ),
FormTextarea = require( 'components/forms/form-textarea' ),
PostFormatsData = require( 'components/data/post-formats-data' ),
PostFormatsAccordion = require( 'post-editor/editor-post-formats/accordion' ),
Expand Down Expand Up @@ -114,23 +113,9 @@ var EditorDrawer = React.createClass( {
},

renderSharing: function() {
var element = (
<SharingAccordion
site={ this.props.site }
post={ this.props.post }
isNew={ this.props.isNew }
className="editor-drawer__accordion" />
return (
<EditorSharingContainer site={ this.props.site } />
);

if ( this.props.site ) {
element = (
<SharingConnectionsData siteId={ this.props.site.ID }>
{ element }
</SharingConnectionsData>
);
}

return element;
},

renderFeaturedImage: function() {
Expand Down
4 changes: 3 additions & 1 deletion client/post-editor/editor-sharing/accordion.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ module.exports = React.createClass( {
site: React.PropTypes.object,
post: React.PropTypes.object,
isNew: React.PropTypes.bool,
connections: React.PropTypes.array
connections: React.PropTypes.array,
fetchConnections: React.PropTypes.func
},

getSubtitle: function() {
Expand Down Expand Up @@ -116,6 +117,7 @@ module.exports = React.createClass( {
post={ this.props.post }
isNew={ this.props.isNew }
connections={ this.props.connections }
fetchConnections={ this.props.fetchConnections }
/>
) }
{ this.renderShortUrl() }
Expand Down
96 changes: 96 additions & 0 deletions client/post-editor/editor-sharing/container.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* External dependencies
*/
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';

/**
* Internal dependencies
*/
import PostEditStore from 'lib/posts/post-edit-store';
import { fetchConnections } from 'lib/sharing/publicize/actions';
import { getConnectionsBySiteId, hasFetchedConnections } from 'lib/sharing/publicize/selectors';
import EditorSharingAccordion from './accordion';

class EditorSharingContainer extends Component {
constructor( props ) {
super( props );

// Set state
this.state = this.getState();

// Trigger connection fetch
this.ensureHasConnections();

// Bind legacy store update handler
this.boundUpdateState = this.updateState.bind( this );
PostEditStore.on( 'change', this.boundUpdateState );
}

componentDidUpdate() {
this.ensureHasConnections();
}

componentWillUnmount() {
PostEditStore.off( 'change', this.boundUpdateState );
}

ensureHasConnections() {
if ( this.props.hasFetchedConnections ) {
return;
}

this.fetchConnections();
}

updateState() {
this.setState( this.getState() );
}

getState() {
return {
post: PostEditStore.get(),
isNew: PostEditStore.isNew()
};
}

fetchConnections() {
const { site, dispatch } = this.props;
if ( ! site ) {
return;
}

dispatch( fetchConnections( site.ID ) );
}

render() {
const { site, connections } = this.props;
const { post, isNew } = this.state;

return (
<EditorSharingAccordion
site={ site }
post={ post }
isNew={ isNew }
connections={ connections }
fetchConnections={ this.fetchConnections.bind( this ) }
className="editor-drawer__accordion" />
);
}
}

EditorSharingContainer.propTypes = {
site: PropTypes.object,
dispatch: PropTypes.func.isRequired,
hasFetchedConnections: PropTypes.bool,
connections: PropTypes.array
};

export default connect(
( state, props ) => {
return {
hasFetchedConnections: props.site && hasFetchedConnections( state, props.site.ID ),
connections: props.site ? getConnectionsBySiteId( state, props.site.ID ) : null
};
}
)( EditorSharingContainer );
10 changes: 6 additions & 4 deletions client/post-editor/editor-sharing/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ module.exports = React.createClass( {
site: React.PropTypes.object,
post: React.PropTypes.object,
isNew: React.PropTypes.bool,
connections: React.PropTypes.array
connections: React.PropTypes.array,
fetchConnections: React.PropTypes.func
},

isSharingButtonsEnabled() {
Expand Down Expand Up @@ -66,9 +67,10 @@ module.exports = React.createClass( {

return (
<PublicizeOptions
post={ this.props.post }
site={ this.props.site }
connections={ this.props.connections } />
post={ this.props.post }
site={ this.props.site }
connections={ this.props.connections }
fetchConnections={ this.props.fetchConnections } />
);
},

Expand Down
18 changes: 10 additions & 8 deletions client/post-editor/editor-sharing/publicize-options.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ var PublicizeMessage = require( './publicize-message' ),
PublicizeServices = require( './publicize-services' ),
paths = require( 'lib/paths' ),
PostMetadata = require( 'lib/post-metadata' ),
connections = require( 'lib/connections-list' )(),
PopupMonitor = require( 'lib/popup-monitor' ),
AddNewButton = require( 'components/add-new-button' ),
siteUtils = require( 'lib/site/utils' ),
Expand All @@ -29,7 +28,14 @@ module.exports = React.createClass( {
propTypes: {
site: React.PropTypes.object,
post: React.PropTypes.object,
connections: React.PropTypes.array
connections: React.PropTypes.array,
fetchConnections: React.PropTypes.func
},

getDefaultProps() {
return {
fetchConnections: () => {}
};
},

hasConnections: function() {
Expand Down Expand Up @@ -60,11 +66,7 @@ module.exports = React.createClass( {
}

this.connectionPopupMonitor.open( href );
this.connectionPopupMonitor.once( 'close', this.onNewConnectionPopupClosed );
},

onNewConnectionPopupClosed: function() {
connections.get( this.props.site.ID, { force: true } );
this.connectionPopupMonitor.once( 'close', this.props.fetchConnections );
},

newConnection: function() {
Expand Down Expand Up @@ -102,7 +104,7 @@ module.exports = React.createClass( {
// to connections previously returning a 400 error
this.props.site.once( 'change', () => {
if ( this.props.site.isModuleActive( 'publicize' ) ) {
connections.get( this.props.site.ID, { force: true } );
this.props.fetchConnections();
}
} );
},
Expand Down
5 changes: 4 additions & 1 deletion client/post-editor/editor-sharing/publicize-services.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ module.exports = React.createClass( {
},

renderConnections: function( serviceName ) {
var connections = serviceConnections.getConnections( serviceName, this.props.siteId );
const connections = serviceConnections.getConnectionsAvailableToCurrentUser(
serviceName,
this.props.connections
);

return connections.map( function( connection ) {
return (
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@
"raf": "2.0.4",
"react": "0.13.3",
"react-day-picker": "1.1.0",
"react-redux": "3.1.0",
"react-tap-event-plugin": "0.1.6",
"redux": "3.0.4",
"redux-thunk": "1.0.0",
"rtlcss": "1.6.1",
"sanitize-html": "1.11.1",
"source-map": "0.1.39",
Expand Down
12 changes: 12 additions & 0 deletions shared/lib/create-redux-store/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
`create-redux-store`
====================

This module exports a function which, when invoked, returns an instance of a [Redux store](http://redux.js.org/docs/basics/Store.html). The store instance is intended to reflect the global state of the application, and runs dispatched actions against all reducers defined in [`reducers.js`](./reducers.js). To include a reducer in the global store, simply add the reducing function to the combined reducer exported by `reducers.js`.

## Usage

```js
import createReduxStore from 'lib/create-redux-store';

const store = createReduxStore();
```
Loading

0 comments on commit 20792e1

Please sign in to comment.