mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 01:51:40 -05:00
More work on voting
This commit is contained in:
parent
683da40216
commit
15ed3c3923
13 changed files with 299 additions and 200 deletions
|
@ -37,7 +37,7 @@ class PostsItem extends PureComponent {
|
|||
<div className={postClass}>
|
||||
|
||||
<div className="posts-item-vote">
|
||||
<Components.Vote collection={Posts} document={post} currentUser={this.props.currentUser}/>
|
||||
<Components.Vote collection={Posts} document={post} currentUser={this.props.currentUser} showDownvote={false}/>
|
||||
</div>
|
||||
|
||||
{post.thumbnailUrl ? <Components.PostsThumbnail post={post}/> : null}
|
||||
|
|
|
@ -10,7 +10,6 @@ import '../components/common/FlashMessages.jsx';
|
|||
import '../components/common/Newsletter.jsx';
|
||||
import '../components/common/NewsletterButton.jsx';
|
||||
import '../components/common/SearchForm.jsx';
|
||||
import '../components/common/Vote.jsx';
|
||||
|
||||
// posts
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.upvote-button{
|
||||
.vote-button{
|
||||
@include border;
|
||||
@include border-radius;
|
||||
@include flex-center;
|
||||
|
@ -127,7 +127,16 @@
|
|||
.voted &{
|
||||
color: $light-text;
|
||||
}
|
||||
&.show-downvote{
|
||||
.icon{
|
||||
&:hover{
|
||||
color: $active-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.hide-downvote{
|
||||
@include activeHover;
|
||||
}
|
||||
&, &:active, &:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
110
packages/vulcan-voting/lib/components/Vote.jsx
Normal file
110
packages/vulcan-voting/lib/components/Vote.jsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
import { Components, registerComponent, withMessages } from 'meteor/vulcan:core';
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { withVote } from '../containers/withVote.js';
|
||||
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
||||
|
||||
class Vote extends PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.vote = this.vote.bind(this);
|
||||
this.upvote = this.upvote.bind(this);
|
||||
this.downvote = this.downvote.bind(this);
|
||||
this.hasVoted = this.hasVoted.bind(this);
|
||||
this.getActionClass = this.getActionClass.bind(this);
|
||||
// this.startLoading = this.startLoading.bind(this);
|
||||
// this.stopLoading = this.stopLoading.bind(this);
|
||||
this.state = {
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
|
||||
hasVoted() {
|
||||
return this.props.document.currentUserVotes && this.props.document.currentUserVotes.length;
|
||||
}
|
||||
|
||||
vote(voteType) {
|
||||
|
||||
const document = this.props.document;
|
||||
const collection = this.props.collection;
|
||||
const user = this.props.currentUser;
|
||||
|
||||
if(!user){
|
||||
this.props.flash(this.context.intl.formatMessage({id: 'users.please_log_in'}));
|
||||
} else {
|
||||
const operationType = this.hasVoted() ? 'cancelVote' : voteType;
|
||||
this.props.vote({document, operationType, collection, currentUser: this.props.currentUser});
|
||||
}
|
||||
}
|
||||
|
||||
upvote(e) {
|
||||
e.preventDefault();
|
||||
this.vote('upvote');
|
||||
}
|
||||
|
||||
downvote(e) {
|
||||
e.preventDefault();
|
||||
this.vote('downvote');
|
||||
}
|
||||
|
||||
getActionClass() {
|
||||
|
||||
const actionsClass = classNames(
|
||||
'vote-button',
|
||||
{'show-downvote': this.props.showDownvote},
|
||||
{'hide-downvote': !this.props.showDownvote},
|
||||
{voted: this.hasVoted()},
|
||||
);
|
||||
|
||||
return actionsClass;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.showDownvote) {
|
||||
return (
|
||||
<div className={this.getActionClass()}>
|
||||
<a className="upvote-button" onClick={this.upvote}>
|
||||
<Components.Icon name="upvote" />
|
||||
<div className="sr-only"><FormattedMessage id="voting.upvote"/></div>
|
||||
</a>
|
||||
<div className="vote-count">{this.props.document.baseScore || 0}</div>
|
||||
<a className="downvote-button" onClick={this.downvote}>
|
||||
<Components.Icon name="downvote" />
|
||||
<div className="sr-only"><FormattedMessage id="voting.downvote"/></div>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className={this.getActionClass()}>
|
||||
<a className="upvote-button" onClick={this.upvote}>
|
||||
<Components.Icon name="upvote" />
|
||||
<div className="sr-only"><FormattedMessage id="voting.upvote"/></div>
|
||||
<div className="vote-count">{this.props.document.baseScore || 0}</div>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vote.propTypes = {
|
||||
document: PropTypes.object.isRequired, // the document to upvote
|
||||
collection: PropTypes.object.isRequired, // the collection containing the document
|
||||
vote: PropTypes.func.isRequired, // mutate function with callback inside
|
||||
currentUser: PropTypes.object, // user might not be logged in, so don't make it required
|
||||
showDownvote: PropTypes.bool,
|
||||
};
|
||||
|
||||
Vote.defaultProps = {
|
||||
showDownvote: false
|
||||
};
|
||||
|
||||
Vote.contextTypes = {
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
registerComponent('Vote', Vote, withMessages, withVote);
|
|
@ -1,70 +1,65 @@
|
|||
/*
|
||||
|
||||
This variant of the Vote.jsx component implements a loading spinner instead of
|
||||
optimistic response
|
||||
|
||||
*/
|
||||
import { Components, registerComponent, withMessages } from 'meteor/vulcan:core';
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { withVote, hasUpvoted, hasDownvoted } from 'meteor/vulcan:voting';
|
||||
import { /*FormattedMessage,*/ intlShape } from 'meteor/vulcan:i18n';
|
||||
import { withVote } from '../containers/withVote.js';
|
||||
|
||||
class Vote extends PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.upvote = this.upvote.bind(this);
|
||||
this.hasVoted = this.hasVoted.bind(this);
|
||||
this.getActionClass = this.getActionClass.bind(this);
|
||||
// this.startLoading = this.startLoading.bind(this);
|
||||
// this.stopLoading = this.stopLoading.bind(this);
|
||||
this.startLoading = this.startLoading.bind(this);
|
||||
this.stopLoading = this.stopLoading.bind(this);
|
||||
this.state = {
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
startLoading() {
|
||||
this.setState({ loading: true });
|
||||
}
|
||||
|
||||
note: with optimisitc UI, loading functions are not needed
|
||||
also, setState triggers issues when the component is unmounted
|
||||
before the vote mutation returns.
|
||||
stopLoading() {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// startLoading() {
|
||||
// this.setState({ loading: true });
|
||||
// }
|
||||
|
||||
// stopLoading() {
|
||||
// this.setState({ loading: false });
|
||||
// }
|
||||
hasVoted() {
|
||||
return this.props.document.currentUserVotes && this.props.document.currentUserVotes.length;
|
||||
}
|
||||
|
||||
upvote(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// this.startLoading();
|
||||
|
||||
this.startLoading();
|
||||
const document = this.props.document;
|
||||
const collection = this.props.collection;
|
||||
const user = this.props.currentUser;
|
||||
|
||||
if(!user){
|
||||
this.props.flash(this.context.intl.formatMessage({id: 'users.please_log_in'}));
|
||||
// this.stopLoading();
|
||||
this.stopLoading();
|
||||
} else {
|
||||
const operationType = this.props.document.currentUserVotes && this.props.document.currentUserVotes.length ? 'cancelVote' : 'upvote';
|
||||
const operationType = this.hasVoted() ? 'cancelVote' : 'upvote';
|
||||
this.props.vote({document, operationType, collection, currentUser: this.props.currentUser}).then(result => {
|
||||
// this.stopLoading();
|
||||
this.stopLoading();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getActionClass() {
|
||||
const document = this.props.document;
|
||||
const user = this.props.currentUser;
|
||||
|
||||
const isUpvoted = hasUpvoted(user, document);
|
||||
const isDownvoted = hasDownvoted(user, document);
|
||||
const actionsClass = classNames(
|
||||
'vote',
|
||||
{voted: isUpvoted || isDownvoted},
|
||||
{upvoted: isUpvoted},
|
||||
{downvoted: isDownvoted}
|
||||
{voted: this.hasVoted()},
|
||||
);
|
||||
|
||||
return actionsClass;
|
||||
|
@ -91,8 +86,4 @@ Vote.propTypes = {
|
|||
currentUser: PropTypes.object, // user might not be logged in, so don't make it required
|
||||
};
|
||||
|
||||
Vote.contextTypes = {
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
registerComponent('Vote', Vote, withMessages, withVote);
|
||||
registerComponent('Upvote', Vote, withMessages, withVote);
|
|
@ -1,14 +1,14 @@
|
|||
import React, { PropTypes, Component } from 'react';
|
||||
import { graphql } from 'react-apollo';
|
||||
import gql from 'graphql-tag';
|
||||
import { voteOnItem } from '../modules/vote.js';
|
||||
import { voteOptimisticResponse } from '../modules/vote.js';
|
||||
import { VoteableCollections } from '../modules/make_voteable.js';
|
||||
|
||||
const withVote = component => {
|
||||
export const withVote = component => {
|
||||
|
||||
return graphql(gql`
|
||||
mutation vote($documentId: String, $operationType: String, $collectionName: String) {
|
||||
vote(documentId: $documentId, operationType: $operationType, collectionName: $collectionName) {
|
||||
mutation vote($documentId: String, $operationType: String, $collectionName: String, $voteId: String) {
|
||||
vote(documentId: $documentId, operationType: $operationType, collectionName: $collectionName, voteId: $voteId) {
|
||||
${VoteableCollections.map(collection => `
|
||||
... on ${collection.typeName} {
|
||||
__typename
|
||||
|
@ -27,22 +27,22 @@ const withVote = component => {
|
|||
props: ({ownProps, mutate}) => ({
|
||||
vote: ({document, operationType, collection, currentUser}) => {
|
||||
|
||||
const voteResult = voteOnItem(collection, document, currentUser, operationType, true);
|
||||
const voteId = Random.id();
|
||||
const newDocument = voteOptimisticResponse({collection, document, user: currentUser, operationType, voteId});
|
||||
|
||||
return mutate({
|
||||
variables: {
|
||||
documentId: document._id,
|
||||
operationType,
|
||||
collectionName: collection._name,
|
||||
collectionName: collection.options.collectionName,
|
||||
voteId,
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
vote: voteResult.document,
|
||||
vote: newDocument,
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
})(component);
|
||||
}
|
||||
|
||||
export default withVote;
|
1
packages/vulcan-voting/lib/modules/components.js
Normal file
1
packages/vulcan-voting/lib/modules/components.js
Normal file
|
@ -0,0 +1 @@
|
|||
import '../components/Vote.jsx';
|
|
@ -1,9 +1,10 @@
|
|||
import './custom_fields.js';
|
||||
import './permissions.js';
|
||||
import './fragments.js';
|
||||
import './components.js';
|
||||
|
||||
export { default as Votes } from './votes/collection.js';
|
||||
export * from './make_voteable.js';
|
||||
export { default as withVote } from '../containers/withVote.js';
|
||||
export { withVote } from '../containers/withVote.js';
|
||||
export * from './helpers.js';
|
||||
export * from './vote.js';
|
||||
|
|
|
@ -17,9 +17,9 @@ export const makeVoteable = collection => {
|
|||
resolveAs: {
|
||||
type: '[Vote]',
|
||||
resolver: async (document, args, { Users, Votes, currentUser }) => {
|
||||
if (!currentUser) return null;
|
||||
if (!currentUser) return [];
|
||||
const votes = Votes.find({userId: currentUser._id, itemId: document._id}).fetch();
|
||||
if (!votes.length) return null;
|
||||
if (!votes.length) return [];
|
||||
return votes;
|
||||
// return Users.restrictViewableFields(currentUser, Votes, votes);
|
||||
},
|
||||
|
|
|
@ -1,143 +1,163 @@
|
|||
import Users from 'meteor/vulcan:users';
|
||||
import { hasUpvoted, hasDownvoted } from './helpers.js';
|
||||
import { runCallbacks, runCallbacksAsync, registerSetting, getSetting } from 'meteor/vulcan:core';
|
||||
import update from 'immutability-helper';
|
||||
import { registerSetting, getSetting } from 'meteor/vulcan:core';
|
||||
import { createError } from 'apollo-errors';
|
||||
import Votes from './votes/collection.js';
|
||||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
registerSetting('voting.maxVotes', 1, 'How many times a user can vote on the same document');
|
||||
|
||||
// The equation to determine voting power. Defaults to returning 1 for everybody
|
||||
export const getVotePower = (user, operationType) => {
|
||||
return operationType === 'upvote' ? 1 : -1;
|
||||
};
|
||||
/*
|
||||
|
||||
const keepVoteProperties = item => _.pick(item, '__typename', '_id', 'upvoters', 'downvoters', 'upvotes', 'downvotes', 'baseScore');
|
||||
Define voting operations
|
||||
|
||||
*/
|
||||
export const voteOperations = {
|
||||
'upvote': {
|
||||
power: 1,
|
||||
// TODO: refactor voteOptimisticResponse and performVoteOperation code
|
||||
// into extensible, action-specific objects
|
||||
// clientOperation: () => {
|
||||
|
||||
// },
|
||||
// serverOperation: () => {
|
||||
|
||||
// }
|
||||
},
|
||||
'downvote': {
|
||||
power: -1
|
||||
},
|
||||
'adminUpvote': {
|
||||
power: user => Users.isAdmin(user) ? 5 : 1
|
||||
},
|
||||
'cancelVote': {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Runs all the operation and returns an objects without affecting the db.
|
||||
Determine a user's voting power for a given operation.
|
||||
If power is a function, call it on user
|
||||
|
||||
*/
|
||||
export const voteOnItem = function (collection, document, user, operationType = 'upvote') {
|
||||
export const getVotePower = (user, operationType) => {
|
||||
const power = voteOperations[operationType].power;
|
||||
return typeof power === 'function' ? power(user) : power;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Calculate total power of all a user's votes on a document
|
||||
|
||||
*/
|
||||
export const calculateTotalPower = userVotes => _.pluck(userVotes, 'power').reduce((a, b) => a + b, 0);
|
||||
|
||||
/*
|
||||
|
||||
Create new vote object
|
||||
|
||||
*/
|
||||
export const createVote = ({ documentId, collectionName, operationType, user, voteId }) => ({
|
||||
_id: voteId,
|
||||
itemId: documentId,
|
||||
collectionName,
|
||||
userId: user._id,
|
||||
voteType: operationType,
|
||||
power: getVotePower(user, operationType),
|
||||
votedAt: new Date(),
|
||||
__typename: 'Vote'
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
Optimistic response for votes
|
||||
|
||||
*/
|
||||
export const voteOptimisticResponse = ({collection, document, user, operationType = 'upvote', voteId}) => {
|
||||
|
||||
const collectionName = collection.options.collectionName;
|
||||
let result = {};
|
||||
|
||||
// make sure item and user are defined
|
||||
if (!document || !user) {
|
||||
throw new Error(`Cannot perform operation '${collectionName}.${operationType}'`);
|
||||
}
|
||||
|
||||
/*
|
||||
// console.log('// voteOptimisticResponse')
|
||||
// console.log('collectionName: ', collectionName)
|
||||
// console.log('document:', document)
|
||||
// console.log('operationType:', operationType)
|
||||
|
||||
First, handle vote cancellation.
|
||||
Just remove last vote and subtract its power from the base score
|
||||
// create a "lite" version of the document that only contains relevant fields
|
||||
// we do not want to affect the original item directly
|
||||
const newDocument = {
|
||||
_id: document._id,
|
||||
baseScore: document.baseScore || 0,
|
||||
__typename: collection.options.typeName,
|
||||
};
|
||||
|
||||
*/
|
||||
if (operationType === 'cancelVote') {
|
||||
|
||||
// create a "lite" version of the document that only contains relevant fields
|
||||
const newDocument = {
|
||||
_id: document._id,
|
||||
currentUserVotes: document.currentUserVotes || [],
|
||||
// voters: document.voters || [],
|
||||
baseScore: document.baseScore || 0,
|
||||
__typename: collection.options.typeName,
|
||||
}; // we do not want to affect the original item directly
|
||||
// subtract vote scores
|
||||
newDocument.baseScore -= calculateTotalPower(document.currentUserVotes);
|
||||
|
||||
// if document has votes
|
||||
if (newDocument.currentUserVotes.length) {
|
||||
// remove one vote
|
||||
const cancelledVote = _.last(newDocument.currentUserVotes);
|
||||
newDocument.currentUserVotes = _.initial(newDocument.currentUserVotes);
|
||||
result.vote = cancelledVote;
|
||||
|
||||
// update base score
|
||||
newDocument.baseScore -= cancelledVote.power;
|
||||
}
|
||||
|
||||
// console.log('// voteOnItem')
|
||||
// console.log('collection: ', collectionName)
|
||||
// console.log('document:', document)
|
||||
// console.log('newDocument:', newDocument)
|
||||
|
||||
result.document = newDocument;
|
||||
// clear out all votes
|
||||
newDocument.currentUserVotes = [];
|
||||
|
||||
} else {
|
||||
/*
|
||||
|
||||
Next, handle all other vote types (upvote, downvote, etc.)
|
||||
|
||||
*/
|
||||
// create new vote and add it to currentUserVotes array
|
||||
const vote = createVote({ documentId: document._id, collectionName, operationType, user, voteId });
|
||||
newDocument.currentUserVotes = [...document.currentUserVotes, vote];
|
||||
|
||||
// increment baseScore
|
||||
const power = getVotePower(user, operationType);
|
||||
|
||||
// create vote object
|
||||
const vote = {
|
||||
_id: Random.id(),
|
||||
itemId: document._id,
|
||||
collectionName,
|
||||
userId: user._id,
|
||||
voteType: operationType,
|
||||
power,
|
||||
votedAt: new Date(),
|
||||
__typename: 'Vote'
|
||||
};
|
||||
|
||||
// create a "lite" version of the document that only contains relevant fields
|
||||
const currentUserVotes = document.currentUserVotes || [];
|
||||
const newDocument = {
|
||||
_id: document._id,
|
||||
currentUserVotes: [...currentUserVotes, vote],
|
||||
// voters: document.voters || [],
|
||||
baseScore: document.baseScore || 0,
|
||||
__typename: collection.options.typeName,
|
||||
}; // we do not want to affect the original item directly
|
||||
|
||||
// update score
|
||||
newDocument.baseScore += power;
|
||||
|
||||
// console.log('// voteOnItem')
|
||||
// console.log('collection: ', collectionName)
|
||||
// console.log('document:', document)
|
||||
// console.log('newDocument:', newDocument)
|
||||
|
||||
// make sure item and user are defined, and user can perform the operation
|
||||
if (newDocument.currentUserVotes.length > getSetting('voting.maxVotes')) {
|
||||
throw new Error(`Cannot perform operation '${collectionName}.${operationType}'`);
|
||||
}
|
||||
|
||||
// ------------------------------ Sync Callbacks ------------------------------ //
|
||||
|
||||
// item = runCallbacks(operation, item, user, operation, isClient);
|
||||
|
||||
result = {
|
||||
document: newDocument,
|
||||
vote
|
||||
};
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
};
|
||||
|
||||
export const cancelVote = function (collection, document, user, voteType = 'vote') {
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Call operateOnItem, update the db with the result, run callbacks.
|
||||
Server-side database operation
|
||||
|
||||
*/
|
||||
// export const mutateItem = function (collection, originalItem, user, operation) {
|
||||
// const newItem = operateOnItem(collection, originalItem, user, operation, false);
|
||||
// newItem.inactive = false;
|
||||
export const performVoteOperation = ({documentId, operationType, collection, voteId, currentUser}) => {
|
||||
// console.log('// performVoteMutation')
|
||||
// console.log('operationType: ', operationType)
|
||||
// console.log('collectionName: ', collectionName)
|
||||
// console.log('// document: ', collection.findOne(documentId))
|
||||
|
||||
// collection.update({_id: newItem._id}, newItem, {bypassCollection2:true});
|
||||
const power = getVotePower(currentUser, operationType);
|
||||
const userVotes = Votes.find({itemId: documentId, userId: currentUser._id}).fetch();
|
||||
|
||||
// // --------------------- Server-Side Async Callbacks --------------------- //
|
||||
// runCallbacksAsync(operation+'.async', newItem, user, collection, operation);
|
||||
if (operationType === 'cancelVote') {
|
||||
|
||||
// return newItem;
|
||||
// }
|
||||
// if a vote has been cancelled, delete all votes and subtract their power from base score
|
||||
const scoreTotal = calculateTotalPower(userVotes);
|
||||
|
||||
// remove vote object
|
||||
Votes.remove({itemId: documentId, userId: currentUser._id});
|
||||
|
||||
// update document score
|
||||
collection.update({_id: documentId}, {$inc: {baseScore: -scoreTotal }});
|
||||
|
||||
} else {
|
||||
|
||||
if (userVotes.length < getSetting('voting.maxVotes')) {
|
||||
|
||||
// create vote and insert it
|
||||
const vote = createVote({ documentId, collectionName: collection.options.collectionName, operationType, user: currentUser, voteId });
|
||||
delete vote.__typename;
|
||||
Votes.insert(vote);
|
||||
|
||||
// update document score
|
||||
collection.update({_id: documentId}, {$inc: {baseScore: power }});
|
||||
|
||||
} else {
|
||||
const VoteError = createError('voting.maximum_votes_reached', {message: 'voting.maximum_votes_reached'});
|
||||
throw new VoteError();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
import { addCallback, addGraphQLSchema, addGraphQLResolvers, addGraphQLMutation, Utils, registerSetting, getSetting } from 'meteor/vulcan:core';
|
||||
import { voteOnItem } from '../modules/vote.js';
|
||||
import { performVoteOperation } from '../modules/vote.js';
|
||||
import { VoteableCollections } from '../modules/make_voteable.js';
|
||||
import { createError } from 'apollo-errors';
|
||||
import Votes from '../modules/votes/collection.js';
|
||||
|
||||
|
||||
function CreateVoteableUnionType() {
|
||||
const voteableSchema = VoteableCollections.length ? `union Voteable = ${VoteableCollections.map(collection => collection.typeName).join(' | ')}` : '';
|
||||
|
@ -12,7 +10,6 @@ function CreateVoteableUnionType() {
|
|||
}
|
||||
addCallback('graphql.init.before', CreateVoteableUnionType);
|
||||
|
||||
|
||||
const resolverMap = {
|
||||
Voteable: {
|
||||
__resolveType(obj, context, info){
|
||||
|
@ -23,59 +20,26 @@ const resolverMap = {
|
|||
|
||||
addGraphQLResolvers(resolverMap);
|
||||
|
||||
addGraphQLMutation('vote(documentId: String, operationType: String, collectionName: String) : Voteable');
|
||||
addGraphQLMutation('vote(documentId: String, operationType: String, collectionName: String, voteId: String) : Voteable');
|
||||
|
||||
const voteResolver = {
|
||||
Mutation: {
|
||||
async vote(root, {documentId, operationType, collectionName}, context) {
|
||||
async vote(root, {documentId, operationType, collectionName, voteId}, context) {
|
||||
|
||||
const { currentUser } = context;
|
||||
const collection = context[Utils.capitalize(collectionName)];
|
||||
|
||||
// query for document being voted on
|
||||
const document = await collection.queryOne(documentId, {
|
||||
fragmentText: `
|
||||
fragment DocumentVoteFragment on ${collection.typeName} {
|
||||
__typename
|
||||
_id
|
||||
currentUserVotes{
|
||||
_id
|
||||
voteType
|
||||
power
|
||||
}
|
||||
baseScore
|
||||
}
|
||||
`,
|
||||
context
|
||||
});
|
||||
const collection = context[collectionName];
|
||||
|
||||
if (context.Users.canDo(currentUser, `${collectionName.toLowerCase()}.${operationType}`)) {
|
||||
|
||||
// put document through voteOnItem and get result
|
||||
const voteResult = voteOnItem(collection, document, currentUser, operationType);
|
||||
performVoteOperation({documentId, operationType, collection, voteId, currentUser});
|
||||
|
||||
// get new version of document
|
||||
const newDocument = voteResult.document;
|
||||
newDocument.__typename = collection.typeName;
|
||||
|
||||
// get created or cancelled vote
|
||||
const vote = voteResult.vote;
|
||||
|
||||
if (operationType === 'cancelVote' && vote) {
|
||||
// if a vote has been cancelled, delete it
|
||||
Votes.remove(vote._id);
|
||||
} else {
|
||||
// if a vote has been created, insert it
|
||||
delete vote.__typename;
|
||||
Votes.insert(vote);
|
||||
}
|
||||
|
||||
// in any case, return the document that was voted on
|
||||
return newDocument;
|
||||
const document = collection.findOne(documentId);
|
||||
document.__typename = collection.options.typeName;
|
||||
return document;
|
||||
|
||||
} else {
|
||||
|
||||
const VoteError = createError('cannot_vote');
|
||||
const VoteError = createError('voting.cannot_vote', {message: 'voting.cannot_vote'});
|
||||
throw new VoteError();
|
||||
|
||||
}
|
||||
|
|
0
packages/vulcan-voting/lib/stylesheets/vote.scss
Normal file
0
packages/vulcan-voting/lib/stylesheets/vote.scss
Normal file
|
@ -10,10 +10,14 @@ Package.onUse(function (api) {
|
|||
api.versionsFrom("METEOR@1.0");
|
||||
|
||||
api.use([
|
||||
'fourseven:scss',
|
||||
'vulcan:core@1.7.0',
|
||||
'vulcan:i18n@1.7.0',
|
||||
], ['client', 'server']);
|
||||
|
||||
api.mainModule("lib/server/main.js", "server");
|
||||
api.mainModule("lib/client/main.js", "client");
|
||||
|
||||
api.addFiles(['lib/stylesheets/vote.scss'], ['client']);
|
||||
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue