mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 10:01:40 -05:00
Merge branch 'devel' of https://github.com/TelescopeJS/Telescope into devel
This commit is contained in:
commit
bc306ba703
79 changed files with 633 additions and 912 deletions
|
@ -1,38 +1,11 @@
|
||||||
|
|
||||||
# see http://docs.vulcanjs.org/packages
|
# see http://docs.vulcanjs.org/packages
|
||||||
|
|
||||||
############ Core Packages ############
|
vulcan:core
|
||||||
|
|
||||||
vulcan:core # core components and wrappers
|
|
||||||
vulcan:forms # auto-generated forms
|
|
||||||
vulcan:routing # routing and server-side rendering
|
|
||||||
vulcan:users # user management and permissions
|
|
||||||
|
|
||||||
############ Features Packages ############
|
|
||||||
|
|
||||||
# vulcan:email
|
|
||||||
# vulcan:posts
|
|
||||||
# vulcan:comments
|
|
||||||
# vulcan:newsletter
|
|
||||||
# vulcan:notifications
|
|
||||||
# vulcan:getting-started
|
|
||||||
# vulcan:categories
|
|
||||||
# vulcan:voting
|
|
||||||
# vulcan:events
|
|
||||||
# vulcan:embedly
|
|
||||||
# vulcan:api
|
|
||||||
# vulcan:rss
|
|
||||||
# vulcan:subscribe
|
|
||||||
|
|
||||||
############ Theme Packages ############
|
|
||||||
|
|
||||||
# vulcan:base-components # default ui components
|
|
||||||
# vulcan:base-styles # default styling
|
|
||||||
# vulcan:email-templates # default email templates for notifications
|
|
||||||
|
|
||||||
############ Language Packages ############
|
############ Language Packages ############
|
||||||
|
|
||||||
vulcan:i18n-en-us # default language translation
|
vulcan:i18n-en-us
|
||||||
|
|
||||||
############ Accounts Packages ############
|
############ Accounts Packages ############
|
||||||
|
|
||||||
|
@ -43,5 +16,6 @@ accounts-password@1.3.4
|
||||||
############ Your Packages ############
|
############ Your Packages ############
|
||||||
|
|
||||||
example-movies
|
example-movies
|
||||||
# example-movies-full
|
# example-instagram
|
||||||
|
# example-forum
|
||||||
# example-customization
|
# example-customization
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"analytics-node": "^2.1.1",
|
"analytics-node": "^2.1.1",
|
||||||
"apollo-client": "^1.0.0-rc.5",
|
"apollo-client": "^1.0.0-rc.6",
|
||||||
"babel-runtime": "^6.18.0",
|
"babel-runtime": "^6.18.0",
|
||||||
"bcrypt": "^0.8.7",
|
"bcrypt": "^0.8.7",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.15.2",
|
||||||
|
@ -26,12 +26,11 @@
|
||||||
"graphql-anywhere": "^3.0.1",
|
"graphql-anywhere": "^3.0.1",
|
||||||
"graphql-date": "^1.0.2",
|
"graphql-date": "^1.0.2",
|
||||||
"graphql-server-express": "^0.6.0",
|
"graphql-server-express": "^0.6.0",
|
||||||
"graphql-tag": "^1.3.2",
|
"graphql-tag": "^2.0.0",
|
||||||
"graphql-tools": "^0.10.1",
|
"graphql-tools": "^0.10.1",
|
||||||
"graphql-type-json": "^0.1.4",
|
"graphql-type-json": "^0.1.4",
|
||||||
"handlebars": "^4.0.5",
|
"handlebars": "^4.0.5",
|
||||||
"history": "^3.0.0",
|
"history": "^3.0.0",
|
||||||
"hoist-non-react-statics": "^1.2.0",
|
|
||||||
"html-to-text": "^2.1.0",
|
"html-to-text": "^2.1.0",
|
||||||
"immutability-helper": "^2.0.0",
|
"immutability-helper": "^2.0.0",
|
||||||
"intl": "^1.2.4",
|
"intl": "^1.2.4",
|
||||||
|
|
|
@ -5,12 +5,9 @@ Package.describe({
|
||||||
Package.onUse( function(api) {
|
Package.onUse( function(api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'fourseven:scss',
|
'fourseven:scss@3.8.0',
|
||||||
|
|
||||||
'vulcan:core',
|
'example-forum',
|
||||||
'vulcan:base-components',
|
|
||||||
'vulcan:posts',
|
|
||||||
'vulcan:users'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule('server.js', 'server');
|
api.mainModule('server.js', 'server');
|
||||||
|
|
1
packages/example-forum/README.md
Normal file
1
packages/example-forum/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Vulcan forum example package.
|
43
packages/example-forum/package.js
Normal file
43
packages/example-forum/package.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
Package.describe({
|
||||||
|
name: "example-forum",
|
||||||
|
summary: "Telescope forum package",
|
||||||
|
version: '1.3.0',
|
||||||
|
git: "https://github.com/TelescopeJS/Telescope.git"
|
||||||
|
});
|
||||||
|
|
||||||
|
Package.onUse(function (api) {
|
||||||
|
|
||||||
|
api.versionsFrom(['METEOR@1.0']);
|
||||||
|
|
||||||
|
api.use([
|
||||||
|
|
||||||
|
// vulcan core
|
||||||
|
'vulcan:core@1.3.0',
|
||||||
|
|
||||||
|
// vulcan packages
|
||||||
|
'vulcan:posts@1.3.0',
|
||||||
|
'vulcan:comments@1.3.0',
|
||||||
|
'vulcan:voting@1.3.0',
|
||||||
|
'vulcan:accounts@1.3.0',
|
||||||
|
'vulcan:email',
|
||||||
|
'vulcan:forms',
|
||||||
|
'vulcan:newsletter',
|
||||||
|
'vulcan:notifications',
|
||||||
|
'vulcan:getting-started',
|
||||||
|
'vulcan:categories',
|
||||||
|
'vulcan:events',
|
||||||
|
'vulcan:embedly',
|
||||||
|
'vulcan:api',
|
||||||
|
'vulcan:rss',
|
||||||
|
'vulcan:subscribe',
|
||||||
|
|
||||||
|
'vulcan:base-components',
|
||||||
|
'vulcan:base-styles',
|
||||||
|
'vulcan:email-templates',
|
||||||
|
|
||||||
|
]);
|
||||||
|
|
||||||
|
// api.mainModule("lib/server.js", "server");
|
||||||
|
// api.mainModule("lib/client.js", "client");
|
||||||
|
|
||||||
|
});
|
|
@ -1,7 +1,17 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
A component to configure the "edit comment" form.
|
A component to configure the "edit comment" form.
|
||||||
Wrapped with the "withDocument" container.
|
|
||||||
|
Components.SmartForm props:
|
||||||
|
|
||||||
|
- collection: the collection in which to edit a document
|
||||||
|
- documentId: the id of the document to edit
|
||||||
|
- mutationFragment: the GraphQL fragment defining the data returned by the mutation
|
||||||
|
- showRemove: whether to show the "delete document" action in the form
|
||||||
|
- successCallback: what to do after the mutation succeeds
|
||||||
|
|
||||||
|
Note: `closeModal` is available as a prop because this form will be opened
|
||||||
|
in a modal popup.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
An item in the comments list.
|
An item in the comments list.
|
||||||
Wrapped with the "withCurrentUser" container.
|
|
||||||
|
Note: Comments.options.mutations.edit.check is defined in
|
||||||
|
modules/comments/mutations.js and is used both on the server when
|
||||||
|
performing the mutation, and here to check if the form link
|
||||||
|
should be displayed.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
List of comments.
|
List of comments.
|
||||||
Wrapped with the "withList" and "withCurrentUser" containers.
|
Wrapped with the "withList" and "withCurrentUser" containers.
|
||||||
|
|
||||||
|
All props except currentUser are passed by the withList container.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { PropTypes, Component } from 'react';
|
||||||
|
|
|
@ -2,14 +2,19 @@
|
||||||
|
|
||||||
A component to configure the "new comment" form.
|
A component to configure the "new comment" form.
|
||||||
|
|
||||||
|
The "prefilledProps" option lets you prefill specific form fields
|
||||||
|
(in this case "picId"). This works even if the field is not actually
|
||||||
|
displayed in the form, as is the case here
|
||||||
|
(picId's "hidden" property is set to true in the Comments schema)
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { PropTypes, Component } from 'react';
|
||||||
import { Components, registerComponent, withCurrentUser, getFragment } from 'meteor/vulcan:core';
|
import { Components, registerComponent, getFragment } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
import Comments from '../../modules/comments/collection.js';
|
import Comments from '../../modules/comments/collection.js';
|
||||||
|
|
||||||
const CommentsNewForm = ({currentUser, picId}) =>
|
const CommentsNewForm = ({picId}) =>
|
||||||
|
|
||||||
<div className="comments-new-form">
|
<div className="comments-new-form">
|
||||||
|
|
||||||
|
@ -21,4 +26,4 @@ const CommentsNewForm = ({currentUser, picId}) =>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
export default withCurrentUser(CommentsNewForm);
|
export default CommentsNewForm;
|
|
@ -1,9 +1,21 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
The Header component.
|
||||||
|
|
||||||
|
Components.ModalTrigger is a built-in Vulcan component that displays
|
||||||
|
its children in a popup triggered by either a text link, or a cusotm
|
||||||
|
component (if the "component" prop is specified).
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { PropTypes, Component } from 'react';
|
||||||
import { Components, withCurrentUser } from 'meteor/vulcan:core';
|
import { Components, withCurrentUser } from 'meteor/vulcan:core';
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
import PicsNewForm from '../pics/PicsNewForm';
|
import PicsNewForm from '../pics/PicsNewForm';
|
||||||
|
|
||||||
const HeaderLoggedIn = ({currentUser}) =>
|
// navigation bar component when the user is logged in
|
||||||
|
|
||||||
|
const NavLoggedIn = ({currentUser}) =>
|
||||||
|
|
||||||
<div className="header-nav header-logged-in">
|
<div className="header-nav header-logged-in">
|
||||||
|
|
||||||
|
@ -26,7 +38,9 @@ const HeaderLoggedIn = ({currentUser}) =>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
const HeaderLoggedOut = ({currentUser}) =>
|
// navigation bar component when the user is logged out
|
||||||
|
|
||||||
|
const NavLoggedOut = ({currentUser}) =>
|
||||||
|
|
||||||
<div className="header-nav header-logged-out">
|
<div className="header-nav header-logged-out">
|
||||||
|
|
||||||
|
@ -36,6 +50,8 @@ const HeaderLoggedOut = ({currentUser}) =>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
// Header component
|
||||||
|
|
||||||
const Header = ({currentUser}) =>
|
const Header = ({currentUser}) =>
|
||||||
|
|
||||||
<div className="header-wrapper">
|
<div className="header-wrapper">
|
||||||
|
@ -47,8 +63,8 @@ const Header = ({currentUser}) =>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{currentUser ?
|
{currentUser ?
|
||||||
<HeaderLoggedIn currentUser={currentUser}/> :
|
<NavLoggedIn currentUser={currentUser}/> :
|
||||||
<HeaderLoggedOut currentUser={currentUser}/>
|
<NavLoggedOut currentUser={currentUser}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,40 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
The Layout component.
|
||||||
|
|
||||||
|
In other words, the template used to display every page in the app.
|
||||||
|
Specific pages will be displayed in place of the "children" property.
|
||||||
|
|
||||||
|
Note: the Helmet library is used to insert meta tags and link tags in the <head>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { PropTypes, Component } from 'react';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
import Header from './Header.jsx';
|
import Header from './Header.jsx';
|
||||||
|
|
||||||
|
const links = [
|
||||||
|
// note: modal popups won't work with anything above alpha.5.
|
||||||
|
// see https://github.com/twbs/bootstrap/issues/21876#issuecomment-276181539
|
||||||
|
{
|
||||||
|
rel: 'stylesheet',
|
||||||
|
type: 'text/css',
|
||||||
|
href: 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'stylesheet',
|
||||||
|
type: 'text/css',
|
||||||
|
href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const Layout = ({children}) =>
|
const Layout = ({children}) =>
|
||||||
|
|
||||||
<div className="wrapper" id="wrapper">
|
<div className="wrapper" id="wrapper">
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
|
<Helmet title="Vulcanstagram" link={links} />
|
||||||
|
|
||||||
|
<link />
|
||||||
|
|
||||||
<Header/>
|
<Header/>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
A component that shows a detailed view of a single movie.
|
A component that shows a detailed view of a single picture.
|
||||||
Wrapped with the "withDocument" container.
|
Wrapped with the "withDocument" container.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
A component to configure the "edit pic" form.
|
A component to configure the "edit pic" form.
|
||||||
Wrapped with the "withDocument" container.
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
|
|
||||||
const PicsImage = ({imageUrl, onClick}) =>
|
|
||||||
|
|
||||||
<div className="pics-image" onClick={onClick}>
|
|
||||||
|
|
||||||
<img src={imageUrl}/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
export default PicsImage;
|
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
An item in the pics list.
|
An item in the pics list.
|
||||||
Wrapped with the "withCurrentUser" container.
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -9,15 +8,12 @@ import React, { PropTypes, Component } from 'react';
|
||||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
import PicsDetail from './PicsDetails.jsx';
|
import PicsDetail from './PicsDetails.jsx';
|
||||||
import PicsImage from './PicsImage.jsx';
|
|
||||||
|
|
||||||
const PicsItem = ({pic, currentUser}) =>
|
const PicsItem = ({pic, currentUser}) =>
|
||||||
|
|
||||||
<div className="pics-item">
|
<div className="pics-item">
|
||||||
|
|
||||||
{/* document properties */}
|
<Components.ModalTrigger className="pics-details-modal" component={<div className="pics-image"><img src={pic.imageUrl}/></div>}>
|
||||||
|
|
||||||
<Components.ModalTrigger className="pics-details-modal" component={<PicsImage imageUrl={pic.imageUrl} />}>
|
|
||||||
<PicsDetail documentId={pic._id} currentUser={currentUser} />
|
<PicsDetail documentId={pic._id} currentUser={currentUser} />
|
||||||
</Components.ModalTrigger>
|
</Components.ModalTrigger>
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
A component to configure the "new pic" form.
|
A component to configure the "new pic" form.
|
||||||
|
|
||||||
|
We're using Pics.options.mutations.new.check (defined in modules/pics/mutations.js)
|
||||||
|
to check if the user has the proper permissions to actually insert a new picture.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { PropTypes, Component } from 'react';
|
||||||
|
@ -14,13 +17,11 @@ const PicsNewForm = ({currentUser, closeModal}) =>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
{Pics.options.mutations.new.check(currentUser) ?
|
{Pics.options.mutations.new.check(currentUser) ?
|
||||||
<div style={{marginBottom: '20px', paddingBottom: '20px', borderBottom: '1px solid #ccc'}}>
|
<Components.SmartForm
|
||||||
<Components.SmartForm
|
collection={Pics}
|
||||||
collection={Pics}
|
mutationFragment={getFragment('PicsItemFragment')}
|
||||||
mutationFragment={getFragment('PicsItemFragment')}
|
successCallback={closeModal}
|
||||||
successCallback={closeModal}
|
/> :
|
||||||
/>
|
|
||||||
</div> :
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,10 @@ import './permissions.js';
|
||||||
|
|
||||||
const Comments = createCollection({
|
const Comments = createCollection({
|
||||||
|
|
||||||
collectionName: 'comments',
|
collectionName: 'Comments',
|
||||||
|
|
||||||
|
// avoid conflicts with 'comments' collection in vulcan:comments
|
||||||
|
dbCollectionName: 'commentsInstagram',
|
||||||
|
|
||||||
typeName: 'Comment',
|
typeName: 'Comment',
|
||||||
|
|
||||||
|
@ -25,7 +28,16 @@ const Comments = createCollection({
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Comments.addView('picComments', function (terms) {
|
/*
|
||||||
|
|
||||||
|
Set a default results view whenever the Comments collection is queried:
|
||||||
|
|
||||||
|
- Comments are limited to those corresponding to the current picture
|
||||||
|
- They're sorted by their createdAt timestamp in ascending order
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
Comments.addDefaultView(terms => {
|
||||||
return {
|
return {
|
||||||
selector: {picId: terms.picId},
|
selector: {picId: terms.picId},
|
||||||
options: {sort: {createdAt: 1}}
|
options: {sort: {createdAt: 1}}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Declare permissions for the comments collection.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
|
|
||||||
const membersActions = [
|
const membersActions = [
|
||||||
|
|
|
@ -16,13 +16,14 @@ const schema = {
|
||||||
type: Date,
|
type: Date,
|
||||||
viewableBy: ['guests'],
|
viewableBy: ['guests'],
|
||||||
autoValue: (documentOrModifier) => {
|
autoValue: (documentOrModifier) => {
|
||||||
if (documentOrModifier && !documentOrModifier.$set) return new Date() // if this is an insert, set createdAt to current timestamp
|
// if this is an insert, set createdAt to current timestamp
|
||||||
|
if (documentOrModifier && !documentOrModifier.$set) return new Date()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
userId: {
|
userId: {
|
||||||
type: String,
|
type: String,
|
||||||
viewableBy: ['guests'],
|
viewableBy: ['guests'],
|
||||||
resolveAs: 'user: User',
|
resolveAs: 'user: User', // resolve as "user" on the client
|
||||||
},
|
},
|
||||||
|
|
||||||
// custom properties
|
// custom properties
|
||||||
|
@ -40,7 +41,7 @@ const schema = {
|
||||||
type: String,
|
type: String,
|
||||||
viewableBy: ['guests'],
|
viewableBy: ['guests'],
|
||||||
insertableBy: ['members'],
|
insertableBy: ['members'],
|
||||||
hidden: true,
|
hidden: true, // never show this in forms
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,11 +10,10 @@ import resolvers from './resolvers.js';
|
||||||
import './fragments.js';
|
import './fragments.js';
|
||||||
import mutations from './mutations.js';
|
import mutations from './mutations.js';
|
||||||
import './permissions.js';
|
import './permissions.js';
|
||||||
import './parameters.js';
|
|
||||||
|
|
||||||
const Pics = createCollection({
|
const Pics = createCollection({
|
||||||
|
|
||||||
collectionName: 'pics',
|
collectionName: 'Pics',
|
||||||
|
|
||||||
typeName: 'Pic',
|
typeName: 'Pic',
|
||||||
|
|
||||||
|
@ -26,4 +25,18 @@ const Pics = createCollection({
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Set a default results view whenever the Pics collection is queried:
|
||||||
|
|
||||||
|
- Pics are sorted by their createdAt timestamp in descending order
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
Pics.addDefaultView(terms => {
|
||||||
|
return {
|
||||||
|
options: {sort: {createdAt: -1}}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export default Pics;
|
export default Pics;
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { addCallback } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
function sortByCreatedAt (parameters, terms) {
|
|
||||||
return {
|
|
||||||
selector: parameters.selector,
|
|
||||||
options: {...parameters.options, sort: {createdAt: -1}}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
addCallback('pics.parameters', sortByCreatedAt);
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Declare permissions for the comments collection.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
|
|
||||||
const membersActions = [
|
const membersActions = [
|
||||||
|
|
|
@ -4,8 +4,7 @@ A SimpleSchema-compatible JSON schema
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getSetting } from 'meteor/vulcan:core';
|
import FormsUpload from 'meteor/vulcan:forms-upload';
|
||||||
import Upload from 'meteor/vulcan:forms-upload';
|
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
|
|
||||||
|
@ -19,13 +18,14 @@ const schema = {
|
||||||
type: Date,
|
type: Date,
|
||||||
viewableBy: ['guests'],
|
viewableBy: ['guests'],
|
||||||
autoValue: (documentOrModifier) => {
|
autoValue: (documentOrModifier) => {
|
||||||
if (documentOrModifier && !documentOrModifier.$set) return new Date() // if this is an insert, set createdAt to current timestamp
|
// if this is an insert, set createdAt to current timestamp
|
||||||
|
if (documentOrModifier && !documentOrModifier.$set) return new Date()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
userId: {
|
userId: {
|
||||||
type: String,
|
type: String,
|
||||||
viewableBy: ['guests'],
|
viewableBy: ['guests'],
|
||||||
resolveAs: 'user: User',
|
resolveAs: 'user: User', // resolve this field as "user" on the client
|
||||||
},
|
},
|
||||||
|
|
||||||
// custom properties
|
// custom properties
|
||||||
|
@ -37,10 +37,10 @@ const schema = {
|
||||||
viewableBy: ['guests'],
|
viewableBy: ['guests'],
|
||||||
insertableBy: ['members'],
|
insertableBy: ['members'],
|
||||||
editableBy: ['members'],
|
editableBy: ['members'],
|
||||||
control: Upload,
|
control: FormsUpload, // use the FormsUpload form component
|
||||||
form: {
|
form: {
|
||||||
options: {
|
options: {
|
||||||
preset: getSetting('cloudinaryPresets').pics
|
preset: 'vulcanstagram'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -48,7 +48,7 @@ const schema = {
|
||||||
label: 'Body',
|
label: 'Body',
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
control: 'textarea',
|
control: 'textarea', // use a textarea form component
|
||||||
viewableBy: ['guests'],
|
viewableBy: ['guests'],
|
||||||
insertableBy: ['members'],
|
insertableBy: ['members'],
|
||||||
editableBy: ['members']
|
editableBy: ['members']
|
||||||
|
@ -61,7 +61,7 @@ const schema = {
|
||||||
optional: true,
|
optional: true,
|
||||||
viewableBy: ['guests'],
|
viewableBy: ['guests'],
|
||||||
hidden: true,
|
hidden: true,
|
||||||
resolveAs: 'commentsCount: Float'
|
resolveAs: 'commentsCount: Float' // resolve as commentCount on the client
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,16 +5,20 @@ Package.describe({
|
||||||
Package.onUse(function (api) {
|
Package.onUse(function (api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
|
|
||||||
|
// vulcan core
|
||||||
'vulcan:core',
|
'vulcan:core',
|
||||||
|
|
||||||
|
// vulcan packages
|
||||||
'vulcan:forms',
|
'vulcan:forms',
|
||||||
'vulcan:routing',
|
|
||||||
'vulcan:accounts',
|
'vulcan:accounts',
|
||||||
'vulcan:forms-upload',
|
'vulcan:forms-upload',
|
||||||
|
|
||||||
'fourseven:scss',
|
// third-party packages
|
||||||
|
'fourseven:scss@3.8.0',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.addFiles('lib/stylesheets/bootstrap.min.css');
|
|
||||||
api.addFiles('lib/stylesheets/style.scss');
|
api.addFiles('lib/stylesheets/style.scss');
|
||||||
|
|
||||||
api.addAssets([
|
api.addAssets([
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Vulcan demo package.
|
|
|
@ -1 +0,0 @@
|
||||||
import '../modules/index.js';
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
A component that shows a detailed view of a single movie.
|
|
||||||
Wrapped with the "withDocument" container.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
import Movies from '../../modules/movies/collection.js';
|
|
||||||
import { withDocument, registerComponent } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
const MoviesDetails = props => {
|
|
||||||
const movie = props.document;
|
|
||||||
if (props.loading) {
|
|
||||||
return <p>Loading…</p>
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>{movie.name} ({movie.year})</h2>
|
|
||||||
<p>Reviewed by <strong>{movie.user && movie.user.displayName}</strong> on {movie.createdAt}</p>
|
|
||||||
<p>{movie.review}</p>
|
|
||||||
{movie.privateComments ? <p><strong>PRIVATE</strong>: {movie.privateComments}</p>: null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
collection: Movies,
|
|
||||||
queryName: 'moviesSingleQuery',
|
|
||||||
fragmentName: 'MoviesDetailsFragment',
|
|
||||||
};
|
|
||||||
|
|
||||||
registerComponent('MoviesDetails', MoviesDetails, withDocument(options));
|
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
A component to configure the "edit movie" form.
|
|
||||||
Wrapped with the "withDocument" container.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
import { Components, registerComponent, getFragment } from "meteor/vulcan:core";
|
|
||||||
import Movies from '../../modules/movies/collection.js';
|
|
||||||
|
|
||||||
const MoviesEditForm = props =>
|
|
||||||
<Components.SmartForm
|
|
||||||
collection={Movies}
|
|
||||||
documentId={props.documentId}
|
|
||||||
mutationFragment={getFragment('MoviesDetailsFragment')}
|
|
||||||
showRemove={true}
|
|
||||||
successCallback={document => {
|
|
||||||
props.closeModal();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
registerComponent('MoviesEditForm', MoviesEditForm);
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
An item in the movies list.
|
|
||||||
Wrapped with the "withCurrentUser" container.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
import { Button } from 'react-bootstrap';
|
|
||||||
import { Components, registerComponent, ModalTrigger } from 'meteor/vulcan:core';
|
|
||||||
import Movies from '../../modules/movies/collection.js';
|
|
||||||
|
|
||||||
class MoviesItem extends Component {
|
|
||||||
|
|
||||||
renderDetails() {
|
|
||||||
|
|
||||||
const movie = this.props.movie;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{display: 'inline-block', marginRight: '5px'}}>
|
|
||||||
<ModalTrigger label="View Details">
|
|
||||||
<Components.MoviesDetails documentId={movie._id}/>
|
|
||||||
</ModalTrigger>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
renderEdit() {
|
|
||||||
|
|
||||||
const movie = this.props.movie;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{display: 'inline-block', marginRight: '5px'}}>
|
|
||||||
<ModalTrigger label="Edit Movie" >
|
|
||||||
<Components.MoviesEditForm currentUser={this.props.currentUser} documentId={movie._id} />
|
|
||||||
</ModalTrigger>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
|
|
||||||
const movie = this.props.movie;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={movie.name} style={{paddingBottom: "15px",marginBottom: "15px", borderBottom: "1px solid #ccc"}}>
|
|
||||||
<h2>{movie.name} ({movie.year})</h2>
|
|
||||||
<p>By <strong>{movie.user && movie.user.displayName}</strong></p>
|
|
||||||
<div className="item-actions">
|
|
||||||
{this.renderDetails()}
|
|
||||||
|
|
|
||||||
{Movies.options.mutations.edit.check(this.props.currentUser, movie) ? this.renderEdit() : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
registerComponent('MoviesItem', MoviesItem);
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
List of movies.
|
|
||||||
Wrapped with the "withList" and "withCurrentUser" containers.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
import { Button } from 'react-bootstrap';
|
|
||||||
import Movies from '../../modules/movies/collection.js';
|
|
||||||
import { Components, registerComponent, ModalTrigger, withList, withCurrentUser } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
const LoadMore = props => <a href="#" className="load-more button button--primary" onClick={e => {e.preventDefault(); props.loadMore();}}>Load More ({props.count}/{props.totalCount})</a>
|
|
||||||
|
|
||||||
class MoviesList extends Component {
|
|
||||||
|
|
||||||
renderNew() {
|
|
||||||
|
|
||||||
const component = (
|
|
||||||
<div className="add-movie">
|
|
||||||
<ModalTrigger
|
|
||||||
title="Add Movie"
|
|
||||||
component={<Button bsStyle="primary">Add Movie</Button>}
|
|
||||||
>
|
|
||||||
<Components.MoviesNewForm />
|
|
||||||
</ModalTrigger>
|
|
||||||
<hr/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return !!this.props.currentUser ? component : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
|
|
||||||
const canCreateNewMovie = Movies.options.mutations.new.check(this.props.currentUser);
|
|
||||||
|
|
||||||
if (this.props.loading) {
|
|
||||||
return <Components.Loading />
|
|
||||||
} else {
|
|
||||||
const hasMore = this.props.totalCount > this.props.results.length;
|
|
||||||
return (
|
|
||||||
<div className="movies">
|
|
||||||
{canCreateNewMovie ? this.renderNew() : null}
|
|
||||||
{this.props.results.map(movie => <Components.MoviesItem key={movie._id} movie={movie} currentUser={this.props.currentUser} />)}
|
|
||||||
{hasMore ? <LoadMore {...this.props}/> : <p>No more movies</p>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
collection: Movies,
|
|
||||||
queryName: 'moviesListQuery',
|
|
||||||
fragmentName: 'MoviesItemFragment',
|
|
||||||
limit: 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
registerComponent('MoviesList', MoviesList, withList(options), withCurrentUser);
|
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
A component to configure the "new movie" form.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
import Movies from '../../modules/movies/collection.js';
|
|
||||||
import { Components, registerComponent, withMessages, getFragment } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
const MoviesNewForm = props =>
|
|
||||||
<Components.SmartForm
|
|
||||||
collection={Movies}
|
|
||||||
mutationFragment={getFragment('MoviesItemFragment')}
|
|
||||||
successCallback={document => {
|
|
||||||
props.closeModal();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
registerComponent('MoviesNewForm', MoviesNewForm, withMessages);
|
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Wrapper for the Movies components
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
const MoviesWrapper = () =>
|
|
||||||
<div className="wrapper framework-demo" style={{maxWidth: '500px', margin: 'auto'}}>
|
|
||||||
|
|
||||||
<div className="header" style={{padding: '20px 0', marginBottom: '20px', borderBottom: '1px solid #ccc'}}>
|
|
||||||
<Components.AccountsLoginForm />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="main">
|
|
||||||
<Components.MoviesList />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
registerComponent('MoviesWrapper', MoviesWrapper);
|
|
|
@ -1,6 +0,0 @@
|
||||||
import '../components/movies/MoviesDetails.jsx';
|
|
||||||
import '../components/movies/MoviesEditForm.jsx';
|
|
||||||
import '../components/movies/MoviesItem.jsx';
|
|
||||||
import '../components/movies/MoviesList.jsx';
|
|
||||||
import '../components/movies/MoviesNewForm.jsx';
|
|
||||||
import '../components/movies/MoviesWrapper.jsx';
|
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Add strings for internationalization.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { addStrings } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
addStrings('en', {
|
|
||||||
'movies.delete': "Delete Movie",
|
|
||||||
'movies.delete_confirm': "Delete Movie?"
|
|
||||||
});
|
|
|
@ -1,15 +0,0 @@
|
||||||
// The main Movies collection
|
|
||||||
import MoviesImport from './movies/collection.js';
|
|
||||||
|
|
||||||
// Text strings used in the UI
|
|
||||||
import './i18n.js';
|
|
||||||
|
|
||||||
|
|
||||||
// React components
|
|
||||||
import './components.js';
|
|
||||||
|
|
||||||
// Routes
|
|
||||||
import './routes.js';
|
|
||||||
|
|
||||||
// Add Movies collection to global Meteor namespace (optional)
|
|
||||||
Movies = MoviesImport;
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
The main Movies collection definition file.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { createCollection } from 'meteor/vulcan:core';
|
|
||||||
import schema from './schema.js';
|
|
||||||
import resolvers from './resolvers.js';
|
|
||||||
import mutations from './mutations.js';
|
|
||||||
|
|
||||||
// Groups & user permissions
|
|
||||||
import './permissions.js';
|
|
||||||
|
|
||||||
// GraphQL fragments used to query for data
|
|
||||||
import './fragments.js';
|
|
||||||
|
|
||||||
// Sorting & filtering parameters
|
|
||||||
import './parameters.js';
|
|
||||||
|
|
||||||
const Movies = createCollection({
|
|
||||||
|
|
||||||
collectionName: 'movies',
|
|
||||||
|
|
||||||
typeName: 'Movie',
|
|
||||||
|
|
||||||
schema,
|
|
||||||
|
|
||||||
resolvers,
|
|
||||||
|
|
||||||
mutations,
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Movies;
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Register the two GraphQL fragments used to query for data
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { registerFragment } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
registerFragment(`
|
|
||||||
fragment MoviesItemFragment on Movie {
|
|
||||||
_id
|
|
||||||
name
|
|
||||||
year
|
|
||||||
createdAt
|
|
||||||
userId
|
|
||||||
user {
|
|
||||||
displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
registerFragment(`
|
|
||||||
fragment MoviesDetailsFragment on Movie {
|
|
||||||
_id
|
|
||||||
name
|
|
||||||
createdAt
|
|
||||||
year
|
|
||||||
review
|
|
||||||
privateComments
|
|
||||||
userId
|
|
||||||
user {
|
|
||||||
displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
|
@ -1,104 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Define the three default mutations:
|
|
||||||
|
|
||||||
- new (e.g.: moviesNew(document: moviesInput) : Movie )
|
|
||||||
- edit (e.g.: moviesEdit(documentId: String, set: moviesInput, unset: moviesUnset) : Movie )
|
|
||||||
- remove (e.g.: moviesRemove(documentId: String) : Movie )
|
|
||||||
|
|
||||||
Each mutation has:
|
|
||||||
|
|
||||||
- A name
|
|
||||||
- A check function that takes the current user and (optionally) the document affected
|
|
||||||
- The actual mutation
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { newMutation, editMutation, removeMutation, Utils } from 'meteor/vulcan:core';
|
|
||||||
import Users from 'meteor/vulcan:users';
|
|
||||||
|
|
||||||
const performCheck = (mutation, user, document) => {
|
|
||||||
if (!mutation.check(user, document)) throw new Error(Utils.encodeIntlError({id: `app.mutation_not_allowed`, value: `"${mutation.name}" on _id "${document._id}"`}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
|
|
||||||
new: {
|
|
||||||
|
|
||||||
name: 'moviesNew',
|
|
||||||
|
|
||||||
check(user) {
|
|
||||||
if (!user) return false;
|
|
||||||
return Users.canDo(user, 'movies.new');
|
|
||||||
},
|
|
||||||
|
|
||||||
mutation(root, {document}, context) {
|
|
||||||
|
|
||||||
performCheck(this, context.currentUser, document);
|
|
||||||
|
|
||||||
return newMutation({
|
|
||||||
collection: context.Movies,
|
|
||||||
document: document,
|
|
||||||
currentUser: context.currentUser,
|
|
||||||
validate: true,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
edit: {
|
|
||||||
|
|
||||||
name: 'moviesEdit',
|
|
||||||
|
|
||||||
check(user, document) {
|
|
||||||
if (!user || !document) return false;
|
|
||||||
return Users.owns(user, document) ? Users.canDo(user, 'movies.edit.own') : Users.canDo(user, `movies.edit.all`);
|
|
||||||
},
|
|
||||||
|
|
||||||
mutation(root, {documentId, set, unset}, context) {
|
|
||||||
|
|
||||||
const document = context.Movies.findOne(documentId);
|
|
||||||
performCheck(this, context.currentUser, document);
|
|
||||||
|
|
||||||
return editMutation({
|
|
||||||
collection: context.Movies,
|
|
||||||
documentId: documentId,
|
|
||||||
set: set,
|
|
||||||
unset: unset,
|
|
||||||
currentUser: context.currentUser,
|
|
||||||
validate: true,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
remove: {
|
|
||||||
|
|
||||||
name: 'moviesRemove',
|
|
||||||
|
|
||||||
check(user, document) {
|
|
||||||
if (!user || !document) return false;
|
|
||||||
return Users.owns(user, document) ? Users.canDo(user, 'movies.remove.own') : Users.canDo(user, `movies.remove.all`);
|
|
||||||
},
|
|
||||||
|
|
||||||
mutation(root, {documentId}, context) {
|
|
||||||
|
|
||||||
const document = context.Movies.findOne(documentId);
|
|
||||||
performCheck(this, context.currentUser, document);
|
|
||||||
|
|
||||||
return removeMutation({
|
|
||||||
collection: context.Movies,
|
|
||||||
documentId: documentId,
|
|
||||||
currentUser: context.currentUser,
|
|
||||||
validate: true,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export default mutations;
|
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Add a new parameter callback that sorts movies by 'createdAt' property.
|
|
||||||
|
|
||||||
We use a callback instead of defining the sort in the resolver so that
|
|
||||||
the same sort can be used on the client, too.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { addCallback } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
function sortByCreatedAt (parameters, terms) {
|
|
||||||
return {
|
|
||||||
selector: parameters.selector,
|
|
||||||
options: {...parameters.options, sort: {createdAt: -1}}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
addCallback("movies.parameters", sortByCreatedAt);
|
|
|
@ -1,14 +0,0 @@
|
||||||
import Users from 'meteor/vulcan:users';
|
|
||||||
|
|
||||||
const membersActions = [
|
|
||||||
'movies.new',
|
|
||||||
'movies.edit.own',
|
|
||||||
'movies.remove.own',
|
|
||||||
];
|
|
||||||
Users.groups.members.can(membersActions);
|
|
||||||
|
|
||||||
const adminActions = [
|
|
||||||
'movies.edit.all',
|
|
||||||
'movies.remove.all'
|
|
||||||
];
|
|
||||||
Users.groups.admins.can(adminActions);
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Three resolvers are defined:
|
|
||||||
|
|
||||||
- list (e.g.: moviesList(terms: JSON, offset: Int, limit: Int) )
|
|
||||||
- single (e.g.: moviesSingle(_id: String) )
|
|
||||||
- listTotal (e.g.: moviesTotal )
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { GraphQLSchema } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
// add the "user" resolver for the Movie type separately
|
|
||||||
const movieResolver = {
|
|
||||||
Movie: {
|
|
||||||
user(movie, args, context) {
|
|
||||||
return context.Users.findOne({ _id: movie.userId }, { fields: context.getViewableFields(context.currentUser, context.Users) });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
GraphQLSchema.addResolvers(movieResolver);
|
|
||||||
|
|
||||||
// basic list, single, and total query resolvers
|
|
||||||
const resolvers = {
|
|
||||||
|
|
||||||
list: {
|
|
||||||
|
|
||||||
name: 'moviesList',
|
|
||||||
|
|
||||||
resolver(root, {terms = {}}, context, info) {
|
|
||||||
let {selector, options} = context.Movies.getParameters(terms);
|
|
||||||
options.limit = (terms.limit < 1 || terms.limit > 100) ? 100 : terms.limit;
|
|
||||||
options.fields = context.getViewableFields(context.currentUser, context.Movies);
|
|
||||||
return context.Movies.find(selector, options).fetch();
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
single: {
|
|
||||||
|
|
||||||
name: 'moviesSingle',
|
|
||||||
|
|
||||||
resolver(root, {documentId}, context) {
|
|
||||||
const document = context.Movies.findOne({_id: documentId});
|
|
||||||
return _.pick(document, _.keys(context.getViewableFields(context.currentUser, context.Movies, document)));
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
total: {
|
|
||||||
|
|
||||||
name: 'moviesTotal',
|
|
||||||
|
|
||||||
resolver(root, {terms = {}}, context) {
|
|
||||||
let {selector, options} = context.Movies.getParameters(terms);
|
|
||||||
return context.Movies.find(selector, options).count();
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default resolvers;
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
A SimpleSchema-compatible JSON schema
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Users from 'meteor/vulcan:users';
|
|
||||||
|
|
||||||
// define schema
|
|
||||||
const schema = {
|
|
||||||
_id: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
viewableBy: ['guests'],
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
label: 'Name',
|
|
||||||
type: String,
|
|
||||||
viewableBy: ['guests'],
|
|
||||||
insertableBy: ['members'],
|
|
||||||
editableBy: ['members'],
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Date,
|
|
||||||
viewableBy: ['guests'],
|
|
||||||
autoValue: (documentOrModifier) => {
|
|
||||||
if (documentOrModifier && !documentOrModifier.$set) return new Date() // if this is an insert, set createdAt to current timestamp
|
|
||||||
}
|
|
||||||
},
|
|
||||||
year: {
|
|
||||||
label: 'Year',
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
viewableBy: ['guests'],
|
|
||||||
insertableBy: ['members'],
|
|
||||||
editableBy: ['members'],
|
|
||||||
},
|
|
||||||
review: {
|
|
||||||
label: 'Review',
|
|
||||||
type: String,
|
|
||||||
control: 'textarea',
|
|
||||||
viewableBy: ['guests'],
|
|
||||||
insertableBy: ['members'],
|
|
||||||
editableBy: ['members']
|
|
||||||
},
|
|
||||||
privateComments: {
|
|
||||||
label: 'Private Comments',
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
control: 'textarea',
|
|
||||||
viewableBy: Users.owns,
|
|
||||||
insertableBy: ['members'],
|
|
||||||
editableBy: ['members']
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
viewableBy: ['guests'],
|
|
||||||
resolveAs: 'user: User',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default schema;
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { addRoute, getComponent } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
// add new "/movies" route that loads the MoviesWrapper component
|
|
||||||
addRoute({ name: 'movies', path: '/', componentName: 'MoviesWrapper' });
|
|
|
@ -1,2 +0,0 @@
|
||||||
import '../modules/index.js';
|
|
||||||
import './seed.js';
|
|
|
@ -1,80 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Seed the database with some dummy content.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Movies from '../modules/movies/collection.js';
|
|
||||||
import Users from 'meteor/vulcan:users';
|
|
||||||
import { newMutation } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
const seedData = [
|
|
||||||
{
|
|
||||||
name: 'Star Wars',
|
|
||||||
year: '1973',
|
|
||||||
review: `A classic.`,
|
|
||||||
privateComments: `Actually, I don't really like Star Wars…`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Die Hard',
|
|
||||||
year: '1987',
|
|
||||||
review: `A must-see if you like action movies.`,
|
|
||||||
privateComments: `I love Bruce Willis so much!`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Terminator',
|
|
||||||
year: '1983',
|
|
||||||
review: `Once again, Schwarzenegger shows why he's the boss.`,
|
|
||||||
privateComments: `Terminator is my favorite movie ever. `
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Jaws',
|
|
||||||
year: '1971',
|
|
||||||
review: 'The original blockbuster.',
|
|
||||||
privateComments: `I'm scared of sharks…`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Die Hard II',
|
|
||||||
year: '1991',
|
|
||||||
review: `Another classic.`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Rush Hour',
|
|
||||||
year: '1993',
|
|
||||||
review: `Jackie Chan at his best.`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Citizen Kane',
|
|
||||||
year: '1943',
|
|
||||||
review: `A disappointing lack of action sequences.`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Commando',
|
|
||||||
year: '1983',
|
|
||||||
review: 'A good contender for highest kill count ever.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
Meteor.startup(function () {
|
|
||||||
if (Users.find().fetch().length === 0) {
|
|
||||||
Accounts.createUser({
|
|
||||||
username: 'DemoUser',
|
|
||||||
email: 'dummyuser@telescopeapp.org',
|
|
||||||
profile: {
|
|
||||||
isDummy: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const currentUser = Users.findOne();
|
|
||||||
if (Movies.find().fetch().length === 0) {
|
|
||||||
seedData.forEach(document => {
|
|
||||||
newMutation({
|
|
||||||
action: 'movies.new',
|
|
||||||
collection: Movies,
|
|
||||||
document: document,
|
|
||||||
currentUser: currentUser,
|
|
||||||
validate: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
File diff suppressed because one or more lines are too long
|
@ -1,23 +0,0 @@
|
||||||
Package.describe({
|
|
||||||
name: 'example-movies-full',
|
|
||||||
});
|
|
||||||
|
|
||||||
Package.onUse(function (api) {
|
|
||||||
|
|
||||||
api.use([
|
|
||||||
'vulcan:core',
|
|
||||||
'vulcan:forms',
|
|
||||||
'vulcan:routing',
|
|
||||||
'vulcan:accounts',
|
|
||||||
]);
|
|
||||||
|
|
||||||
api.addFiles('lib/stylesheets/bootstrap.min.css', 'client');
|
|
||||||
|
|
||||||
api.mainModule('lib/server/main.js', 'server');
|
|
||||||
api.mainModule('lib/client/main.js', 'client');
|
|
||||||
|
|
||||||
api.export([
|
|
||||||
'Movies',
|
|
||||||
], ['client', 'server']);
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
A component to configure the "edit movie" form.
|
A component to configure the "edit movie" form.
|
||||||
Wrapped with the "withDocument" container.
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,14 @@ Package.describe({
|
||||||
Package.onUse(function (api) {
|
Package.onUse(function (api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
|
|
||||||
|
// vulcan core
|
||||||
'vulcan:core',
|
'vulcan:core',
|
||||||
|
|
||||||
|
// vulcan packages
|
||||||
'vulcan:forms',
|
'vulcan:forms',
|
||||||
'vulcan:routing',
|
|
||||||
'vulcan:accounts',
|
'vulcan:accounts',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.addFiles('lib/stylesheets/bootstrap.min.css');
|
api.addFiles('lib/stylesheets/bootstrap.min.css');
|
||||||
|
|
|
@ -8,6 +8,9 @@ Package.describe({
|
||||||
|
|
||||||
Package.onUse(function(api) {
|
Package.onUse(function(api) {
|
||||||
api.versionsFrom('1.3');
|
api.versionsFrom('1.3');
|
||||||
|
|
||||||
|
api.use('vulcan:core@1.3.0');
|
||||||
|
|
||||||
api.use('ecmascript');
|
api.use('ecmascript');
|
||||||
api.use('tracker');
|
api.use('tracker');
|
||||||
api.use('underscore');
|
api.use('underscore');
|
||||||
|
|
|
@ -44,7 +44,9 @@ class HeadTags extends Component {
|
||||||
// add <link /> markup specific to the page rendered
|
// add <link /> markup specific to the page rendered
|
||||||
const link = Headtags.link.concat([
|
const link = Headtags.link.concat([
|
||||||
{ rel: "canonical", href: Utils.getSiteUrl() },
|
{ rel: "canonical", href: Utils.getSiteUrl() },
|
||||||
{ rel: "shortcut icon", href: getSetting("faviconUrl", "/img/favicon.ico") }
|
{ rel: "shortcut icon", href: getSetting("faviconUrl", "/img/favicon.ico") },
|
||||||
|
{ rel: 'stylesheet', type: 'text/css', href: 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css' },
|
||||||
|
{ rel: 'stylesheet', type: 'text/css', href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,11 +4,10 @@ import React, { PropTypes, Component } from 'react';
|
||||||
import { intlShape } from 'react-intl';
|
import { intlShape } from 'react-intl';
|
||||||
import { withRouter } from 'react-router'
|
import { withRouter } from 'react-router'
|
||||||
|
|
||||||
const PostsNewForm = (props, context) => {
|
const PostsNewForm = (props, context) =>
|
||||||
return (
|
<Components.ShowIf
|
||||||
<Components.ShowIf
|
|
||||||
check={Posts.options.mutations.new.check}
|
check={Posts.options.mutations.new.check}
|
||||||
failureComponent={<Components.UsersAccountForm />}
|
failureComponent={<Components.AccountsLoginForm />}
|
||||||
>
|
>
|
||||||
<div className="posts-new-form">
|
<div className="posts-new-form">
|
||||||
<Components.SmartForm
|
<Components.SmartForm
|
||||||
|
@ -22,8 +21,6 @@ const PostsNewForm = (props, context) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Components.ShowIf>
|
</Components.ShowIf>
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PostsNewForm.propTypes = {
|
PostsNewForm.propTypes = {
|
||||||
closeModal: React.PropTypes.func,
|
closeModal: React.PropTypes.func,
|
||||||
|
|
|
@ -16,9 +16,6 @@ Package.onUse(function (api) {
|
||||||
'vulcan:comments@1.3.0',
|
'vulcan:comments@1.3.0',
|
||||||
'vulcan:voting@1.3.0',
|
'vulcan:voting@1.3.0',
|
||||||
'vulcan:accounts@1.3.0',
|
'vulcan:accounts@1.3.0',
|
||||||
|
|
||||||
// third-party packages
|
|
||||||
'fortawesome:fontawesome@4.5.0',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule("lib/server.js", "server");
|
api.mainModule("lib/server.js", "server");
|
||||||
|
|
|
@ -11,11 +11,11 @@ Package.onUse(function (api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.3.0',
|
'vulcan:core@1.3.0',
|
||||||
'fourseven:scss',
|
'fourseven:scss@3.8.0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.addFiles([
|
api.addFiles([
|
||||||
'lib/stylesheets/bootstrap.css',
|
// 'lib/stylesheets/bootstrap.css',
|
||||||
'lib/stylesheets/main.scss'
|
'lib/stylesheets/main.scss'
|
||||||
], ['client']);
|
], ['client']);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ export {
|
||||||
// fragments
|
// fragments
|
||||||
Fragments, registerFragment, getFragment, getFragmentName, extendFragment,
|
Fragments, registerFragment, getFragment, getFragmentName, extendFragment,
|
||||||
// graphql
|
// graphql
|
||||||
GraphQLSchema, addGraphQLSchema, addGraphQLQuery, addGraphQLMutation, addGraphQLResolvers, addToGraphQLContext,
|
GraphQLSchema, addGraphQLSchema, addGraphQLQuery, addGraphQLMutation, addGraphQLResolvers, removeGraphQLResolver, addToGraphQLContext,
|
||||||
// headtags
|
// headtags
|
||||||
Headtags,
|
Headtags,
|
||||||
// inject data
|
// inject data
|
||||||
|
|
|
@ -12,6 +12,7 @@ Package.onUse(function(api) {
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:lib@1.3.0',
|
'vulcan:lib@1.3.0',
|
||||||
'vulcan:users@1.3.0',
|
'vulcan:users@1.3.0',
|
||||||
|
'vulcan:routing@1.3.0'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.imply([
|
api.imply([
|
||||||
|
|
|
@ -11,7 +11,7 @@ Package.onUse(function (api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
|
|
||||||
'fourseven:scss',
|
'fourseven:scss@3.8.0',
|
||||||
|
|
||||||
// Vulcan packages
|
// Vulcan packages
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ Package.onUse(function (api) {
|
||||||
api.versionsFrom(['METEOR@1.0']);
|
api.versionsFrom(['METEOR@1.0']);
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:lib@1.3.0'
|
'vulcan:core@1.3.0'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule("lib/server.js", "server");
|
api.mainModule("lib/server.js", "server");
|
||||||
|
|
|
@ -12,7 +12,7 @@ Package.onUse( function(api) {
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.3.0',
|
'vulcan:core@1.3.0',
|
||||||
'vulcan:posts@1.3.0',
|
'vulcan:posts@1.3.0',
|
||||||
'fourseven:scss'
|
'fourseven:scss@3.8.0'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.addFiles([
|
api.addFiles([
|
||||||
|
|
153
packages/vulcan-forms-upload/README.md
Executable file
153
packages/vulcan-forms-upload/README.md
Executable file
|
@ -0,0 +1,153 @@
|
||||||
|
# nova-upload
|
||||||
|
🏖🔭 Vulcan package extending `vulcan:forms` to upload images to Cloudinary from a drop zone.
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Want to add this to your Vulcan instance? Read below:
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
### 1. Meteor package
|
||||||
|
I would recommend that you clone this repo in your vulcan's `/packages` folder.
|
||||||
|
|
||||||
|
Then, open the `.meteor/packages` file and add at the end of the **Optional packages** section:
|
||||||
|
`xavcz:nova-forms-upload`
|
||||||
|
|
||||||
|
> **Note:** This is the version for Nova 1.0.0, running with GraphQL. *If you are looking for a version compatible with Nova "classic", you'll need to change the package's branch, like below. Then, refer to [the README for `nova-forms-upload` on Nova Classic](https://github.com/xavcz/nova-forms-upload/blob/nova-classic/README.md#installation)*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# only for Nova classic users (v0.27.5)
|
||||||
|
cd nova-forms-upload
|
||||||
|
git checkout nova-classic
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. NPM dependency
|
||||||
|
This package depends on the awesome `react-dropzone` ([repo](https://github.com/okonet/react-dropzone)), you need to install the dependency:
|
||||||
|
```
|
||||||
|
npm install react-dropzone isomorphic-fetch
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Cloudinary account
|
||||||
|
Create a [Cloudinary account](https://cloudinary.com) if you don't have one.
|
||||||
|
|
||||||
|
The upload to Cloudinary relies on **unsigned upload**:
|
||||||
|
|
||||||
|
> Unsigned upload is an option for performing upload directly from a browser or mobile application with no authentication signature, and without going through your servers at all. However, for security reasons, not all upload parameters can be specified directly when performing unsigned upload calls.
|
||||||
|
|
||||||
|
Unsigned upload options are controlled by [an upload preset](http://cloudinary.com/documentation/upload_images#upload_presets), so in order to use this feature you first need to enable unsigned uploading for your Cloudinary account from the [Upload Settings](https://cloudinary.com/console/settings/upload) page.
|
||||||
|
|
||||||
|
When creating your **preset**, you can define image transformations. I recommend to set something like 200px width & height, fill mode and auto quality. Once created, you will get a preset id.
|
||||||
|
|
||||||
|
It may look like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 4. Nova Settings
|
||||||
|
Edit your `settings.json` and add inside the `public: { ... }` block the following entries with your own credentials:
|
||||||
|
|
||||||
|
```json
|
||||||
|
public: {
|
||||||
|
|
||||||
|
|
||||||
|
"cloudinaryCloudName": "YOUR_APP_NAME",
|
||||||
|
"cloudinaryPresets": {
|
||||||
|
"avatar": "YOUR_PRESET_ID",
|
||||||
|
"posts": "THE_SAME_OR_ANOTHER_PRESET_ID"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Picture upload in Nova is now enabled! Easy-peasy, right? 👯
|
||||||
|
|
||||||
|
### 5. Your custom package & custom fields
|
||||||
|
|
||||||
|
Make your custom package depends on this package: open `package.js` in your custom package and add `xavcz:nova-forms-upload` as a dependency, near by the other `nova:xxx` packages.
|
||||||
|
|
||||||
|
You can now use the `Upload` component as a classic form extension with [custom fields](https://www.youtube.com/watch?v=1yTT48xaSy8) like `nova:forms-tags` or `nova:embedly`.
|
||||||
|
|
||||||
|
**⚠️ Note:** Don't forget to update your query fragments wherever needed after defining your custom fields, else they will never be available!
|
||||||
|
|
||||||
|
## Image for posts
|
||||||
|
Let's say you want to enhance your posts with a custom image. In your custom package, your new custom field could look like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ... your imports
|
||||||
|
import { getComponent, getSetting } from 'meteor/nova:lib';
|
||||||
|
import Posts from 'meteor/nova:posts';
|
||||||
|
|
||||||
|
// extends Posts schema with a new field: 'image' 🏖
|
||||||
|
Posts.addField({
|
||||||
|
fieldName: 'image',
|
||||||
|
fieldSchema: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
control: getComponent('Upload'),
|
||||||
|
insertableBy: ['members'],
|
||||||
|
editableBy: ['members'],
|
||||||
|
viewableBy: ['guests'],
|
||||||
|
form: {
|
||||||
|
options: {
|
||||||
|
preset: getSetting('cloudinaryPresets').posts // this setting refers to the transformation you want to apply to the image
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Avatar for users
|
||||||
|
Let's say you want to enable your users to upload their own avatar. In your custom package, your new custom field could look like this:
|
||||||
|
```js
|
||||||
|
// ... your imports
|
||||||
|
import { getComponent, getSetting } from 'meteor/nova:lib';
|
||||||
|
import Users from 'meteor/nova:users';
|
||||||
|
|
||||||
|
// extends Users schema with a new field: 'avatar' 👁
|
||||||
|
Users.addField({
|
||||||
|
fieldName: 'avatar',
|
||||||
|
fieldSchema: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
control: getComponent('Upload'),
|
||||||
|
insertableBy: ['members'],
|
||||||
|
editableBy: ['members'],
|
||||||
|
viewableBy: ['guests'],
|
||||||
|
preload: true, // ⚠️ will preload the field for the current user!
|
||||||
|
form: {
|
||||||
|
options: {
|
||||||
|
preset: getSetting('cloudinaryPresets').avatar // this setting refers to the transformation you want to apply to the image
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding the opportunity to upload an avatar comes with a trade-off: you also need to extend the behavior of the `Users.avatar` methods. You can do this by adding this snippet, in `custom_fields.js` for instance:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const originalAvatarConstructor = Users.avatar;
|
||||||
|
|
||||||
|
// extends the Users.avatar function
|
||||||
|
Users.avatar = {
|
||||||
|
...originalAvatarConstructor,
|
||||||
|
getUrl(user) {
|
||||||
|
url = originalAvatarConstructor.getUrl(user);
|
||||||
|
|
||||||
|
return !!user && user.avatar ? user.avatar : url;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, you also need to update the query fragments related to `User` when you want the custom avatar to show up :)
|
||||||
|
|
||||||
|
## S3? Google Cloud?
|
||||||
|
Feel free to contribute to add new features and flexibility to this package :)
|
||||||
|
|
||||||
|
You are welcome to come chat about it [on the Nova Slack chatroom](http://slack.telescopeapp.org)
|
||||||
|
|
||||||
|
## What about `nova:cloudinary` ?
|
||||||
|
This package and `nova:cloudinary` share a settings in common: `cloudinaryCloudName`. They are fully compatible.
|
||||||
|
|
||||||
|
Happy hacking! 🚀
|
123
packages/vulcan-forms-upload/lib/Upload.jsx
Executable file
123
packages/vulcan-forms-upload/lib/Upload.jsx
Executable file
|
@ -0,0 +1,123 @@
|
||||||
|
import { Components, getSetting, registerComponent } from 'meteor/vulcan:lib';
|
||||||
|
import React, { PropTypes, Component } from 'react';
|
||||||
|
import Dropzone from 'react-dropzone';
|
||||||
|
import 'isomorphic-fetch'; // patch for browser which don't have fetch implemented
|
||||||
|
|
||||||
|
class Upload extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onDrop = this.onDrop.bind(this);
|
||||||
|
this.clearImage = this.clearImage.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
preview: '',
|
||||||
|
uploading: false,
|
||||||
|
value: props.value || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.context.addToAutofilledValues({[this.props.name]: this.props.value || ''});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDrop(files) {
|
||||||
|
console.log(this)
|
||||||
|
|
||||||
|
// set the component in upload mode with the preview
|
||||||
|
this.setState({
|
||||||
|
preview: files[0].preview,
|
||||||
|
uploading: true,
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// request url to cloudinary
|
||||||
|
const cloudinaryUrl = `https://api.cloudinary.com/v1_1/${getSetting("cloudinaryCloudName")}/upload`;
|
||||||
|
|
||||||
|
// request body
|
||||||
|
const body = new FormData();
|
||||||
|
body.append("file", files[0]);
|
||||||
|
body.append("upload_preset", this.props.options.preset);
|
||||||
|
|
||||||
|
// post request to cloudinary
|
||||||
|
fetch(cloudinaryUrl, {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
.then(res => res.json()) // json-ify the readable strem
|
||||||
|
.then(body => {
|
||||||
|
// use the https:// url given by cloudinary
|
||||||
|
const avatarUrl = body.secure_url;
|
||||||
|
|
||||||
|
// set the uploading status to false
|
||||||
|
this.setState({
|
||||||
|
preview: '',
|
||||||
|
uploading: false,
|
||||||
|
value: avatarUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// tell vulcanForm to catch the value
|
||||||
|
this.context.addToAutofilledValues({[this.props.name]: avatarUrl});
|
||||||
|
})
|
||||||
|
.catch(err => console.log("err", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
clearImage(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.context.addToAutofilledValues({[this.props.name]: ''});
|
||||||
|
this.setState({
|
||||||
|
preview: '',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
const { uploading, preview, value } = this.state;
|
||||||
|
// show the actual uploaded image or the preview
|
||||||
|
const image = preview || value;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-group row">
|
||||||
|
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<div className="upload-field">
|
||||||
|
<Dropzone ref="dropzone"
|
||||||
|
multiple={false}
|
||||||
|
onDrop={this.onDrop}
|
||||||
|
accept="image/*"
|
||||||
|
className="dropzone-base"
|
||||||
|
activeClassName="dropzone-active"
|
||||||
|
rejectClassName="dropzone-reject"
|
||||||
|
>
|
||||||
|
<div>Drop an image here, or click to select an image to upload.</div>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{image ?
|
||||||
|
<div className="upload-state">
|
||||||
|
{uploading ? <span>Uploading... Preview:</span> : null}
|
||||||
|
{value ? <a onClick={this.clearImage}><Components.Icon name="close"/> Remove image</a> : null}
|
||||||
|
<img style={{height: 120}} src={image} />
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Upload.propTypes = {
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
value: React.PropTypes.any,
|
||||||
|
label: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
Upload.contextTypes = {
|
||||||
|
addToAutofilledValues: React.PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
registerComponent('Upload', Upload);
|
||||||
|
|
||||||
|
export default Upload;
|
30
packages/vulcan-forms-upload/lib/Upload.scss
Executable file
30
packages/vulcan-forms-upload/lib/Upload.scss
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
.upload-field {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-base {
|
||||||
|
border: 4px dashed #ccc;
|
||||||
|
padding: 30px;
|
||||||
|
transition: "all 0.5s";
|
||||||
|
width: 300px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-active {
|
||||||
|
border: #4FC47F 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-reject {
|
||||||
|
border: #DD3A0A 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-basis: 300px;
|
||||||
|
}
|
3
packages/vulcan-forms-upload/lib/modules.js
Executable file
3
packages/vulcan-forms-upload/lib/modules.js
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
import Upload from "./Upload.jsx";
|
||||||
|
|
||||||
|
export default Upload;
|
24
packages/vulcan-forms-upload/package.js
Executable file
24
packages/vulcan-forms-upload/package.js
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
Package.describe({
|
||||||
|
name: "vulcan:forms-upload",
|
||||||
|
summary: "Vulcan package extending vulcan:forms to upload images to Cloudinary from a drop zone.",
|
||||||
|
version: "1.3.0",
|
||||||
|
git: 'https://github.com/xavcz/nova-forms-upload.git'
|
||||||
|
});
|
||||||
|
|
||||||
|
Package.onUse( function(api) {
|
||||||
|
|
||||||
|
api.versionsFrom("METEOR@1.0");
|
||||||
|
|
||||||
|
api.use([
|
||||||
|
'vulcan:core@1.3.0',
|
||||||
|
'vulcan:forms@1.3.0',
|
||||||
|
'fourseven:scss@3.8.0'
|
||||||
|
]);
|
||||||
|
|
||||||
|
api.addFiles([
|
||||||
|
"lib/Upload.scss"
|
||||||
|
], "client");
|
||||||
|
|
||||||
|
api.mainModule("lib/modules.js", ["client", "server"]);
|
||||||
|
|
||||||
|
});
|
|
@ -1,40 +0,0 @@
|
||||||
import Posts from "meteor/vulcan:posts";
|
|
||||||
import Comments from "meteor/vulcan:comments";
|
|
||||||
import Users from 'meteor/vulcan:users';
|
|
||||||
import { addCallback } from 'meteor/vulcan:core';
|
|
||||||
|
|
||||||
Users.addField({
|
|
||||||
fieldName: 'isDummy',
|
|
||||||
fieldSchema: {
|
|
||||||
type: Boolean,
|
|
||||||
optional: true,
|
|
||||||
hidden: true // never show this
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Posts.addField({
|
|
||||||
fieldName: 'dummySlug',
|
|
||||||
fieldSchema: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
hidden: true // never show this
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Posts.addField({
|
|
||||||
fieldName: 'isDummy',
|
|
||||||
fieldSchema: {
|
|
||||||
type: Boolean,
|
|
||||||
optional: true,
|
|
||||||
hidden: true // never show this
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Comments.addField({
|
|
||||||
fieldName: 'isDummy',
|
|
||||||
fieldSchema: {
|
|
||||||
type: Boolean,
|
|
||||||
optional: true,
|
|
||||||
hidden: true // never show this
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -5,6 +5,27 @@ import Comments from "meteor/vulcan:comments";
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
import Events from "meteor/vulcan:events";
|
import Events from "meteor/vulcan:events";
|
||||||
|
|
||||||
|
const dummyFlag = {
|
||||||
|
fieldName: 'isDummy',
|
||||||
|
fieldSchema: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Users.addField(dummyFlag);
|
||||||
|
Posts.addField(dummyFlag);
|
||||||
|
Comments.addField(dummyFlag);
|
||||||
|
|
||||||
|
Posts.addField({
|
||||||
|
fieldName: 'dummySlug',
|
||||||
|
fieldSchema: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
hidden: true // never show this
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var toTitleCase = function (str) {
|
var toTitleCase = function (str) {
|
||||||
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
|
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
|
||||||
};
|
};
|
||||||
|
@ -58,6 +79,7 @@ var createComment = function (slug, username, body, parentBody) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var createDummyUsers = function () {
|
var createDummyUsers = function () {
|
||||||
|
console.log('// inserting dummy users…');
|
||||||
Accounts.createUser({
|
Accounts.createUser({
|
||||||
username: 'Bruce',
|
username: 'Bruce',
|
||||||
email: 'dummyuser1@telescopeapp.org',
|
email: 'dummyuser1@telescopeapp.org',
|
||||||
|
@ -82,6 +104,7 @@ var createDummyUsers = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
var createDummyPosts = function () {
|
var createDummyPosts = function () {
|
||||||
|
console.log('// inserting dummy posts');
|
||||||
|
|
||||||
createPost("read_this_first", moment().toDate(), "Bruce", "telescope.png");
|
createPost("read_this_first", moment().toDate(), "Bruce", "telescope.png");
|
||||||
|
|
||||||
|
@ -96,6 +119,7 @@ var createDummyPosts = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
var createDummyComments = function () {
|
var createDummyComments = function () {
|
||||||
|
console.log('// inserting dummy comments…');
|
||||||
|
|
||||||
createComment("read_this_first", "Bruce", "What an awesome app!");
|
createComment("read_this_first", "Bruce", "What an awesome app!");
|
||||||
|
|
||||||
|
@ -109,32 +133,21 @@ var createDummyComments = function () {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var deleteDummyContent = function () {
|
const deleteDummyContent = function () {
|
||||||
Users.remove({'profile.isDummy': true});
|
Users.remove({'profile.isDummy': true});
|
||||||
Posts.remove({isDummy: true});
|
Posts.remove({isDummy: true});
|
||||||
Comments.remove({isDummy: true});
|
Comments.remove({isDummy: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
Meteor.methods({
|
|
||||||
addGettingStartedContent: function () {
|
|
||||||
if (Users.isAdmin(Meteor.user())) {
|
|
||||||
createDummyUsers();
|
|
||||||
createDummyPosts();
|
|
||||||
createDummyComments();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeGettingStartedContent: function () {
|
|
||||||
if (Users.isAdmin(Meteor.user()))
|
|
||||||
deleteDummyContent();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Meteor.startup(function () {
|
Meteor.startup(function () {
|
||||||
// insert dummy content only if createDummyContent hasn't happened and there aren't any posts or users in the db
|
// insert dummy content only if createDummyContent hasn't happened and there aren't any posts or users in the db
|
||||||
if (!Users.find().count() && !Events.findOne({name: 'createDummyContent'}) && !Posts.find().count()) {
|
if (!Users.find().count()) {
|
||||||
createDummyUsers();
|
createDummyUsers();
|
||||||
|
}
|
||||||
|
if (!Posts.find().count()) {
|
||||||
createDummyPosts();
|
createDummyPosts();
|
||||||
|
}
|
||||||
|
if (!Comments.find().count()) {
|
||||||
createDummyComments();
|
createDummyComments();
|
||||||
Events.log({name: 'createDummyContent', unique: true, important: true});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -20,12 +20,6 @@ Package.onUse(function (api) {
|
||||||
'vulcan:events@1.3.0',
|
'vulcan:events@1.3.0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// both
|
|
||||||
|
|
||||||
api.addFiles([
|
|
||||||
'lib/getting_started.js'
|
|
||||||
], ['client', 'server']);
|
|
||||||
|
|
||||||
// client
|
// client
|
||||||
|
|
||||||
api.addAssets([
|
api.addAssets([
|
||||||
|
@ -36,7 +30,7 @@ Package.onUse(function (api) {
|
||||||
// server
|
// server
|
||||||
|
|
||||||
api.addFiles([
|
api.addFiles([
|
||||||
'lib/server/dummy_content.js'
|
'lib/server/seed.js'
|
||||||
], ['server']);
|
], ['server']);
|
||||||
|
|
||||||
api.addAssets('content/read_this_first.md', 'server');
|
api.addAssets('content/read_this_first.md', 'server');
|
||||||
|
|
|
@ -89,10 +89,10 @@ Mongo.Collection.prototype.helpers = function(helpers) {
|
||||||
|
|
||||||
export const createCollection = options => {
|
export const createCollection = options => {
|
||||||
|
|
||||||
const {collectionName, typeName, schema, resolvers, mutations, generateGraphQLSchema = true } = options;
|
const {collectionName, typeName, schema, resolvers, mutations, generateGraphQLSchema = true, dbCollectionName } = options;
|
||||||
|
|
||||||
// initialize new Mongo collection
|
// initialize new Mongo collection
|
||||||
const collection = collectionName === 'users' ? Meteor.users : new Mongo.Collection(collectionName);
|
const collection = collectionName === 'users' ? Meteor.users : new Mongo.Collection(dbCollectionName ? dbCollectionName : collectionName.toLowerCase());
|
||||||
|
|
||||||
// decorate collection with options
|
// decorate collection with options
|
||||||
collection.options = options;
|
collection.options = options;
|
||||||
|
|
|
@ -85,6 +85,9 @@ export const GraphQLSchema = {
|
||||||
addResolvers(resolvers) {
|
addResolvers(resolvers) {
|
||||||
this.resolvers = deepmerge(this.resolvers, resolvers);
|
this.resolvers = deepmerge(this.resolvers, resolvers);
|
||||||
},
|
},
|
||||||
|
removeResolver(typeName, resolverName) {
|
||||||
|
delete this.resolvers[typeName][resolverName];
|
||||||
|
},
|
||||||
|
|
||||||
// add objects to context
|
// add objects to context
|
||||||
context: {},
|
context: {},
|
||||||
|
@ -95,7 +98,8 @@ export const GraphQLSchema = {
|
||||||
// generate a GraphQL schema corresponding to a given collection
|
// generate a GraphQL schema corresponding to a given collection
|
||||||
generateSchema(collection) {
|
generateSchema(collection) {
|
||||||
|
|
||||||
const collectionName = collection._name;
|
const collectionName = collection.options.collectionName;
|
||||||
|
|
||||||
const mainTypeName = collection.typeName ? collection.typeName : Utils.camelToSpaces(_.initial(collectionName).join('')); // default to posts -> Post
|
const mainTypeName = collection.typeName ? collection.typeName : Utils.camelToSpaces(_.initial(collectionName).join('')); // default to posts -> Post
|
||||||
|
|
||||||
// backward-compatibility code: we do not want user.telescope fields in the graphql schema
|
// backward-compatibility code: we do not want user.telescope fields in the graphql schema
|
||||||
|
@ -160,4 +164,5 @@ export const addGraphQLSchema = GraphQLSchema.addSchema.bind(GraphQLSchema);
|
||||||
export const addGraphQLQuery = GraphQLSchema.addQuery.bind(GraphQLSchema);
|
export const addGraphQLQuery = GraphQLSchema.addQuery.bind(GraphQLSchema);
|
||||||
export const addGraphQLMutation = GraphQLSchema.addMutation.bind(GraphQLSchema);
|
export const addGraphQLMutation = GraphQLSchema.addMutation.bind(GraphQLSchema);
|
||||||
export const addGraphQLResolvers = GraphQLSchema.addResolvers.bind(GraphQLSchema);
|
export const addGraphQLResolvers = GraphQLSchema.addResolvers.bind(GraphQLSchema);
|
||||||
|
export const removeGraphQLResolver = GraphQLSchema.removeResolver.bind(GraphQLSchema);
|
||||||
export const addToGraphQLContext = GraphQLSchema.addToContext.bind(GraphQLSchema);
|
export const addToGraphQLContext = GraphQLSchema.addToContext.bind(GraphQLSchema);
|
||||||
|
|
|
@ -14,7 +14,7 @@ import './mongo_redux.js';
|
||||||
export { Components, registerComponent, replaceComponent, getRawComponent, getComponent, copyHoCs, populateComponentsApp } from './components.js';
|
export { Components, registerComponent, replaceComponent, getRawComponent, getComponent, copyHoCs, populateComponentsApp } from './components.js';
|
||||||
export { createCollection } from './collections.js';
|
export { createCollection } from './collections.js';
|
||||||
export { Callbacks, addCallback, removeCallback, runCallbacks, runCallbacksAsync } from './callbacks.js';
|
export { Callbacks, addCallback, removeCallback, runCallbacks, runCallbacksAsync } from './callbacks.js';
|
||||||
export { GraphQLSchema, addGraphQLSchema, addGraphQLQuery, addGraphQLMutation, addGraphQLResolvers, addToGraphQLContext } from './graphql.js';
|
export { GraphQLSchema, addGraphQLSchema, addGraphQLQuery, addGraphQLMutation, addGraphQLResolvers, removeGraphQLResolver, addToGraphQLContext } from './graphql.js';
|
||||||
export { Routes, addRoute, getRoute, populateRoutesApp } from './routes.js';
|
export { Routes, addRoute, getRoute, populateRoutesApp } from './routes.js';
|
||||||
export { Utils } from './utils.js';
|
export { Utils } from './utils.js';
|
||||||
export { getSetting } from './settings.js';
|
export { getSetting } from './settings.js';
|
||||||
|
|
|
@ -23,7 +23,6 @@ Package.onUse(function (api) {
|
||||||
'check',
|
'check',
|
||||||
'http',
|
'http',
|
||||||
'email',
|
'email',
|
||||||
'tracker',
|
|
||||||
'ecmascript',
|
'ecmascript',
|
||||||
'service-configuration',
|
'service-configuration',
|
||||||
'shell-server',
|
'shell-server',
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Router, browserHistory } from 'react-router';
|
||||||
|
|
||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
|
||||||
import { InjectData } from 'meteor/vulcan:core';
|
import { InjectData } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
export const RouterClient = {
|
export const RouterClient = {
|
||||||
run(routes, options) {
|
run(routes, options) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
addReducer, addMiddleware,
|
addReducer, addMiddleware,
|
||||||
Routes, populateComponentsApp, populateRoutesApp, runCallbacks,
|
Routes, populateComponentsApp, populateRoutesApp, runCallbacks,
|
||||||
getRenderContext,
|
getRenderContext,
|
||||||
} from 'meteor/vulcan:core';
|
} from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
import { RouterClient } from './router.jsx';
|
import { RouterClient } from './router.jsx';
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import ReactDOMServer from 'react-dom/server';
|
||||||
|
|
||||||
import { RoutePolicy } from 'meteor/routepolicy';
|
import { RoutePolicy } from 'meteor/routepolicy';
|
||||||
|
|
||||||
import { withRenderContextEnvironment, InjectData } from 'meteor/vulcan:core';
|
import { withRenderContextEnvironment, InjectData } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
function isAppUrl(req) {
|
function isAppUrl(req) {
|
||||||
const url = req.url;
|
const url = req.url;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
addRoute,
|
addRoute,
|
||||||
Routes, populateComponentsApp, populateRoutesApp,
|
Routes, populateComponentsApp, populateRoutesApp,
|
||||||
getRenderContext,
|
getRenderContext,
|
||||||
} from 'meteor/vulcan:core';
|
} from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
import { RouterServer } from './router.jsx';
|
import { RouterServer } from './router.jsx';
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ Package.onUse(function (api) {
|
||||||
api.versionsFrom(['METEOR@1.0']);
|
api.versionsFrom(['METEOR@1.0']);
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.3.0',
|
'vulcan:lib@1.3.0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule('lib/server/main.js', 'server');
|
api.mainModule('lib/server/main.js', 'server');
|
||||||
|
|
|
@ -18,11 +18,11 @@ echo "Telescope requires Meteor but it's not installed. Trying to Install..." >&
|
||||||
if [ "$(uname)" == "Darwin" ]; then
|
if [ "$(uname)" == "Darwin" ]; then
|
||||||
# Mac OS platform
|
# Mac OS platform
|
||||||
echo "🔭 ${bold}${purple}Good news you have a Mac and we will install it now! ${reset}";
|
echo "🔭 ${bold}${purple}Good news you have a Mac and we will install it now! ${reset}";
|
||||||
curl https://install.meteor.com/ | sh;
|
curl https://install.meteor.com/ | bash;
|
||||||
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
|
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
|
||||||
# GNU/Linux platform
|
# GNU/Linux platform
|
||||||
echo "🔭 ${bold}${purple}Good news you are on GNU/Linux platform and we will install Meteor now! ${reset}";
|
echo "🔭 ${bold}${purple}Good news you are on GNU/Linux platform and we will install Meteor now! ${reset}";
|
||||||
curl https://install.meteor.com/ | sh;
|
curl https://install.meteor.com/ | bash;
|
||||||
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
|
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
|
||||||
# Windows NT platform
|
# Windows NT platform
|
||||||
echo "🔭 ${bold}${purple}Oh no! you are on a Windows platform and you will need to install Meteor Manually! ${reset}";
|
echo "🔭 ${bold}${purple}Oh no! you are on a Windows platform and you will need to install Meteor Manually! ${reset}";
|
||||||
|
|
39
yarn.lock
39
yarn.lock
|
@ -23,6 +23,10 @@
|
||||||
version "0.8.6"
|
version "0.8.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.8.6.tgz#b34fb880493ba835b0c067024ee70130d6f9bb68"
|
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.8.6.tgz#b34fb880493ba835b0c067024ee70130d6f9bb68"
|
||||||
|
|
||||||
|
"@types/graphql@^0.9.0":
|
||||||
|
version "0.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.0.tgz#fccf859f0d2817687f210737dc3be48a18b1d754"
|
||||||
|
|
||||||
"@types/isomorphic-fetch@0.0.33":
|
"@types/isomorphic-fetch@0.0.33":
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.33.tgz#3ea1b86f8b73e6a7430d01d4dbd5b1f63fd72718"
|
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.33.tgz#3ea1b86f8b73e6a7430d01d4dbd5b1f63fd72718"
|
||||||
|
@ -130,7 +134,7 @@ ansi-styles@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||||
|
|
||||||
apollo-client@^1.0.0-rc.2, apollo-client@^1.0.0-rc.5:
|
apollo-client@^1.0.0-rc.2:
|
||||||
version "1.0.0-rc.5"
|
version "1.0.0-rc.5"
|
||||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.0-rc.5.tgz#862bbd72a267a998627009665b5581ed13e26010"
|
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.0-rc.5.tgz#862bbd72a267a998627009665b5581ed13e26010"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -144,6 +148,21 @@ apollo-client@^1.0.0-rc.2, apollo-client@^1.0.0-rc.5:
|
||||||
"@types/graphql" "^0.8.0"
|
"@types/graphql" "^0.8.0"
|
||||||
"@types/isomorphic-fetch" "0.0.33"
|
"@types/isomorphic-fetch" "0.0.33"
|
||||||
|
|
||||||
|
apollo-client@^1.0.0-rc.6:
|
||||||
|
version "1.0.0-rc.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.0-rc.8.tgz#30105eb33f59ecab63f493177416d720ba0a8fd9"
|
||||||
|
dependencies:
|
||||||
|
graphql "^0.9.1"
|
||||||
|
graphql-anywhere "^3.0.1"
|
||||||
|
graphql-tag "^2.0.0"
|
||||||
|
redux "^3.4.0"
|
||||||
|
symbol-observable "^1.0.2"
|
||||||
|
whatwg-fetch "^2.0.0"
|
||||||
|
optionalDependencies:
|
||||||
|
"@types/async" "^2.0.31"
|
||||||
|
"@types/graphql" "^0.9.0"
|
||||||
|
"@types/isomorphic-fetch" "0.0.33"
|
||||||
|
|
||||||
argparse@^1.0.7:
|
argparse@^1.0.7:
|
||||||
version "1.0.9"
|
version "1.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
||||||
|
@ -240,6 +259,10 @@ asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
|
||||||
|
attr-accept@^1.0.3:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.0.tgz#b5cd35227f163935a8f1de10ed3eba16941f6be6"
|
||||||
|
|
||||||
autoprefixer@^6.3.6:
|
autoprefixer@^6.3.6:
|
||||||
version "6.6.1"
|
version "6.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.6.1.tgz#11a4077abb4b313253ec2f6e1adb91ad84253519"
|
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.6.1.tgz#11a4077abb4b313253ec2f6e1adb91ad84253519"
|
||||||
|
@ -1682,13 +1705,13 @@ graphql-server-module-graphiql@^0.6.0:
|
||||||
resolved "https://registry.yarnpkg.com/graphql-server-module-graphiql/-/graphql-server-module-graphiql-0.6.0.tgz#e37634b05f000731981e8ed13103f9a5861e5da0"
|
resolved "https://registry.yarnpkg.com/graphql-server-module-graphiql/-/graphql-server-module-graphiql-0.6.0.tgz#e37634b05f000731981e8ed13103f9a5861e5da0"
|
||||||
|
|
||||||
graphql-tag@^1.3.1:
|
graphql-tag@^1.3.1:
|
||||||
version "1.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.3.1.tgz#16cdf13635f10bbc968c6f2c6265ffe883a906da"
|
|
||||||
|
|
||||||
graphql-tag@^1.3.2:
|
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.3.2.tgz#7abb3a8fd9f3415d07163314ed237061c785b759"
|
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.3.2.tgz#7abb3a8fd9f3415d07163314ed237061c785b759"
|
||||||
|
|
||||||
|
graphql-tag@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.0.0.tgz#f3efe3b4d64f33bfe8479ae06a461c9d72f2a6fe"
|
||||||
|
|
||||||
graphql-tools@^0.10.1:
|
graphql-tools@^0.10.1:
|
||||||
version "0.10.1"
|
version "0.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-0.10.1.tgz#274aa338d50b1c0b3ed6936eafd8ed3a19ed1828"
|
resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-0.10.1.tgz#274aa338d50b1c0b3ed6936eafd8ed3a19ed1828"
|
||||||
|
@ -2861,6 +2884,12 @@ react-datetime@^2.3.2:
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.0"
|
object-assign "^4.1.0"
|
||||||
|
|
||||||
|
react-dropzone@^3.12.2:
|
||||||
|
version "3.12.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-3.12.2.tgz#7e66a37322a80cf26a205d749ecf8cad0d90aa6f"
|
||||||
|
dependencies:
|
||||||
|
attr-accept "^1.0.3"
|
||||||
|
|
||||||
react-helmet@^3.1.0:
|
react-helmet@^3.1.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-3.3.0.tgz#419933e7ce5a75d04aab3fefe77169eed8e55646"
|
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-3.3.0.tgz#419933e7ce5a75d04aab3fefe77169eed8e55646"
|
||||||
|
|
Loading…
Add table
Reference in a new issue