mirror of
https://github.com/vale981/Vulcan
synced 2025-03-04 17:21:37 -05:00
Merge branch 'devel'
# Conflicts: # .github/CONTRIBUTING.md # packages/vulcan-lib/lib/server/mutations.js
This commit is contained in:
commit
5434356866
537 changed files with 8574 additions and 7673 deletions
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
|
@ -1,3 +1,3 @@
|
|||
Before starting on a new feature, please [check out the roadmap](https://trello.com/b/oLMMqjVL/telescope-roadmap) and come check-in in the [Vulcan Slack channel](http://slack.telescopeapp.org/).
|
||||
Before starting on a new feature, please [check out the roadmap](https://trello.com/b/dwPR0LTz/vulcanjs-roadmap) and come check-in in the [Vulcan Slack channel](http://slack.telescopeapp.org/).
|
||||
|
||||
Also, all PRs should be made to the `devel` branch, not `master`.
|
||||
Also, all PRs should be made to the `devel` branch, not `master`.
|
|
@ -22,3 +22,6 @@ example-simple
|
|||
# example-permissions
|
||||
# example-membership
|
||||
# example-interfaces
|
||||
# example-reactions
|
||||
|
||||
vulcan:debug
|
|
@ -1 +1 @@
|
|||
METEOR@1.5.1
|
||||
METEOR@1.5.2.2
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
accounts-base@1.3.1
|
||||
accounts-base@1.3.4
|
||||
accounts-password@1.4.0
|
||||
allow-deny@1.0.6
|
||||
allow-deny@1.0.9
|
||||
autoupdate@1.3.12
|
||||
babel-compiler@6.19.4
|
||||
babel-compiler@6.20.0
|
||||
babel-runtime@1.0.1
|
||||
base64@1.0.10
|
||||
binary-heap@1.0.10
|
||||
boilerplate-generator@1.1.2
|
||||
boilerplate-generator@1.2.0
|
||||
buffer@0.0.0
|
||||
caching-compiler@1.1.9
|
||||
callback-hook@1.0.10
|
||||
check@1.2.5
|
||||
ddp@1.3.0
|
||||
ddp-client@2.0.0
|
||||
ddp@1.3.1
|
||||
ddp-client@2.1.3
|
||||
ddp-common@1.2.9
|
||||
ddp-rate-limiter@1.0.7
|
||||
ddp-server@2.0.0
|
||||
ddp-server@2.0.2
|
||||
diff-sequence@1.0.7
|
||||
dynamic-import@0.1.1
|
||||
ecmascript@0.8.2
|
||||
dynamic-import@0.1.3
|
||||
ecmascript@0.8.3
|
||||
ecmascript-runtime@0.4.1
|
||||
ecmascript-runtime-client@0.4.3
|
||||
ecmascript-runtime-server@0.4.1
|
||||
ejson@1.0.13
|
||||
ejson@1.0.14
|
||||
email@1.2.3
|
||||
example-simple@0.0.0
|
||||
fourseven:scss@4.5.4
|
||||
|
@ -33,22 +33,23 @@ id-map@1.0.9
|
|||
livedata@1.0.18
|
||||
localstorage@1.1.1
|
||||
logging@1.1.17
|
||||
meteor@1.7.0
|
||||
meteor@1.7.2
|
||||
meteor-base@1.1.0
|
||||
meteorhacks:inject-initial@1.0.4
|
||||
meteorhacks:picker@1.0.3
|
||||
minifier-css@1.2.16
|
||||
minifier-js@2.1.1
|
||||
minimongo@1.2.1
|
||||
modules@0.9.2
|
||||
minifier-js@2.1.4
|
||||
minimongo@1.3.2
|
||||
modules@0.10.0
|
||||
modules-runtime@0.8.0
|
||||
mongo@1.1.22
|
||||
mongo@1.2.2
|
||||
mongo-dev-server@1.0.1
|
||||
mongo-id@1.0.6
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@2.2.30
|
||||
ordered-dict@1.0.9
|
||||
percolatestudio:synced-cron@1.1.0
|
||||
promise@0.8.9
|
||||
promise@0.9.0
|
||||
random@1.0.10
|
||||
rate-limit@1.0.8
|
||||
reactive-dict@1.1.9
|
||||
|
@ -61,19 +62,21 @@ session@1.1.7
|
|||
sha@1.0.9
|
||||
shell-server@0.2.4
|
||||
srp@1.0.10
|
||||
standard-minifier-css@1.3.4
|
||||
standard-minifier-js@2.1.1
|
||||
standard-minifier-css@1.3.5
|
||||
standard-minifier-js@2.1.2
|
||||
standard-minifiers@1.1.0
|
||||
tracker@1.1.3
|
||||
underscore@1.0.10
|
||||
url@1.1.0
|
||||
vulcan:accounts@1.7.0
|
||||
vulcan:core@1.7.0
|
||||
vulcan:forms@1.7.0
|
||||
vulcan:i18n@1.7.0
|
||||
vulcan:i18n-en-us@1.7.0
|
||||
vulcan:lib@1.7.0
|
||||
vulcan:routing@1.7.0
|
||||
vulcan:users@1.7.0
|
||||
webapp@1.3.17
|
||||
vulcan:accounts@1.8.0
|
||||
vulcan:core@1.8.0
|
||||
vulcan:debug@1.8.0
|
||||
vulcan:email@1.8.0
|
||||
vulcan:forms@1.8.0
|
||||
vulcan:i18n@1.8.0
|
||||
vulcan:i18n-en-us@1.8.0
|
||||
vulcan:lib@1.8.0
|
||||
vulcan:routing@1.8.0
|
||||
vulcan:users@1.8.0
|
||||
webapp@1.3.19
|
||||
webapp-hashing@1.0.9
|
||||
|
|
21
package-lock.json
generated
Normal file
21
package-lock.json
generated
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "Vulcan",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"flat": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz",
|
||||
"integrity": "sha512-ji/WMv2jdsE+LaznpkIF9Haax0sdpTBozrz/Dtg4qSRMfbs8oVg4ypJunIRYPiMLvH/ed6OflXbnbTIKJhtgeg==",
|
||||
"requires": {
|
||||
"is-buffer": "1.1.5"
|
||||
}
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
|
||||
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,11 +18,13 @@
|
|||
"body-parser": "^1.15.2",
|
||||
"classnames": "^2.2.3",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"cross-fetch": "^0.0.8",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"dataloader": "^1.3.0",
|
||||
"deepmerge": "^1.2.0",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"express": "^4.14.0",
|
||||
"flat": "^4.0.0",
|
||||
"formsy-react": "^0.19.5",
|
||||
"formsy-react-components": "^0.10.1",
|
||||
"graphql": "^0.9.6",
|
||||
|
@ -40,7 +42,6 @@
|
|||
"import": "0.0.6",
|
||||
"intl": "^1.2.4",
|
||||
"intl-locales-supported": "^1.0.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"juice": "^1.11.0",
|
||||
"mailchimp": "^1.1.6",
|
||||
"marked": "^0.3.5",
|
||||
|
@ -73,7 +74,7 @@
|
|||
"rss": "^1.2.1",
|
||||
"sanitize-html": "^1.11.4",
|
||||
"sendy-api": "^0.1.0",
|
||||
"simpl-schema": "^0.2.3",
|
||||
"simpl-schema": "^0.3.2",
|
||||
"speakingurl": "^9.0.0",
|
||||
"stripe": "^4.23.1",
|
||||
"styled-components": "^2.1.1",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Package.describe({
|
||||
name: "boilerplate-generator",
|
||||
summary: "Generates the boilerplate html from program's manifest",
|
||||
version: '1.1.2'
|
||||
version: '1.2.0'
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Components, getRawComponent, replaceComponent } from 'meteor/vulcan:cor
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
import { Link } from 'react-router';
|
||||
import Posts from "meteor/vulcan:posts";
|
||||
import { Posts } from 'meteor/example-forum';
|
||||
import moment from 'moment';
|
||||
|
||||
class CustomPostsItem extends getRawComponent('PostsItem') {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Posts from "meteor/vulcan:posts";
|
||||
import { Posts } from 'meteor/example-forum';
|
||||
|
||||
/*
|
||||
Let's assign a color to each post (why? cause we want to, that's why).
|
||||
|
|
|
@ -10,11 +10,9 @@ VulcanEmail.addEmails({
|
|||
customEmail: {
|
||||
template: "customEmail",
|
||||
path: "/email/custom-email",
|
||||
getProperties() {return {};},
|
||||
subject() {
|
||||
return "My awesome new email";
|
||||
},
|
||||
getTestObject() {return {};}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
|
@ -1,18 +1,18 @@
|
|||
<span class="heading">
|
||||
<a href="{{profileUrl}}">{{postAuthorName}}</a>
|
||||
<a href="{{PostsSingle.user.pageUrl}}">{{PostsSingle.user.displayName}}</a>
|
||||
has created a new AWESOME post:
|
||||
{{#if url}}
|
||||
<a href="{{linkUrl}}" class="action-link">{{postTitle}}</a>
|
||||
{{#if PostsSingle.url}}
|
||||
<a href="{{PostsSingle.linkUrl}}" class="action-link">{{PostsSingle.title}}</a>
|
||||
{{else}}
|
||||
{{postTitle}}
|
||||
{{PostsSingle.title}}
|
||||
{{/if}}
|
||||
</span><br><br>
|
||||
|
||||
{{#if htmlBody}}
|
||||
{{#if PostsSingle.htmlBody}}
|
||||
<div class="post-body">
|
||||
{{{htmlBody}}}
|
||||
{{{PostsSingle.htmlBody}}}
|
||||
</div>
|
||||
<br>
|
||||
{{/if}}
|
||||
|
||||
<a href="{{postUrl}}">Discuss</a><br><br>
|
||||
<a href="{{PostsSingle.pageUrl}}">Discuss</a><br><br>
|
||||
|
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
1
packages/example-forum/lib/client/main.js
Normal file
1
packages/example-forum/lib/client/main.js
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '../modules/index.js';
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import Posts from 'meteor/vulcan:posts';
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
const AdminUsersPosts = ({ document: user }) =>
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { intlShape } from 'meteor/vulcan:i18n';
|
||||
import { Components, registerComponent, getFragment, withMessages } from 'meteor/vulcan:core';
|
||||
import Categories from "meteor/vulcan:categories";
|
||||
import { Categories } from '../../modules/categories/index.js';
|
||||
|
||||
const CategoriesEditForm = (props, context) => {
|
||||
|
|
@ -7,7 +7,7 @@ import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
|||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
import { withRouter } from 'react-router'
|
||||
import { LinkContainer } from 'react-router-bootstrap';
|
||||
import Categories from 'meteor/vulcan:categories';
|
||||
import { Categories } from '../../modules/categories/index.js';
|
||||
import { withApollo } from 'react-apollo';
|
||||
|
||||
class CategoriesList extends PureComponent {
|
||||
|
@ -30,7 +30,7 @@ class CategoriesList extends PureComponent {
|
|||
getCategoryLink(slug) {
|
||||
const categories = this.getCurrentCategoriesArray();
|
||||
return {
|
||||
pathname: '/',
|
||||
pathname: Utils.getRoutePath('posts.list'),
|
||||
query: {
|
||||
...this.props.location.query,
|
||||
cat: categories.includes(slug) ? _.without(categories, slug) : categories.concat([slug])
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { intlShape } from 'meteor/vulcan:i18n';
|
||||
import { Components, registerComponent, getFragment, withMessages } from 'meteor/vulcan:core';
|
||||
import Categories from "meteor/vulcan:categories";
|
||||
import { Categories } from '../../modules/categories/index.js';
|
||||
|
||||
const CategoriesNewForm = (props, context) => {
|
||||
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import { LinkContainer } from 'react-router-bootstrap';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem'
|
||||
import { withRouter } from 'react-router'
|
||||
import Categories from 'meteor/vulcan:categories';
|
||||
import { Categories } from '../../modules/categories/index.js';
|
||||
|
||||
class Category extends PureComponent {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Components, registerComponent, getFragment, withMessages } from 'meteor/vulcan:core';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Comments from "meteor/vulcan:comments";
|
||||
import { Comments } from '../../modules/comments/index.js';
|
||||
|
||||
const CommentsEditForm = (props, context) => {
|
||||
return (
|
|
@ -2,7 +2,7 @@ import { Components, registerComponent, withMessages } from 'meteor/vulcan:core'
|
|||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { intlShape, FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
import Comments from 'meteor/vulcan:comments';
|
||||
import { Comments } from '../../modules/comments/index.js';
|
||||
import moment from 'moment';
|
||||
|
||||
class CommentsItem extends PureComponent {
|
|
@ -1,7 +1,7 @@
|
|||
import { Components, registerComponent, getFragment, withMessages } from 'meteor/vulcan:core';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Comments from "meteor/vulcan:comments";
|
||||
import { Comments } from '../../modules/comments/index.js';
|
||||
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
|
||||
const CommentsNewForm = (props, context) => {
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withCurrentUser, getSetting, Components, registerComponent } from 'meteor/vulcan:core';
|
||||
import { withCurrentUser, getSetting, registerSetting, Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
const Header = (props, context) => {
|
||||
|
||||
const logoUrl = getSetting("logoUrl");
|
||||
const siteTitle = getSetting("title", "My App");
|
||||
const tagline = getSetting("tagline");
|
||||
const logoUrl = getSetting('logoUrl');
|
||||
const siteTitle = getSetting('title', 'My App');
|
||||
const tagline = getSetting('tagline');
|
||||
|
||||
return (
|
||||
<div className="header-wrapper">
|
|
@ -11,7 +11,9 @@ const Layout = ({currentUser, children, currentRoute}) =>
|
|||
<link name="bootstrap" rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css"/>
|
||||
<link name="font-awesome" rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
|
||||
</Helmet>
|
||||
|
||||
|
||||
<Components.HeadTags />
|
||||
|
||||
{currentUser ? <Components.UsersProfileCheck currentUser={currentUser} documentId={currentUser._id} /> : null}
|
||||
|
||||
<Components.Header />
|
|
@ -1,4 +1,4 @@
|
|||
import { registerComponent, Components } from 'meteor/vulcan:core';
|
||||
import { registerComponent, Components, Utils } from 'meteor/vulcan:core';
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import { intlShape } from 'meteor/vulcan:i18n';
|
||||
import Formsy from 'formsy-react';
|
||||
|
@ -42,7 +42,7 @@ class SearchForm extends Component{
|
|||
const query = data.searchQuery === '' ? routerQuery : {...routerQuery, query: data.searchQuery};
|
||||
|
||||
delay(() => {
|
||||
router.push({pathname: "/", query: query});
|
||||
router.push({pathname: Utils.getRoutePath('posts.list'), query: query});
|
||||
}, 700 );
|
||||
|
||||
}
|
71
packages/example-forum/lib/components/common/Vote.jsx
Normal file
71
packages/example-forum/lib/components/common/Vote.jsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { Components, registerComponent, withMessages } from 'meteor/vulcan:core';
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { withVote, hasVotedClient } from 'meteor/vulcan:voting';
|
||||
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
||||
|
||||
class Vote extends PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.vote = this.vote.bind(this);
|
||||
this.getActionClass = this.getActionClass.bind(this);
|
||||
this.hasVoted = this.hasVoted.bind(this);
|
||||
}
|
||||
|
||||
vote(e) {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
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 {
|
||||
this.props.vote({document, voteType: 'upvote', collection, currentUser: this.props.currentUser});
|
||||
}
|
||||
}
|
||||
|
||||
hasVoted() {
|
||||
return hasVotedClient({document: this.props.document, voteType: 'upvote'})
|
||||
}
|
||||
|
||||
getActionClass() {
|
||||
|
||||
const actionsClass = classNames(
|
||||
'vote-button',
|
||||
{upvoted: this.hasVoted()},
|
||||
);
|
||||
|
||||
return actionsClass;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={this.getActionClass()}>
|
||||
<a className="upvote-button" onClick={this.vote}>
|
||||
<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
|
||||
};
|
||||
|
||||
Vote.contextTypes = {
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
registerComponent('Vote', Vote, withMessages, withVote);
|
|
@ -1,7 +1,7 @@
|
|||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import Posts from "meteor/vulcan:posts";
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
|
||||
const PostsCommenters = ({post}) => {
|
||||
return (
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
import { withList, withCurrentUser, Components, registerComponent, Utils } from 'meteor/vulcan:core';
|
||||
import Comments from 'meteor/vulcan:comments';
|
||||
import { Comments } from '../../modules/comments/index.js';
|
||||
|
||||
const PostsCommentsThread = (props, /* context*/) => {
|
||||
|
21
packages/example-forum/lib/components/posts/PostsDaily.jsx
Normal file
21
packages/example-forum/lib/components/posts/PostsDaily.jsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Components, registerComponent, getSetting, registerSetting } from 'meteor/vulcan:core';
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
registerSetting('forum.numberOfDays', 5, 'Number of days to display in Daily view');
|
||||
|
||||
const PostsDaily = props => {
|
||||
// const terms = props.location && props.location.query;
|
||||
const numberOfDays = getSetting('forum.numberOfDays', 5);
|
||||
const terms = {
|
||||
view: 'top',
|
||||
after: moment().subtract(numberOfDays - 1, 'days').format('YYYY-MM-DD'),
|
||||
before: moment().format('YYYY-MM-DD'),
|
||||
};
|
||||
|
||||
return <Components.PostsDailyList terms={terms}/>
|
||||
};
|
||||
|
||||
PostsDaily.displayName = 'PostsDaily';
|
||||
|
||||
registerComponent('PostsDaily', PostsDaily);
|
|
@ -2,8 +2,8 @@ import React, { PureComponent } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
import Posts from 'meteor/vulcan:posts';
|
||||
import { withCurrentUser, withList, getSetting, Components, getRawComponent, registerComponent } from 'meteor/vulcan:core';
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
import { withCurrentUser, withList, getSetting, registerSetting, Components, getRawComponent, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
class PostsDailyList extends PureComponent {
|
||||
|
||||
|
@ -51,7 +51,7 @@ class PostsDailyList extends PureComponent {
|
|||
// variant 1: reload everything each time (works with polling)
|
||||
loadMoreDays(e) {
|
||||
e.preventDefault();
|
||||
const numberOfDays = getSetting('numberOfDays', 5);
|
||||
const numberOfDays = getSetting('forum.numberOfDays', 5);
|
||||
const loadMoreAfter = moment(this.state.after, 'YYYY-MM-DD').subtract(numberOfDays, 'days').format('YYYY-MM-DD');
|
||||
|
||||
this.props.loadMore({
|
||||
|
@ -68,7 +68,7 @@ class PostsDailyList extends PureComponent {
|
|||
// variant 2: only load new data (need to disable polling)
|
||||
loadMoreDaysInc(e) {
|
||||
e.preventDefault();
|
||||
const numberOfDays = getSetting('numberOfDays', 5);
|
||||
const numberOfDays = getSetting('forum.numberOfDays', 5);
|
||||
const loadMoreAfter = moment(this.state.after, 'YYYY-MM-DD').subtract(numberOfDays, 'days').format('YYYY-MM-DD');
|
||||
const loadMoreBefore = moment(this.state.after, 'YYYY-MM-DD').subtract(1, 'days').format('YYYY-MM-DD');
|
||||
|
||||
|
@ -105,8 +105,8 @@ PostsDailyList.propTypes = {
|
|||
};
|
||||
|
||||
PostsDailyList.defaultProps = {
|
||||
days: getSetting('numberOfDays', 5),
|
||||
increment: getSetting('numberOfDays', 5)
|
||||
days: getSetting('forum.numberOfDays', 5),
|
||||
increment: getSetting('forum.numberOfDays', 5)
|
||||
};
|
||||
|
||||
const options = {
|
|
@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { Components, registerComponent, getFragment, withMessages, withCurrentUser } from 'meteor/vulcan:core';
|
||||
import { intlShape } from 'meteor/vulcan:i18n';
|
||||
import Posts from "meteor/vulcan:posts";
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
import Users from "meteor/vulcan:users";
|
||||
import { withRouter } from 'react-router'
|
||||
|
|
@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
import { Link } from 'react-router';
|
||||
import Posts from "meteor/vulcan:posts";
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
import moment from 'moment';
|
||||
|
||||
class PostsItem extends PureComponent {
|
||||
|
@ -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} />
|
||||
</div>
|
||||
|
||||
{post.thumbnailUrl ? <Components.PostsThumbnail post={post}/> : null}
|
|
@ -1,7 +1,7 @@
|
|||
import { Components, registerComponent, withList, withCurrentUser, Utils } from 'meteor/vulcan:core';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Posts from 'meteor/vulcan:posts';
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
import Alert from 'react-bootstrap/lib/Alert'
|
||||
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
||||
import classNames from 'classnames';
|
||||
|
@ -17,7 +17,7 @@ const PostsList = ({className, results, loading, count, totalCount, loadMore, sh
|
|||
const hasMore = totalCount > results.length;
|
||||
|
||||
return (
|
||||
<div className={classNames(className, 'posts-list')}>
|
||||
<div className={classNames(className, 'posts-list', `posts-list-${terms.view}`)}>
|
||||
{showHeader ? <Components.PostsListHeader/> : null}
|
||||
{error ? <Error error={Utils.decodeIntlError(error)} /> : null }
|
||||
<div className="posts-list-content">
|
|
@ -1,5 +1,5 @@
|
|||
import { Components, registerComponent, getRawComponent, getFragment, withMessages } from 'meteor/vulcan:core';
|
||||
import Posts from "meteor/vulcan:posts";
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { intlShape, FormattedMessage } from 'meteor/vulcan:i18n';
|
|
@ -1,5 +1,5 @@
|
|||
import { Components, registerComponent, withDocument, withCurrentUser, getActions, withMutation } from 'meteor/vulcan:core';
|
||||
import Posts from 'meteor/vulcan:posts';
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
|
@ -5,10 +5,10 @@ const PostsStats = ({post}) => {
|
|||
|
||||
return (
|
||||
<div className="posts-stats">
|
||||
{post.score ? <span className="posts-stats-item" title="Score"><Components.Icon name="score"/> {Math.floor(post.score*10000)/10000} <span className="sr-only">Score</span></span> : ""}
|
||||
<span className="posts-stats-item" title="Upvotes"><Components.Icon name="upvote"/> {post.upvotes} <span className="sr-only">Upvotes</span></span>
|
||||
<span className="posts-stats-item" title="Clicks"><Components.Icon name="clicks"/> {post.clickCount} <span className="sr-only">Clicks</span></span>
|
||||
<span className="posts-stats-item" title="Views"><Components.Icon name="views"/> {post.viewCount} <span className="sr-only">Views</span></span>
|
||||
{post.score ? <span className="posts-stats-item" title="Score"><Components.Icon name="score"/> {Math.floor((post.score || 0)*10000)/10000} <span className="sr-only">Score</span></span> : ""}
|
||||
<span className="posts-stats-item" title="Upvotes"><Components.Icon name="upvote"/> {post.baseScore || 0} <span className="sr-only">Upvotes</span></span>
|
||||
<span className="posts-stats-item" title="Clicks"><Components.Icon name="clicks"/> {post.clickCount || 0} <span className="sr-only">Clicks</span></span>
|
||||
<span className="posts-stats-item" title="Views"><Components.Icon name="views"/> {post.viewCount || 0} <span className="sr-only">Views</span></span>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import React from 'react';
|
||||
import Posts from "meteor/vulcan:posts";
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
|
||||
const PostsThumbnail = ({post}) =>
|
||||
<a className="posts-thumbnail" href={Posts.getLink(post)} target={Posts.getLinkTarget(post)}>
|
|
@ -1,4 +1,4 @@
|
|||
import { registerComponent, withCurrentUser } from 'meteor/vulcan:core';
|
||||
import { registerComponent, withCurrentUser, Utils } from 'meteor/vulcan:core';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
||||
|
@ -10,10 +10,10 @@ import Users from 'meteor/vulcan:users';
|
|||
|
||||
const PostsViews = (props, context) => {
|
||||
|
||||
let views = ["top", "new", "best"];
|
||||
const adminViews = ["pending", "rejected", "scheduled"];
|
||||
let views = ['top', 'new', 'best'];
|
||||
const adminViews = ['pending', 'rejected', 'scheduled'];
|
||||
|
||||
if (Users.canDo(props.currentUser, "posts.edit.all")) {
|
||||
if (Users.canDo(props.currentUser, 'posts.edit.all')) {
|
||||
views = views.concat(adminViews);
|
||||
}
|
||||
|
||||
|
@ -24,18 +24,18 @@ const PostsViews = (props, context) => {
|
|||
<DropdownButton
|
||||
bsStyle="default"
|
||||
className="views btn-secondary"
|
||||
title={context.intl.formatMessage({id: "posts.view"})}
|
||||
title={context.intl.formatMessage({id: 'posts.view'})}
|
||||
id="views-dropdown"
|
||||
>
|
||||
{views.map(view =>
|
||||
<LinkContainer key={view} to={{pathname: "/", query: {...query, view: view}}} className="dropdown-item">
|
||||
<LinkContainer key={view} to={{pathname: Utils.getRoutePath('posts.list'), query: {...query, view: view}}} className="dropdown-item">
|
||||
<MenuItem>
|
||||
<FormattedMessage id={"posts."+view}/>
|
||||
</MenuItem>
|
||||
</LinkContainer>
|
||||
)}
|
||||
<LinkContainer to={"/daily"} className="dropdown-item">
|
||||
<MenuItem className={"bar"}>
|
||||
<LinkContainer to="/daily" className="dropdown-item">
|
||||
<MenuItem className="bar">
|
||||
<FormattedMessage id="posts.daily"/>
|
||||
</MenuItem>
|
||||
</LinkContainer>
|
||||
|
@ -50,7 +50,7 @@ PostsViews.propTypes = {
|
|||
};
|
||||
|
||||
PostsViews.defaultProps = {
|
||||
defaultView: "top"
|
||||
defaultView: 'top'
|
||||
};
|
||||
|
||||
PostsViews.contextTypes = {
|
||||
|
@ -58,6 +58,6 @@ PostsViews.contextTypes = {
|
|||
intl: intlShape
|
||||
};
|
||||
|
||||
PostsViews.displayName = "PostsViews";
|
||||
PostsViews.displayName = 'PostsViews';
|
||||
|
||||
registerComponent('PostsViews', PostsViews, withCurrentUser, withRouter);
|
26
packages/example-forum/lib/modules/categories/collection.js
Normal file
26
packages/example-forum/lib/modules/categories/collection.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
|
||||
The Categories collection
|
||||
|
||||
*/
|
||||
|
||||
import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core';
|
||||
import schema from './schema.js';
|
||||
|
||||
/**
|
||||
* @summary The global namespace for Categories.
|
||||
* @namespace Categories
|
||||
*/
|
||||
export const Categories = createCollection({
|
||||
|
||||
collectionName: 'Categories',
|
||||
|
||||
typeName: 'Category',
|
||||
|
||||
schema,
|
||||
|
||||
resolvers: getDefaultResolvers('Categories'),
|
||||
|
||||
mutations: getDefaultMutations('Categories'),
|
||||
|
||||
});
|
|
@ -1,4 +1,10 @@
|
|||
import Posts from "meteor/vulcan:posts";
|
||||
/*
|
||||
|
||||
Custom fields on Posts collection
|
||||
|
||||
*/
|
||||
|
||||
import { Posts } from '../../modules/posts/index.js';
|
||||
import { getCategoriesAsOptions } from './schema.js';
|
||||
|
||||
Posts.addField([
|
||||
|
@ -6,14 +12,14 @@ Posts.addField([
|
|||
fieldName: 'categories',
|
||||
fieldSchema: {
|
||||
type: Array,
|
||||
control: "checkboxgroup",
|
||||
control: 'checkboxgroup',
|
||||
optional: true,
|
||||
insertableBy: ['members'],
|
||||
editableBy: ['members'],
|
||||
viewableBy: ['guests'],
|
||||
form: {
|
||||
noselect: true,
|
||||
type: "bootstrap-category",
|
||||
type: 'bootstrap-category',
|
||||
order: 50,
|
||||
options: formProps => getCategoriesAsOptions(formProps.client),
|
||||
},
|
25
packages/example-forum/lib/modules/categories/fragments.js
Normal file
25
packages/example-forum/lib/modules/categories/fragments.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { registerFragment } from 'meteor/vulcan:core';
|
||||
|
||||
// note: fragment used by default on CategoriesList & PostsList fragments
|
||||
registerFragment(`
|
||||
fragment CategoriesMinimumInfo on Category {
|
||||
# vulcan:categories
|
||||
_id
|
||||
name
|
||||
slug
|
||||
}
|
||||
`);
|
||||
|
||||
registerFragment(`
|
||||
fragment CategoriesList on Category {
|
||||
# vulcan:categories
|
||||
...CategoriesMinimumInfo
|
||||
description
|
||||
order
|
||||
image
|
||||
parentId
|
||||
parent {
|
||||
...CategoriesMinimumInfo
|
||||
}
|
||||
}
|
||||
`);
|
|
@ -1,5 +1,5 @@
|
|||
import Posts from "meteor/vulcan:posts";
|
||||
import Categories from "./collection.js";
|
||||
import { Posts } from '../posts/index.js';
|
||||
import { Categories } from './collection.js';
|
||||
import { Utils } from 'meteor/vulcan:core';
|
||||
|
||||
/**
|
||||
|
@ -30,7 +30,7 @@ Categories.getChildren = function (category) {
|
|||
var categoriesArray = [];
|
||||
|
||||
var getChildren = function recurse (categories) {
|
||||
var children = Categories.find({parentId: {$in: _.pluck(categories, "_id")}}).fetch()
|
||||
var children = Categories.find({parentId: {$in: _.pluck(categories, '_id')}}).fetch()
|
||||
if (children.length > 0) {
|
||||
categoriesArray = categoriesArray.concat(children);
|
||||
recurse(children);
|
||||
|
@ -52,9 +52,9 @@ Posts.getCategories = function (post) {
|
|||
* @param {Object} category
|
||||
*/
|
||||
Categories.getUrl = function (category, isAbsolute) {
|
||||
isAbsolute = typeof isAbsolute === "undefined" ? false : isAbsolute; // default to false
|
||||
const prefix = isAbsolute ? Utils.getSiteUrl().slice(0,-1) : "";
|
||||
// return prefix + FlowRouter.path("postsCategory", category);
|
||||
isAbsolute = typeof isAbsolute === 'undefined' ? false : isAbsolute; // default to false
|
||||
const prefix = isAbsolute ? Utils.getSiteUrl().slice(0,-1) : '';
|
||||
// return prefix + FlowRouter.path('postsCategory', category);
|
||||
return `${prefix}/?cat=${category.slug}`;
|
||||
};
|
||||
/**
|
||||
|
@ -62,6 +62,6 @@ Categories.getUrl = function (category, isAbsolute) {
|
|||
* @param {Object} category
|
||||
*/
|
||||
Categories.getCounterName = function (category) {
|
||||
return category._id + "-postsCount";
|
||||
return category._id + '-postsCount';
|
||||
}
|
||||
|
8
packages/example-forum/lib/modules/categories/index.js
Normal file
8
packages/example-forum/lib/modules/categories/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export * from './collection.js';
|
||||
|
||||
import './fragments.js';
|
||||
import './views.js';
|
||||
import './custom_fields.js';
|
||||
import './helpers.js';
|
||||
import './permissions.js';
|
||||
import './parameters.js';
|
61
packages/example-forum/lib/modules/categories/parameters.js
Normal file
61
packages/example-forum/lib/modules/categories/parameters.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
|
||||
Categories parameter
|
||||
|
||||
*/
|
||||
|
||||
import { addCallback, getSetting, registerSetting, getFragment, runQuery } from 'meteor/vulcan:core';
|
||||
import gql from 'graphql-tag';
|
||||
import { Categories } from './collection.js';
|
||||
|
||||
registerSetting('forum.categoriesFilter', 'union', 'Display posts belonging to all (“intersection”) or at least one of (“union”) the selected categories');
|
||||
|
||||
// Category Posts Parameters
|
||||
// Add a 'categories' property to terms which can be used to filter *all* existing Posts views.
|
||||
function PostsCategoryParameter(parameters, terms, apolloClient) {
|
||||
|
||||
// get category slugs
|
||||
const cat = terms.cat || terms['cat[]'];
|
||||
const categoriesSlugs = Array.isArray(cat) ? cat : [cat];
|
||||
let allCategories = [];
|
||||
|
||||
if (cat && cat.length) {
|
||||
|
||||
// get all categories
|
||||
// note: specify all arguments, see https://github.com/apollographql/apollo-client/issues/2051
|
||||
const query = `
|
||||
query GetCategories($terms: JSON) {
|
||||
CategoriesList(terms: $terms) {
|
||||
_id
|
||||
slug
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
if (Meteor.isClient) {
|
||||
// get categories from Redux store
|
||||
allCategories = apolloClient.readQuery({
|
||||
query: gql`${query}`,
|
||||
variables: {terms: {limit: 0, itemsPerPage: 0}}
|
||||
}).CategoriesList;
|
||||
} else {
|
||||
// TODO: figure out how to make this async without messing up withList on the client
|
||||
// get categories through GraphQL API using runQuery
|
||||
// const results = await runQuery(query);
|
||||
// allCategories = results.data.CategoriesList;
|
||||
allCategories = Categories.find().fetch();
|
||||
}
|
||||
|
||||
// get corresponding category ids
|
||||
const categoriesIds = _.pluck(_.filter(allCategories, category => _.contains(categoriesSlugs, category.slug)), '_id');
|
||||
|
||||
const operator = getSetting('forum.categoriesFilter', 'union') === 'union' ? '$in' : '$all';
|
||||
|
||||
// parameters.selector = Meteor.isClient ? {...parameters.selector, 'categories._id': {$in: categoriesIds}} : {...parameters.selector, categories: {[operator]: categoriesIds}};
|
||||
parameters.selector = {...parameters.selector, categories: {[operator]: categoriesIds}};
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
addCallback('posts.parameters', PostsCategoryParameter);
|
|
@ -1,19 +1,25 @@
|
|||
/*
|
||||
|
||||
Categories permissions
|
||||
|
||||
*/
|
||||
|
||||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
const guestsActions = [
|
||||
"categories.view"
|
||||
'categories.view'
|
||||
];
|
||||
Users.groups.guests.can(guestsActions);
|
||||
|
||||
const membersActions = [
|
||||
"categories.view"
|
||||
'categories.view'
|
||||
];
|
||||
Users.groups.members.can(membersActions);
|
||||
|
||||
const adminActions = [
|
||||
"categories.view",
|
||||
"categories.new",
|
||||
"categories.edit.all",
|
||||
"categories.remove.all"
|
||||
'categories.view',
|
||||
'categories.new',
|
||||
'categories.edit.all',
|
||||
'categories.remove.all'
|
||||
];
|
||||
Users.groups.admins.can(adminActions);
|
|
@ -1,4 +1,11 @@
|
|||
/*
|
||||
|
||||
Categories schema
|
||||
|
||||
*/
|
||||
|
||||
import { Utils } from 'meteor/vulcan:core';
|
||||
import { Categories } from './collection.js';
|
||||
|
||||
export function getCategories (apolloClient) {
|
||||
|
||||
|
@ -78,6 +85,18 @@ const schema = {
|
|||
viewableBy: ['guests'],
|
||||
insertableBy: ['members'],
|
||||
editableBy: ['members'],
|
||||
onInsert: category => {
|
||||
// if no slug has been provided, generate one
|
||||
const slug = category.slug || Utils.slugify(category.name);
|
||||
return Utils.getUnusedSlug(Categories, slug);
|
||||
},
|
||||
onEdit: (modifier, category) => {
|
||||
// if slug is changing
|
||||
if (modifier.$set && modifier.$set.slug && modifier.$set.slug !== category.slug) {
|
||||
const slug = modifier.$set.slug;
|
||||
return Utils.getUnusedSlug(Categories, slug);
|
||||
}
|
||||
}
|
||||
},
|
||||
image: {
|
||||
type: String,
|
15
packages/example-forum/lib/modules/categories/views.js
Normal file
15
packages/example-forum/lib/modules/categories/views.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
|
||||
Default sort
|
||||
|
||||
*/
|
||||
|
||||
import { Categories } from './collection.js';
|
||||
|
||||
Categories.addDefaultView(terms => ({
|
||||
options: {
|
||||
sort: {
|
||||
order: 1
|
||||
}
|
||||
}
|
||||
}));
|
37
packages/example-forum/lib/modules/comments/collection.js
Normal file
37
packages/example-forum/lib/modules/comments/collection.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
|
||||
Comments collection
|
||||
|
||||
*/
|
||||
|
||||
import schema from './schema.js';
|
||||
import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core';
|
||||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
/**
|
||||
* @summary The global namespace for Comments.
|
||||
* @namespace Comments
|
||||
*/
|
||||
export const Comments = createCollection({
|
||||
|
||||
collectionName: 'Comments',
|
||||
|
||||
typeName: 'Comment',
|
||||
|
||||
schema,
|
||||
|
||||
resolvers: getDefaultResolvers('Comments'),
|
||||
|
||||
mutations: getDefaultMutations('Comments'),
|
||||
|
||||
});
|
||||
|
||||
Comments.checkAccess = (currentUser, comment) => {
|
||||
if (Users.isAdmin(currentUser) || Users.owns(currentUser, comment)) { // admins can always see everything, users can always see their own posts
|
||||
return true;
|
||||
} else if (comment.isDeleted) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import Posts from "meteor/vulcan:posts";
|
||||
import Users from "meteor/vulcan:users";
|
||||
import { Posts } from '../posts/index.js';
|
||||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
Users.addField([
|
||||
/**
|
||||
Count of the user's comments
|
||||
*/
|
||||
{
|
||||
fieldName: "commentCount",
|
||||
fieldName: 'commentCount',
|
||||
fieldSchema: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
|
@ -21,7 +21,7 @@ Posts.addField([
|
|||
Count of the post's comments
|
||||
*/
|
||||
{
|
||||
fieldName: "commentCount",
|
||||
fieldName: 'commentCount',
|
||||
fieldSchema: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
|
@ -33,7 +33,7 @@ Posts.addField([
|
|||
An array containing the `_id`s of commenters
|
||||
*/
|
||||
{
|
||||
fieldName: "commenters",
|
||||
fieldName: 'commenters',
|
||||
fieldSchema: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
|
@ -50,7 +50,7 @@ Posts.addField([
|
|||
}
|
||||
},
|
||||
{
|
||||
fieldName: "commenters.$",
|
||||
fieldName: 'commenters.$',
|
||||
fieldSchema: {
|
||||
type: String,
|
||||
optional: true
|
30
packages/example-forum/lib/modules/comments/fragments.js
Normal file
30
packages/example-forum/lib/modules/comments/fragments.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { registerFragment } from 'meteor/vulcan:core';
|
||||
|
||||
// ----------------------------- Comments ------------------------------ //
|
||||
|
||||
registerFragment(`
|
||||
fragment CommentsList on Comment {
|
||||
# vulcan:comments
|
||||
_id
|
||||
postId
|
||||
parentCommentId
|
||||
topLevelCommentId
|
||||
body
|
||||
htmlBody
|
||||
postedAt
|
||||
# vulcan:users
|
||||
userId
|
||||
user {
|
||||
...UsersMinimumInfo
|
||||
}
|
||||
# vulcan:posts
|
||||
post {
|
||||
_id
|
||||
commentCount
|
||||
commenters {
|
||||
...UsersMinimumInfo
|
||||
}
|
||||
}
|
||||
# vulcan:voting
|
||||
}
|
||||
`);
|
|
@ -1,5 +1,11 @@
|
|||
import Comments from './collection.js';
|
||||
import Posts from 'meteor/vulcan:posts';
|
||||
/*
|
||||
|
||||
Comments helpers
|
||||
|
||||
*/
|
||||
|
||||
import { Comments } from './index.js';
|
||||
import { Posts } from '../posts/index.js';
|
||||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
//////////////////
|
7
packages/example-forum/lib/modules/comments/index.js
Normal file
7
packages/example-forum/lib/modules/comments/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export * from './collection.js';
|
||||
|
||||
import './fragments.js';
|
||||
import './custom_fields.js';
|
||||
import './helpers.js';
|
||||
import './permissions.js';
|
||||
import './views.js';
|
30
packages/example-forum/lib/modules/comments/permissions.js
Normal file
30
packages/example-forum/lib/modules/comments/permissions.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
|
||||
Comments permissions
|
||||
|
||||
*/
|
||||
|
||||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
const guestsActions = [
|
||||
'comments.view'
|
||||
];
|
||||
Users.groups.guests.can(guestsActions);
|
||||
|
||||
const membersActions = [
|
||||
'comments.view',
|
||||
'comments.new',
|
||||
'comments.edit.own',
|
||||
'comments.remove.own',
|
||||
'comments.upvote',
|
||||
'comments.cancelUpvote',
|
||||
'comments.downvote',
|
||||
'comments.cancelDownvote'
|
||||
];
|
||||
Users.groups.members.can(membersActions);
|
||||
|
||||
const adminActions = [
|
||||
'comments.edit.all',
|
||||
'comments.remove.all'
|
||||
];
|
||||
Users.groups.admins.can(adminActions);
|
|
@ -1,4 +1,12 @@
|
|||
/*
|
||||
|
||||
Comments schema
|
||||
|
||||
*/
|
||||
|
||||
import Users from 'meteor/vulcan:users';
|
||||
import marked from 'marked';
|
||||
import { Utils } from 'meteor/vulcan:core';
|
||||
|
||||
/**
|
||||
* @summary Comments schema
|
||||
|
@ -97,6 +105,16 @@ const schema = {
|
|||
type: String,
|
||||
optional: true,
|
||||
viewableBy: ['guests'],
|
||||
onInsert: (comment) => {
|
||||
if (comment.body) {
|
||||
return Utils.sanitize(marked(comment.body));
|
||||
}
|
||||
},
|
||||
onEdit: (modifier, comment) => {
|
||||
if (modifier.$set.body) {
|
||||
return Utils.sanitize(marked(modifier.$set.body));
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
The comment author's name
|
||||
|
@ -112,14 +130,6 @@ const schema = {
|
|||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
Whether the comment is inactive. Inactive comments' scores gets recalculated less often
|
||||
*/
|
||||
inactive: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
viewableBy: ['guests'],
|
||||
},
|
||||
/**
|
||||
The post's `_id`
|
||||
*/
|
||||
|
@ -184,7 +194,21 @@ const schema = {
|
|||
type: String,
|
||||
optional: true,
|
||||
viewableBy: ['admins'],
|
||||
}
|
||||
},
|
||||
|
||||
// GraphQL only fields
|
||||
|
||||
pageUrl: {
|
||||
type: String,
|
||||
optional: true,
|
||||
resolveAs: {
|
||||
fieldName: 'pageUrl',
|
||||
type: 'String',
|
||||
resolver: (comment, args, context) => {
|
||||
return context.Comments.getPageUrl(comment, true);
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default schema;
|
21
packages/example-forum/lib/modules/comments/views.js
Normal file
21
packages/example-forum/lib/modules/comments/views.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
|
||||
Comments views
|
||||
|
||||
*/
|
||||
|
||||
import { Comments } from './index.js';
|
||||
|
||||
Comments.addView('postComments', function (terms) {
|
||||
return {
|
||||
selector: {postId: terms.postId},
|
||||
options: {sort: {postedAt: -1}}
|
||||
};
|
||||
});
|
||||
|
||||
Comments.addView('userComments', function (terms) {
|
||||
return {
|
||||
selector: {userId: terms.userId},
|
||||
options: {sort: {postedAt: -1}}
|
||||
};
|
||||
});
|
65
packages/example-forum/lib/modules/components.js
Normal file
65
packages/example-forum/lib/modules/components.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
|
||||
// common
|
||||
|
||||
import '../components/common/Footer.jsx';
|
||||
import '../components/common/Header.jsx';
|
||||
import '../components/common/Layout.jsx';
|
||||
import '../components/common/Logo.jsx';
|
||||
import '../components/common/Newsletter.jsx';
|
||||
import '../components/common/NewsletterButton.jsx';
|
||||
import '../components/common/SearchForm.jsx';
|
||||
import '../components/common/Vote.jsx';
|
||||
|
||||
// posts
|
||||
|
||||
import '../components/posts/PostsHome.jsx';
|
||||
import '../components/posts/PostsSingle.jsx';
|
||||
import '../components/posts/PostsNewButton.jsx';
|
||||
import '../components/posts/PostsLoadMore.jsx';
|
||||
import '../components/posts/PostsNoMore.jsx';
|
||||
import '../components/posts/PostsNoResults.jsx';
|
||||
import '../components/posts/PostsItem.jsx';
|
||||
import '../components/posts/PostsLoading.jsx';
|
||||
import '../components/posts/PostsViews.jsx';
|
||||
import '../components/posts/PostsList.jsx';
|
||||
import '../components/posts/PostsListHeader.jsx';
|
||||
import '../components/posts/PostsCategories.jsx';
|
||||
import '../components/posts/PostsCommenters.jsx';
|
||||
import '../components/posts/PostsPage.jsx';
|
||||
import '../components/posts/PostsStats.jsx';
|
||||
import '../components/posts/PostsDaily.jsx';
|
||||
import '../components/posts/PostsDailyList.jsx';
|
||||
import '../components/posts/PostsDay.jsx';
|
||||
import '../components/posts/PostsThumbnail.jsx';
|
||||
import '../components/posts/PostsEditForm.jsx';
|
||||
import '../components/posts/PostsNewForm.jsx';
|
||||
import '../components/posts/PostsCommentsThread.jsx';
|
||||
|
||||
// comments
|
||||
|
||||
import '../components/comments/CommentsItem.jsx';
|
||||
import '../components/comments/CommentsList.jsx';
|
||||
import '../components/comments/CommentsNode.jsx';
|
||||
import '../components/comments/CommentsNewForm.jsx';
|
||||
import '../components/comments/CommentsEditForm.jsx';
|
||||
import '../components/comments/CommentsLoadMore.jsx';
|
||||
|
||||
// categories
|
||||
|
||||
import '../components/categories/CategoriesList.jsx';
|
||||
import '../components/categories/CategoriesNode.jsx';
|
||||
import '../components/categories/Category.jsx';
|
||||
import '../components/categories/CategoriesEditForm.jsx';
|
||||
import '../components/categories/CategoriesNewForm.jsx';
|
||||
|
||||
// users
|
||||
|
||||
import '../components/users/UsersSingle.jsx';
|
||||
import '../components/users/UsersAccount.jsx';
|
||||
import '../components/users/UsersEditForm.jsx';
|
||||
import '../components/users/UsersProfile.jsx';
|
||||
import '../components/users/UsersProfileCheck.jsx';
|
||||
import '../components/users/UsersAvatar.jsx';
|
||||
import '../components/users/UsersName.jsx';
|
||||
import '../components/users/UsersMenu.jsx';
|
||||
import '../components/users/UsersAccountMenu.jsx';
|
6
packages/example-forum/lib/modules/config.js
Normal file
6
packages/example-forum/lib/modules/config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
Users.avatar.setOptions({
|
||||
'gravatarDefault': 'mm',
|
||||
'defaultImageUrl': 'http://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&f=y'
|
||||
});
|
48
packages/example-forum/lib/modules/fragments.js
Normal file
48
packages/example-forum/lib/modules/fragments.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { registerFragment } from 'meteor/vulcan:core';
|
||||
|
||||
// ------------------------------ Vote ------------------------------ //
|
||||
|
||||
// note: fragment used by default on the UsersProfile fragment
|
||||
registerFragment(`
|
||||
fragment VotedItem on Vote {
|
||||
# vulcan:voting
|
||||
documentId
|
||||
power
|
||||
votedAt
|
||||
}
|
||||
`);
|
||||
|
||||
// ------------------------------ Users ------------------------------ //
|
||||
|
||||
// note: fragment used by default on UsersProfile, PostsList & CommentsList fragments
|
||||
registerFragment(`
|
||||
fragment UsersMinimumInfo on User {
|
||||
# vulcan:users
|
||||
_id
|
||||
slug
|
||||
username
|
||||
displayName
|
||||
emailHash
|
||||
avatarUrl
|
||||
}
|
||||
`);
|
||||
|
||||
registerFragment(`
|
||||
fragment UsersProfile on User {
|
||||
# vulcan:users
|
||||
...UsersMinimumInfo
|
||||
createdAt
|
||||
isAdmin
|
||||
bio
|
||||
htmlBio
|
||||
twitterUsername
|
||||
website
|
||||
groups
|
||||
karma
|
||||
# vulcan:posts
|
||||
postCount
|
||||
# vulcan:comments
|
||||
commentCount
|
||||
}
|
||||
`);
|
||||
|
88
packages/example-forum/lib/modules/i18n.js
Normal file
88
packages/example-forum/lib/modules/i18n.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { addStrings } from 'meteor/vulcan:core';
|
||||
|
||||
addStrings('en', {
|
||||
|
||||
'posts.new_post': 'New Post',
|
||||
'posts.edit': 'Edit',
|
||||
'posts.edit_success': 'Post “{title}” edited.',
|
||||
'posts.delete': 'Delete',
|
||||
'posts.delete_confirm': 'Delete post “{title}”?',
|
||||
'posts.delete_success': 'Post “{title}” deleted.',
|
||||
'posts.title': 'Title',
|
||||
'posts.url': 'URL',
|
||||
'posts.body': 'Body',
|
||||
'posts.categories': 'Categories',
|
||||
'posts.thumbnailUrl': 'Thumbnail URL',
|
||||
'posts.status': 'Status',
|
||||
'posts.sticky': 'Sticky',
|
||||
'posts.load_more': 'Load More',
|
||||
'posts.load_more_days': 'Load More Days',
|
||||
'posts.no_more': 'No more posts.',
|
||||
'posts.no_results': 'No posts to display.',
|
||||
'posts.search': 'Search',
|
||||
'posts.view': 'View',
|
||||
'posts.top': 'Top',
|
||||
'posts.new': 'New',
|
||||
'posts.best': 'Best',
|
||||
'posts.pending': 'Pending',
|
||||
'posts.rejected': 'Rejected',
|
||||
'posts.scheduled': 'Scheduled',
|
||||
'posts.daily': 'Daily',
|
||||
'posts.clear_thumbnail': 'Clear Thumbnail',
|
||||
'posts.clear_thumbnail?': 'Clear thumbnail?',
|
||||
'posts.enter_thumbnail_url': 'Enter URL',
|
||||
'posts.created_message': 'Post created.',
|
||||
'posts.rate_limit_error': 'Please wait {value} seconds before posting again.',
|
||||
'posts.sign_up_or_log_in_first': 'Please sign up or log in first.',
|
||||
'posts.postedAt': 'Posted at',
|
||||
'posts.dateNotDefined': 'Date not defined',
|
||||
'posts.subscribe': 'Subscribe',
|
||||
'posts.unsubscribe': 'Unsubscribe',
|
||||
'posts.subscribed': 'You have subscribed to “{name}” comments.',
|
||||
'posts.unsubscribed': 'You have unsubscribed from “{name}” comments.',
|
||||
'posts.subscribed_posts' : 'Posts subscribed to',
|
||||
'posts.link_already_posted': 'This link has already been posted.',
|
||||
'posts.max_per_day': 'Sorry you cannot submit more than {value} posts per day.',
|
||||
'posts.like': 'Like',
|
||||
|
||||
'comments.comments': 'Comments',
|
||||
'comments.count': '{count, plural, =0 {No comments} one {# comment} other {# comments}}',
|
||||
'comments.count_0': 'No comments',
|
||||
'comments.count_1': '1 comment',
|
||||
'comments.count_2': '{count} comments',
|
||||
'comments.new': 'New Comment',
|
||||
'comments.no_comments': 'No comments to display.',
|
||||
'comments.reply': 'Reply',
|
||||
'comments.edit': 'Edit',
|
||||
'comments.delete': 'Delete',
|
||||
'comments.delete_confirm': 'Delete this comment?',
|
||||
'comments.delete_success': 'Comment deleted.',
|
||||
'comments.please_log_in': 'Please log in to comment.',
|
||||
'comments.parentCommentId': 'Parent Comment ID',
|
||||
'comments.topLevelCommentId': 'Top Level Comment ID',
|
||||
'comments.body': 'Body',
|
||||
'comments.rate_limit_error': 'Please wait {value} seconds before commenting again.',
|
||||
|
||||
'categories': 'Categories',
|
||||
'categories.all': 'All Categories',
|
||||
'categories.edit': 'Edit Category',
|
||||
'categories.edit_success': 'Category “{name}” edited.',
|
||||
'categories.new': 'New Category',
|
||||
'categories.new_success': 'Category “{name}” created.',
|
||||
'categories.delete': 'Delete Category',
|
||||
'categories.name': 'Name',
|
||||
'categories.description': 'Description',
|
||||
'categories.order': 'Order',
|
||||
'categories.slug': 'Slug',
|
||||
'categories.image': 'Image',
|
||||
'categories.parentId': 'Parent ID',
|
||||
'categories.subscribe': 'Subscribe to this category\'s posts',
|
||||
'categories.unsubscribe': 'Unsubscribe to this category\'s posts',
|
||||
'categories.subscribed': 'You have subscribed to “{name}” posts.',
|
||||
'categories.unsubscribed': 'You have unsubscribed from “{name}” posts.',
|
||||
'categories.subscribed_categories' : 'Categories subscribed to',
|
||||
'categories.delete_confirm': 'Delete category “{title}”?',
|
||||
'categories.delete_success': 'Category “{name}” deleted.',
|
||||
'categories.invalid': 'Invalid category',
|
||||
|
||||
});
|
13
packages/example-forum/lib/modules/index.js
Normal file
13
packages/example-forum/lib/modules/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import './voting.js';
|
||||
import './fragments.js';
|
||||
import './components.js';
|
||||
import './config.js';
|
||||
import './routes.js';
|
||||
import './headtags.js';
|
||||
import './i18n.js';
|
||||
|
||||
export { Categories } from './categories/index.js';
|
||||
export { Comments } from './comments/index.js';
|
||||
export { Posts } from './posts/index.js';
|
||||
|
||||
import './notifications/index.js';
|
|
@ -0,0 +1,66 @@
|
|||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
const notificationsGroup = {
|
||||
name: "notifications",
|
||||
order: 2
|
||||
};
|
||||
|
||||
// Add notifications options to user profile settings
|
||||
Users.addField([
|
||||
{
|
||||
fieldName: 'notifications_users',
|
||||
fieldSchema: {
|
||||
label: 'New users',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
control: "checkbox",
|
||||
viewableBy: ['guests'],
|
||||
insertableBy: ['admins'],
|
||||
editableBy: ['admins'],
|
||||
group: notificationsGroup,
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'notifications_posts',
|
||||
fieldSchema: {
|
||||
label: 'New posts',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
control: "checkbox",
|
||||
viewableBy: ['guests'],
|
||||
insertableBy: ['members'],
|
||||
editableBy: ['members'],
|
||||
group: notificationsGroup,
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'notifications_comments',
|
||||
fieldSchema: {
|
||||
label: 'Comments on my posts',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
control: "checkbox",
|
||||
viewableBy: ['guests'],
|
||||
insertableBy: ['members'],
|
||||
editableBy: ['members'],
|
||||
group: notificationsGroup,
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'notifications_replies',
|
||||
fieldSchema: {
|
||||
label: 'Replies to my comments',
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
control: "checkbox",
|
||||
viewableBy: ['guests'],
|
||||
insertableBy: ['members'],
|
||||
editableBy: ['members'],
|
||||
group: notificationsGroup,
|
||||
}
|
||||
}
|
||||
]);
|
192
packages/example-forum/lib/modules/notifications/emails.js
Normal file
192
packages/example-forum/lib/modules/notifications/emails.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
|
||||
Emails
|
||||
|
||||
*/
|
||||
|
||||
import VulcanEmail from 'meteor/vulcan:email';
|
||||
|
||||
/*
|
||||
|
||||
Test
|
||||
|
||||
*/
|
||||
|
||||
VulcanEmail.addEmails({
|
||||
|
||||
test: {
|
||||
template: "test",
|
||||
path: "/email/test",
|
||||
data() {
|
||||
return {date: new Date()};
|
||||
},
|
||||
subject() {
|
||||
return "This is a test";
|
||||
},
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
Users
|
||||
|
||||
*/
|
||||
|
||||
VulcanEmail.addEmails({
|
||||
|
||||
newUser: {
|
||||
template: "newUser",
|
||||
path: "/email/new-user/:_id?",
|
||||
subject() {
|
||||
return "A new user has been created";
|
||||
},
|
||||
query: `
|
||||
query UsersSingleQuery($documentId: String){
|
||||
UsersSingle(documentId: $documentId){
|
||||
displayName
|
||||
pageUrl
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
|
||||
accountApproved: {
|
||||
template: "accountApproved",
|
||||
path: "/email/account-approved/:_id?",
|
||||
subject() {
|
||||
return "Your account has been approved.";
|
||||
},
|
||||
query: `
|
||||
query UsersSingleQuery($documentId: String){
|
||||
UsersSingle(documentId: $documentId){
|
||||
displayName
|
||||
}
|
||||
SiteData{
|
||||
title
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
Posts
|
||||
|
||||
*/
|
||||
|
||||
const postsQuery = `
|
||||
query PostsSingleQuery($documentId: String){
|
||||
PostsSingle(documentId: $documentId){
|
||||
title
|
||||
url
|
||||
pageUrl
|
||||
linkUrl
|
||||
htmlBody
|
||||
thumbnailUrl
|
||||
user{
|
||||
pageUrl
|
||||
displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const dummyPost = {title: '[title]', user: {displayName: '[user]'}};
|
||||
|
||||
VulcanEmail.addEmails({
|
||||
|
||||
newPost: {
|
||||
template: "newPost",
|
||||
path: "/email/new-post/:_id?",
|
||||
subject(data) {
|
||||
const post = _.isEmpty(data) ? dummyPost : data.PostsSingle;
|
||||
return post.user.displayName+' has created a new post: '+post.title;
|
||||
},
|
||||
query: postsQuery
|
||||
},
|
||||
|
||||
newPendingPost: {
|
||||
template: "newPendingPost",
|
||||
path: "/email/new-pending-post/:_id?",
|
||||
subject(data) {
|
||||
const post = _.isEmpty(data) ? dummyPost : data.PostsSingle;
|
||||
return post.user.displayName+' has a new post pending approval: '+post.title;
|
||||
},
|
||||
query: postsQuery
|
||||
},
|
||||
|
||||
postApproved: {
|
||||
template: "postApproved",
|
||||
path: "/email/post-approved/:_id?",
|
||||
subject(data) {
|
||||
const post = _.isEmpty(data) ? dummyPost : data.PostsSingle;
|
||||
return 'Your post “'+post.title+'” has been approved';
|
||||
},
|
||||
query: postsQuery
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
Comments
|
||||
|
||||
*/
|
||||
|
||||
const commentsQuery = `
|
||||
query CommentsSingleQuery($documentId: String){
|
||||
CommentsSingle(documentId: $documentId){
|
||||
pageUrl
|
||||
htmlBody
|
||||
post{
|
||||
pageUrl
|
||||
title
|
||||
}
|
||||
user{
|
||||
pageUrl
|
||||
displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const dummyComment = {post: {title: '[title]'}, user: {displayName: '[user]'}};
|
||||
|
||||
VulcanEmail.addEmails({
|
||||
|
||||
newComment: {
|
||||
template: "newComment",
|
||||
path: "/email/new-comment/:_id?",
|
||||
subject(data) {
|
||||
const comment = _.isEmpty(data) ? dummyComment : data.CommentsSingle;
|
||||
return comment.user.displayName+' left a new comment on your post "' + comment.post.title + '"';
|
||||
},
|
||||
query: commentsQuery
|
||||
},
|
||||
|
||||
newReply: {
|
||||
template: "newReply",
|
||||
path: "/email/new-reply/:_id?",
|
||||
subject(data) {
|
||||
const comment = _.isEmpty(data) ? dummyComment : data.CommentsSingle;
|
||||
return comment.user.displayName+' replied to your comment on "'+comment.post.title+'"';
|
||||
},
|
||||
query: commentsQuery
|
||||
},
|
||||
|
||||
newCommentSubscribed: {
|
||||
template: "newComment",
|
||||
path: "/email/new-comment-subscribed/:_id?",
|
||||
subject(data) {
|
||||
const comment = _.isEmpty(data) ? dummyComment : data.CommentsSingle;
|
||||
return comment.user.displayName+' left a new comment on "' + comment.post.title + '"';
|
||||
},
|
||||
query: commentsQuery
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import './custom_fields.js';
|
||||
import './emails.js';
|
25
packages/example-forum/lib/modules/posts/admin.js
Normal file
25
packages/example-forum/lib/modules/posts/admin.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
|
||||
Admin dashboard extension
|
||||
|
||||
*/
|
||||
|
||||
import { extendFragment, addAdminColumn, addStrings } from 'meteor/vulcan:core';
|
||||
import AdminUsersPosts from '../../components/admin/AdminUsersPosts';
|
||||
|
||||
extendFragment('UsersAdmin', `
|
||||
posts(limit: 5){
|
||||
...PostsPage
|
||||
}
|
||||
`);
|
||||
|
||||
addAdminColumn({
|
||||
name: 'posts',
|
||||
order: 50,
|
||||
component: AdminUsersPosts
|
||||
});
|
||||
|
||||
|
||||
addStrings('en', {
|
||||
'admin.users.posts': 'Posts',
|
||||
});
|
|
@ -1,15 +1,18 @@
|
|||
/*
|
||||
|
||||
Posts collection
|
||||
|
||||
*/
|
||||
|
||||
import schema from './schema.js';
|
||||
import mutations from './mutations.js';
|
||||
import resolvers from './resolvers.js';
|
||||
// import views from './views.js';
|
||||
import { createCollection } from 'meteor/vulcan:core';
|
||||
import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core';
|
||||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
/**
|
||||
* @summary The global namespace for Posts.
|
||||
* @namespace Posts
|
||||
*/
|
||||
const Posts = createCollection({
|
||||
export const Posts = createCollection({
|
||||
|
||||
collectionName: 'Posts',
|
||||
|
||||
|
@ -17,9 +20,9 @@ const Posts = createCollection({
|
|||
|
||||
schema,
|
||||
|
||||
resolvers,
|
||||
resolvers: getDefaultResolvers('Posts'),
|
||||
|
||||
mutations,
|
||||
mutations: getDefaultMutations('Posts'),
|
||||
|
||||
});
|
||||
|
||||
|
@ -69,6 +72,4 @@ Posts.checkAccess = (currentUser, post) => {
|
|||
const status = _.findWhere(Posts.statuses, {value: post.status});
|
||||
return Users.canDo(currentUser, `posts.view.${status.label}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default Posts;
|
||||
}
|
|
@ -1,11 +1,17 @@
|
|||
import Users from "meteor/vulcan:users";
|
||||
/*
|
||||
|
||||
Custom fields on Users collection
|
||||
|
||||
*/
|
||||
|
||||
import Users from 'meteor/vulcan:users';
|
||||
|
||||
Users.addField([
|
||||
/**
|
||||
Count of the user's posts
|
||||
*/
|
||||
{
|
||||
fieldName: "postCount",
|
||||
fieldName: 'postCount',
|
||||
fieldSchema: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
|
@ -17,13 +23,12 @@ Users.addField([
|
|||
The user's associated posts (GraphQL only)
|
||||
*/
|
||||
{
|
||||
fieldName: "posts",
|
||||
fieldName: 'posts',
|
||||
fieldSchema: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
viewableBy: ['guests'],
|
||||
resolveAs: {
|
||||
fieldName: 'posts',
|
||||
arguments: 'limit: Int = 5',
|
||||
type: '[Post]',
|
||||
resolver: (user, { limit }, { currentUser, Users, Posts }) => {
|
|
@ -1,11 +1,10 @@
|
|||
import EmbedlyURL from '../components/EmbedlyURL.jsx';
|
||||
import Posts from "meteor/vulcan:posts";
|
||||
import { Posts } from '../posts/index.js';
|
||||
|
||||
Posts.addField([
|
||||
{
|
||||
fieldName: 'url',
|
||||
fieldSchema: {
|
||||
control: EmbedlyURL, // we are just extending the field url, not replacing it
|
||||
control: 'EmbedURL', // we are just extending the field url, not replacing it
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -44,4 +43,4 @@ Posts.addField([
|
|||
viewableBy: ['guests'],
|
||||
}
|
||||
}
|
||||
]);
|
||||
]);
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue