fix load categories bug; work on post new/edit and permissions

This commit is contained in:
Sacha Greif 2016-02-22 13:23:46 +09:00
parent 49c5969317
commit 05a000eb46
20 changed files with 278 additions and 308 deletions

View file

@ -2,6 +2,10 @@
**Nova** is a top-secret, highly unstable experimental branch of Telescope with a really cool name.
### Install
Clone this branch, then `npm install`.
### Philosophy
Nova was born from a simple realization: 80% of the work involed in Telescope comes from focusing on the user experience. But this comes at the detriment of *developer* experience since it means a much larger codebase.

View file

@ -1 +0,0 @@
node_modules

View file

@ -1,7 +0,0 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

View file

@ -1,137 +0,0 @@
{
"dependencies": {
"formsy-react": {
"version": "0.17.0",
"dependencies": {
"form-data-to-object": {
"version": "0.1.0"
}
}
},
"formsy-react-components": {
"version": "0.6.6",
"dependencies": {
"classnames": {
"version": "2.2.3"
}
}
},
"react": {
"version": "0.14.7",
"dependencies": {
"envify": {
"version": "3.4.0",
"dependencies": {
"through": {
"version": "2.3.8"
},
"jstransform": {
"version": "10.1.0",
"dependencies": {
"base62": {
"version": "0.1.1"
},
"esprima-fb": {
"version": "13001.1001.0-dev-harmony-fb"
},
"source-map": {
"version": "0.1.31",
"dependencies": {
"amdefine": {
"version": "1.0.0"
}
}
}
}
}
}
},
"fbjs": {
"version": "0.6.1",
"dependencies": {
"core-js": {
"version": "1.2.6"
},
"loose-envify": {
"version": "1.1.0",
"dependencies": {
"js-tokens": {
"version": "1.0.2"
}
}
},
"promise": {
"version": "7.1.1",
"dependencies": {
"asap": {
"version": "2.0.3"
}
}
},
"ua-parser-js": {
"version": "0.7.10"
},
"whatwg-fetch": {
"version": "0.9.0"
}
}
}
}
},
"react-dom": {
"version": "0.14.7"
},
"react-modal": {
"version": "0.6.1",
"dependencies": {
"element-class": {
"version": "0.2.2"
},
"exenv": {
"version": "1.2.0"
},
"lodash.assign": {
"version": "3.2.0",
"dependencies": {
"lodash._baseassign": {
"version": "3.2.0",
"dependencies": {
"lodash._basecopy": {
"version": "3.0.1"
}
}
},
"lodash._createassigner": {
"version": "3.1.1",
"dependencies": {
"lodash._bindcallback": {
"version": "3.0.1"
},
"lodash._isiterateecall": {
"version": "3.0.9"
},
"lodash.restparam": {
"version": "3.6.1"
}
}
},
"lodash.keys": {
"version": "3.1.2",
"dependencies": {
"lodash._getnative": {
"version": "3.9.1"
},
"lodash.isarguments": {
"version": "3.0.7"
},
"lodash.isarray": {
"version": "3.0.4"
}
}
}
}
}
}
}
}
}

View file

@ -30,4 +30,10 @@ Telescope.registerComponent("CommentEdit", require('./comments/list/CommentEdit.
// categories
Telescope.registerComponent("CategoriesList", require('./categories/list/CategoriesList.jsx'));
Telescope.registerComponent("CategoriesList", require('./categories/list/CategoriesList.jsx'));
// permissions
Telescope.registerComponent("CanCreatePost", require('./permissions/CanCreatePost.jsx'));
Telescope.registerComponent("CanEditPost", require('./permissions/CanEditPost.jsx'));
Telescope.registerComponent("CanView", require('./permissions/CanView.jsx'));
Telescope.registerComponent("CanViewPost", require('./permissions/CanViewPost.jsx'));

View file

@ -0,0 +1,19 @@
const CanCreatePost = React.createClass({
propTypes: {
user: React.PropTypes.object
},
render() {
if (Users.can.post(this.props.user)) {
return this.props.children;
} else if (!this.props.user){
return <p>Please log in.</p>;
} else {
return <p>Sorry, you do not have permissions to post at this time</p>;
}
}
});
module.exports = CanCreatePost;

View file

@ -0,0 +1,20 @@
const CanEditPost = React.createClass({
propTypes: {
user: React.PropTypes.object,
post: React.PropTypes.object
},
render() {
if (Users.can.edit(this.props.user, this.props.post)) {
return this.props.children;
} else if (!this.props.user){
return <p>Please log in.</p>;
} else {
return <p>Sorry, you do not have permissions to edit this post at this time</p>;
}
}
});
module.exports = CanEditPost;

View file

@ -0,0 +1,19 @@
const CanView = React.createClass({
propTypes: {
user: React.PropTypes.object
},
render() {
if (Users.can.view(this.props.user)) {
return this.props.children;
} else if (!this.props.user){
return <p>Please log in.</p>;
} else {
return <p>Sorry, you do not have permissions to post at this time</p>;
}
}
});
module.exports = CanView;

View file

@ -0,0 +1,20 @@
const CanViewPost = React.createClass({
propTypes: {
user: React.PropTypes.object,
post: React.PropTypes.object
},
render() {
if (Users.can.viewPost(this.props.user, this.props.document)) {
return this.props.children;
} else if (!this.props.user){
return <p>Please log in.</p>;
} else {
return <p>Sorry, you do not have permissions to post at this time</p>;
}
}
});
module.exports = CanViewPost;

View file

@ -1,6 +1,6 @@
const Post = (props) => {
({ListContainer, CommentList, CommentNew} = Telescope.components);
({ListContainer, CommentList, CommentNew, PostCategories} = Telescope.components);
const htmlBody = {__html: props.htmlBody};
@ -10,6 +10,7 @@ const Post = (props) => {
<h3>{props.title}</h3>
<p>{props.commentCount} comments</p>
<p>{moment(props.postedAt).fromNow()}</p>
{props.categoriesArray ? <PostCategories categories={props.categoriesArray} /> : ""}
<div dangerouslySetInnerHTML={htmlBody}></div>
<div className="comments-thread">

View file

@ -25,12 +25,19 @@ const Textarea = FRC.Textarea;
const PostEdit = React.createClass({
propTypes: {
post: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object.isRequired,
categories: React.PropTypes.array
},
submitForm(data) {
event.preventDefault();
const modifier = {$set: _.compactObject(data)};
console.log(modifier)
Meteor.call('posts.edit', modifier, this.props.post._id, (error, post) => {
if (error) {
console.log(error);
// handle error
} else {
FlowRouter.go('posts.single', post);
@ -58,6 +65,8 @@ const PostEdit = React.createClass({
render() {
({CanEditPost} = Telescope.components);
const categoriesOptions = this.props.categories.map(category => {
return {
value: category._id,
@ -66,43 +75,43 @@ const PostEdit = React.createClass({
});
return (
<div className="post-edit">
<h3>Edit Post {this.props.post.title}</h3>
<Formsy.Form onSubmit={this.submitForm}>
<Input
name="url"
value={this.props.post.url}
label="URL"
type="text"
className="text-input"
/>
<Input
name="title"
value={this.props.post.title}
label="Title"
type="text"
className="text-input"
/>
<Textarea
name="body"
value={this.props.post.body}
label="Body"
type="text"
className="textarea"
/>
{/*
<CheckboxGroup
name="categories"
value=""
label="Categories"
type="text"
options={categoriesOptions}
/>
*/}
{Users.is.admin(this.props.currentUser) ? this.renderAdminForm() : ""}
<button type="submit" className="button button--primary">Submit</button>
</Formsy.Form>
</div>
<CanEditPost user={this.props.currentUser} post={this.props.post}>
<div className="post-edit">
<h3>Edit Post {this.props.post.title}</h3>
<Formsy.Form onSubmit={this.submitForm}>
<Input
name="url"
value={this.props.post.url}
label="URL"
type="text"
className="text-input"
/>
<Input
name="title"
value={this.props.post.title}
label="Title"
type="text"
className="text-input"
/>
<Textarea
name="body"
value={this.props.post.body}
label="Body"
type="text"
className="textarea"
/>
<CheckboxGroup
name="categories"
value={this.props.post.categories}
label="Categories"
type="text"
options={categoriesOptions}
/>
{Users.is.admin(this.props.currentUser) ? this.renderAdminForm() : ""}
<button type="submit" className="button button--primary">Submit</button>
</Formsy.Form>
</div>
</CanEditPost>
)
}
});

View file

@ -13,6 +13,11 @@ const Textarea = FRC.Textarea;
const PostNew = React.createClass({
propTypes: {
currentUser: React.PropTypes.object,
categories: React.PropTypes.array
},
getInitialState() {
return {
canSubmit: false
@ -51,6 +56,8 @@ const PostNew = React.createClass({
render() {
({CanCreatePost} = Telescope.components);
const categoriesOptions = this.props.categories.map(category => {
return {
value: category._id,
@ -59,43 +66,43 @@ const PostNew = React.createClass({
});
return (
<div className="post-new">
<h3>New Post</h3>
<Formsy.Form onSubmit={this.submitForm}>
<Input
name="url"
value=""
label="URL"
type="text"
className="text-input"
/>
<Input
name="title"
value=""
label="Title"
type="text"
className="text-input"
/>
<Textarea
name="body"
value=""
label="Body"
type="text"
className="textarea"
/>
{/*
<CheckboxGroup
name="categories"
value=""
label="Categories"
type="text"
options={categoriesOptions}
/>
*/}
{Users.is.admin(this.props.currentUser) ? this.renderAdminForm() : ""}
<button type="submit" className="button button--primary">Submit</button>
</Formsy.Form>
</div>
<CanCreatePost user={this.props.currentUser}>
<div className="post-new">
<h3>New Post</h3>
<Formsy.Form onSubmit={this.submitForm}>
<Input
name="url"
value=""
label="URL"
type="text"
className="text-input"
/>
<Input
name="title"
value=""
label="Title"
type="text"
className="text-input"
/>
<Textarea
name="body"
value=""
label="Body"
type="text"
className="textarea"
/>
<CheckboxGroup
name="categories"
value=""
label="Categories"
type="text"
options={categoriesOptions}
/>
{Users.is.admin(this.props.currentUser) ? this.renderAdminForm() : ""}
<button type="submit" className="button button--primary">Submit</button>
</Formsy.Form>
</div>
</CanCreatePost>
)
}
});

View file

@ -21,25 +21,14 @@ FlowRouter.route('/', {
component={PostList}
joins={Posts.simpleSchema().getJoins()}
/>})
// mount(App, {content: <PostListContainer {...queryParams}/>});
}
});
// FlowRouter.route('/post/new', {
// name: 'postNew',
// action: function (params, queryParams) {
// ({ItemContainer} = Telescope.components);
// mount(AppContainer, {content: <ItemContainer/>})
// // mount(App, {content: <PostListContainer {...queryParams}/>});
// }
// });
FlowRouter.route('/posts/new', {
name: 'posts.new',
action(params, queryParams) {
({AppContainer, PostNewContainer} = Telescope.components);
mount(AppContainer, {content: <PostNewContainer />})
// mount(App, {content: <PostListContainer {...queryParams}/>});
}
});
@ -47,8 +36,14 @@ FlowRouter.route('/posts/:_id', {
name: 'posts.single',
action(params, queryParams) {
({AppContainer, ItemContainer, Post} = Telescope.components);
mount(AppContainer, {content: <ItemContainer collection={Posts} publication="posts.single" terms={params} component={Post}/>})
// mount(App, {content: <PostListContainer {...queryParams}/>});
mount(AppContainer, {content:
<ItemContainer
collection={Posts}
publication="posts.single"
terms={params}
component={Post}
joins={Posts.simpleSchema().getJoins()}
/>})
}
});
@ -57,7 +52,6 @@ FlowRouter.route('/posts/:_id/edit', {
action(params, queryParams) {
({AppContainer, ItemContainer, PostEditContainer} = Telescope.components);
mount(AppContainer, {content: <ItemContainer collection={Posts} publication="posts.single" terms={params} component={PostEditContainer}/>})
// mount(App, {content: <PostListContainer {...queryParams}/>});
}
});

View file

@ -5,12 +5,12 @@ Package.describe({
git: "https://github.com/TelescopeJS/telescope.git"
});
Npm.depends({
'formsy-react': '0.17.0',
'formsy-react-components': '0.6.6',
'react-dom': '0.14.7',
'react-modal': '0.6.1'
});
// Npm.depends({
// 'formsy-react': '0.17.0',
// 'formsy-react-components': '0.6.6',
// 'react-dom': '0.14.7',
// 'react-modal': '0.6.1'
// });
Package.onUse(function (api) {

View file

@ -6,7 +6,8 @@ const ItemContainer = React.createClass({
collection: React.PropTypes.object.isRequired,
component: React.PropTypes.func.isRequired,
publication: React.PropTypes.string.isRequired,
terms: React.PropTypes.object
terms: React.PropTypes.object,
joins: React.PropTypes.array
},
mixins: [ReactMeteorData],
@ -15,9 +16,30 @@ const ItemContainer = React.createClass({
const subscription = Meteor.subscribe(this.props.publication, this.props.terms);
const collection = this.props.collection;
const result = collection.findOne(this.props.terms);
// look for any specified joins
if (this.props.joins) {
// loop over each join
this.props.joins.forEach(join => {
// get the property containing the id or ids
const joinProperty = result[join.property];
const collection = Meteor.isClient ? window[join.collection] : global[join.collection];
// perform the join
if (Array.isArray(joinProperty)) { // join property is an array of ids
result[join.joinAs] = collection.find({_id: {$in: joinProperty}}).fetch();
} else { // join property is a single id
result[join.joinAs] = collection.findOne({_id: joinProperty});
}
});
}
return {
results: collection.findOne(this.props.terms),
result: result,
currentUser: Meteor.user()
};
},
@ -26,9 +48,9 @@ const ItemContainer = React.createClass({
const Component = this.props.component; // could be Post or PostEdit
if (this.data.results) {
if (this.data.result) {
return (
<Component {...this.data.results} />
<Component {...this.data.result} />
)
} else {
return <p>Loading</p>

View file

@ -8,7 +8,7 @@ const PostEditContainer = React.createClass({
return {
categories: Categories.find().fetch(),
postUrl: Session.get("postUrl"),
// postUrl: Session.get("postUrl"),
currentUser: Meteor.user()
};
},

View file

@ -5,9 +5,9 @@ Categories.before.insert(function (userId, doc) {
doc.slug = Telescope.utils.getUnusedSlug(Categories, slug);
});
// generate slug on edit
// generate slug on edit, if it has changed
Categories.before.update(function (userId, doc, fieldNames, modifier) {
if (modifier.$set && modifier.$set.slug) {
if (modifier.$set && modifier.$set.slug && modifier.$set.slug !== doc.slug) {
modifier.$set.slug = Telescope.utils.getUnusedSlug(Categories, modifier.$set.slug);
}
});
@ -24,7 +24,7 @@ Telescope.callbacks.add("postClass", addCategoryClass);
// make sure all categories in the post.categories array exist in the db
var checkCategories = function (post) {
// if there are not categories, stop here
// if there are no categories, stop here
if (!post.categories || post.categories.length === 0) {
return;
}
@ -33,7 +33,7 @@ var checkCategories = function (post) {
var categoryCount = Categories.find({_id: {$in: post.categories}}).count();
if (post.categories.length !== categoryCount) {
throw new Meteor.Error('invalid_category', i18n.t('invalid_category'));
throw new Meteor.Error('invalid_category', __('invalid_category'));
}
};
@ -49,36 +49,38 @@ function postEditCheckCategories (post) {
}
Telescope.callbacks.add("postEdit", postEditCheckCategories);
function addParentCategoriesOnSubmit (post) {
var categories = post.categories;
var newCategories = [];
if (categories) {
categories.forEach(function (categoryId) {
var category = Categories.findOne(categoryId);
newCategories = newCategories.concat(_.pluck(category.getParents().reverse(), "_id"));
newCategories.push(category._id);
});
}
post.categories = _.unique(newCategories);
return post;
}
Telescope.callbacks.add("postSubmit", addParentCategoriesOnSubmit);
// TODO: debug this
function addParentCategoriesOnEdit (modifier, post) {
if (modifier.$unset && modifier.$unset.categories !== undefined) {
return modifier;
}
// function addParentCategoriesOnSubmit (post) {
// var categories = post.categories;
// var newCategories = [];
// if (categories) {
// categories.forEach(function (categoryId) {
// var category = Categories.findOne(categoryId);
// newCategories = newCategories.concat(_.pluck(category.getParents().reverse(), "_id"));
// newCategories.push(category._id);
// });
// }
// post.categories = _.unique(newCategories);
// return post;
// }
// Telescope.callbacks.add("postSubmit", addParentCategoriesOnSubmit);
var categories = modifier.$set.categories;
var newCategories = [];
if (categories) {
categories.forEach(function (categoryId) {
var category = Categories.findOne(categoryId);
newCategories = newCategories.concat(_.pluck(category.getParents().reverse(), "_id"));
newCategories.push(category._id);
});
}
modifier.$set.categories = _.unique(newCategories);
return modifier;
}
Telescope.callbacks.add("postEdit", addParentCategoriesOnEdit);
// function addParentCategoriesOnEdit (modifier, post) {
// if (modifier.$unset && modifier.$unset.categories !== undefined) {
// return modifier;
// }
// var categories = modifier.$set.categories;
// var newCategories = [];
// if (categories) {
// categories.forEach(function (categoryId) {
// var category = Categories.findOne(categoryId);
// newCategories = newCategories.concat(_.pluck(category.getParents().reverse(), "_id"));
// newCategories.push(category._id);
// });
// }
// modifier.$set.categories = _.unique(newCategories);
// return modifier;
// }
// Telescope.callbacks.add("postEdit", addParentCategoriesOnEdit);

View file

@ -65,8 +65,8 @@ Telescope.settings.collection.addField([
instructions: 'Let users filter by one or multiple categories at a time.',
options: function () {
return [
{value: "single", label: i18n.t("categories_behavior_one_at_a_time")},
{value: "multiple", label: i18n.t("categories_behavior_multiple")}
{value: "single", label: __("categories_behavior_one_at_a_time")},
{value: "multiple", label: __("categories_behavior_multiple")}
];
}
}

View file

@ -12,7 +12,7 @@ Meteor.startup(function () {
return Categories.find().count();
},
menuLabel: function () {
return i18n.t("categories");
return __("categories");
},
menuItems: function () {
@ -20,7 +20,7 @@ Meteor.startup(function () {
var defaultItem = [{
route: "postsDefault",
label: i18n.t("all_categories"),
label: __("all_categories"),
itemClass: "item-never-active",
template: "defaultMenuItem"
}];

View file

@ -1,28 +1,20 @@
// Load categories from settings, if there are any
Meteor.startup(()=>{
let count = 0;
if (Meteor.settings && Meteor.settings.categories) {
Meteor.settings.categories.forEach(category => {
if (Meteor.settings && Meteor.settings.categories) {
Meteor.settings.categories.forEach(category => {
// look for existing category with same slug
let existingCategory = Categories.findOne({slug: category.slug});
// look for existing category with same slug
const existingCategory = Categories.findOne({slug: category.slug});
if (existingCategory) {
// if category exists, update it with settings data
Categories.update(existingCategory._id, {$set: category});
} else {
// if not, create it
Categories.insert(category);
count ++;
}
});
}
if (count) {
console.log(`// Created ${count} new categories`);
}
});
if (existingCategory) {
// if category exists, update it with settings data except slug
delete category.slug;
Categories.update(existingCategory._id, {$set: category});
} else {
// if not, create it
Categories.insert(category);
console.log(`// Creating category “${category.name}`);
}
});
}