clean up comment edit form; continue namespacing users.telescope

This commit is contained in:
Sacha Greif 2015-04-28 15:54:19 +09:00
parent 10166402ef
commit 40d38d1364
21 changed files with 177 additions and 134 deletions

View file

@ -1,21 +1,7 @@
<template name="comment_edit"> <template name="comment_edit">
<div class="grid">
{{#with comment}}
<form class="grid-block form-horizontal">
<div class="control-group">
<label class="control-label">{{_ "comment_"}}</label>
<div class="controls comment-field" id="editor"><textarea id="body" value="" class="input-xlarge">{{body}}</textarea></div>
</div>
<div class="form-actions">
<a class="delete-link" href="/comments/deleted">{{_ "delete_comment"}}</a>
<input type="submit" class="button btn-primary" value="{{_ "submit"}}" title="(⌘+enter)" />
</div>
</form>
{{/with}}
</div>
<div class="grid grid-module"> <div class="grid grid-module">
{{> quickForm collection="Comments" doc=comment id="editCommentForm" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls" type="method-update" meteormethod="editComment"}} {{> quickForm collection="Comments" doc=comment id="editCommentForm" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls" type="method-update" meteormethod="editComment" fields=commentFields}}
</div> </div>
<div class="grid grid-module"> <div class="grid grid-module">

View file

@ -1,44 +1,78 @@
var editComment = function(instance) { Template.comment_edit.helpers({
var comment = instance.data.comment; commentFields: function () {
var content = instance.$('#body').val(); var schema = Comments.simpleSchema()._schema;
var comment = this.comment;
var fields = _.filter(_.keys(schema), function (fieldName) {
var field = schema[fieldName];
return Users.can.editField(Meteor.user(), field, comment);
});
return fields;
}
});
AutoForm.hooks({
editCommentForm: {
if(!Meteor.user()) before: {
throw i18n.t('you_must_be_logged_in'); editComment: function(modifier) {
var comment = doc;
Comments.update(comment._id, { // ------------------------------ Checks ------------------------------ //
$set: {
body: content if (!Meteor.user()) {
Messages.flash(i18n.t('you_must_be_logged_in'), "");
return false;
}
// ------------------------------ Callbacks ------------------------------ //
// run all post edit client callbacks on modifier object successively
comment = Telescope.callbacks.run("postEditClient", comment);
return comment;
}
},
onSuccess: function(operation, comment) {
Events.track("edit comment", {'commentId': comment._id});
Router.go('post_page', {_id: comment.postId});
},
onError: function(operation, error) {
console.log(error)
Messages.flash(error.reason.split('|')[0], "error"); // workaround because error.details returns undefined
Messages.clearSeen();
} }
});
Events.track("edit comment", {'postId': comment.postId, 'commentId': comment._id}); }
Router.go('post_page_comment', {_id: comment.postId, commentId: comment._id}); });
};
// delete link
Template.comment_edit.events({
'click .delete-link': function(e){
var comment = this.comment;
e.preventDefault();
if(confirm("Are you sure?")){
Router.go("/");
Meteor.call("deleteCommentById", comment._id, function(error) {
if (error) {
console.log(error);
Messages.flash(error.reason, 'error');
} else {
Messages.flash(i18n.t('your_comment_has_been_deleted'), 'success');
}
});
}
}
});
Template.comment_edit.onRendered(function() { Template.comment_edit.onRendered(function() {
var self = this; var self = this;
this.$("#comment").keydown(function (e) { this.$("#comment").keydown(function (e) {
if(((e.metaKey || e.ctrlKey) && e.keyCode == 13) || (e.ctrlKey && e.keyCode == 13)){ if(((e.metaKey || e.ctrlKey) && e.keyCode == 13) || (e.ctrlKey && e.keyCode == 13)){
editComment(self); // editComment(self);
} }
}); });
}); });
Template.comment_edit.events({
'click input[type=submit]': function(e, instance){
e.preventDefault();
editComment(instance);
},
'click .delete-link': function(e){
var comment = this;
e.preventDefault();
if(confirm(i18n.t("are_you_sure"))){
Meteor.call('removeComment', comment._id);
Router.go('post_page', {_id: comment.postId});
Messages.flash("Your comment has been deleted.", "success");
}
}
});

View file

@ -72,7 +72,11 @@ Telescope.schemas.comments = new SimpleSchema({
}, },
postId: { postId: {
type: String, type: String,
optional: true optional: true,
editableBy: ["owner", "admin"], // TODO: should users be able to set postId, but not modify it?
autoform: {
omit: true // never show this
}
}, },
userId: { userId: {
type: String, type: String,

View file

@ -59,7 +59,8 @@ Meteor.methods({
// parentCommentId // parentCommentId
var user = Meteor.user(), var user = Meteor.user(),
hasAdminRights = Users.is.admin(user); hasAdminRights = Users.is.admin(user),
schema = Comments.simpleSchema()._schema;
// ------------------------------ Checks ------------------------------ // // ------------------------------ Checks ------------------------------ //
@ -88,8 +89,8 @@ Meteor.methods({
// clear restricted properties // clear restricted properties
_.keys(comment).forEach(function (fieldName) { _.keys(comment).forEach(function (fieldName) {
var field = commentSchemaObject[fieldName]; var field = schema[fieldName];
if (!Users.can.editField(user, comment, field)) { if (!Users.can.submitField(user, field)) {
throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + fieldName); throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + fieldName);
} }
@ -107,7 +108,8 @@ Meteor.methods({
var user = Meteor.user(), var user = Meteor.user(),
hasAdminRights = Users.is.admin(user), hasAdminRights = Users.is.admin(user),
comment = Comments.findOne(commentId); comment = Comments.findOne(commentId),
schema = Comments.simpleSchema()._schema;
// ------------------------------ Checks ------------------------------ // // ------------------------------ Checks ------------------------------ //
@ -122,8 +124,8 @@ Meteor.methods({
// loop over each property being operated on // loop over each property being operated on
_.keys(operation).forEach(function (fieldName) { _.keys(operation).forEach(function (fieldName) {
var field = Posts.schema._schema[fieldName]; var field = schema[fieldName];
if (!Users.can.editField(user, comment, field)) { if (!Users.can.editField(user, field, comment)) {
throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + fieldName); throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + fieldName);
} }
@ -136,7 +138,7 @@ Meteor.methods({
// ------------------------------ Update ------------------------------ // // ------------------------------ Update ------------------------------ //
Posts.update(postId, modifier); Comments.update(commentId, modifier);
// ------------------------------ Callbacks ------------------------------ // // ------------------------------ Callbacks ------------------------------ //
@ -147,9 +149,13 @@ Meteor.methods({
return Comments.findOne(commentId); return Comments.findOne(commentId);
}, },
removeComment: function (commentId) { deleteCommentById: function (commentId) {
var comment = Comments.findOne(commentId); var comment = Comments.findOne(commentId);
if(Users.can.edit(Meteor.user(), comment)){ var user = Meteor.user();
if(Users.can.edit(user, comment)){
// decrement post comment count and remove user ID from post // decrement post comment count and remove user ID from post
Posts.update(comment.postId, { Posts.update(comment.postId, {
$inc: {commentCount: -1}, $inc: {commentCount: -1},
@ -158,7 +164,7 @@ Meteor.methods({
// decrement user comment count and remove comment ID from user // decrement user comment count and remove comment ID from user
Meteor.users.update({_id: comment.userId}, { Meteor.users.update({_id: comment.userId}, {
$inc: {'commentCount': -1} $inc: {'telescope.commentCount': -1}
}); });
// note: should we also decrease user's comment karma ? // note: should we also decrease user's comment karma ?
@ -169,8 +175,11 @@ Meteor.methods({
htmlBody: 'Deleted', htmlBody: 'Deleted',
isDeleted: true isDeleted: true
}}); }});
}else{ }else{
Messages.flash("You don't have permission to delete this comment.", "error"); Messages.flash("You don't have permission to delete this comment.", "error");
} }
} }
}); });

View file

@ -47,32 +47,4 @@ Telescope.menus.register("adminMenu", [
label: 'users', label: 'users',
description: 'users_dashboard' description: 'users_dashboard'
} }
]);
Telescope.menus.register("userMenu", [
{
route: function () {
return Router.path('user_profile', {_idOrSlug: Meteor.user().slug});
},
label: 'profile',
description: 'view_your_profile'
},
{
route: function () {
return Router.path('user_edit', {slug: Meteor.user().slug});
},
label: 'edit_account',
description: 'edit_your_profile'
},
{
route: 'settings',
label: 'settings',
description: 'settings',
adminOnly: true
},
{
route: 'signOut',
label: 'sign_out',
description: 'sign_out'
}
]); ]);

View file

@ -57,7 +57,7 @@ Telescope.viewParameters.userPosts = function (terms) {
Telescope.viewParameters.userUpvotedPosts = function (terms) { Telescope.viewParameters.userUpvotedPosts = function (terms) {
var user = Meteor.users.findOne(terms.userId); var user = Meteor.users.findOne(terms.userId);
var postsIds = _.pluck(user.votes.upvotedPosts, "itemId"); var postsIds = _.pluck(user.telescope.upvotedPosts, "itemId");
return { return {
find: {_id: {$in: postsIds}, userId: {$ne: terms.userId}}, // exclude own posts find: {_id: {$in: postsIds}, userId: {$ne: terms.userId}}, // exclude own posts
options: {limit: 5, sort: {postedAt: -1}} options: {limit: 5, sort: {postedAt: -1}}
@ -66,7 +66,7 @@ Telescope.viewParameters.userUpvotedPosts = function (terms) {
Telescope.viewParameters.userDownvotedPosts = function (terms) { Telescope.viewParameters.userDownvotedPosts = function (terms) {
var user = Meteor.users.findOne(terms.userId); var user = Meteor.users.findOne(terms.userId);
var postsIds = _.pluck(user.votes.downvotedPosts, "itemId"); var postsIds = _.pluck(user.telescope.downvotedPosts, "itemId");
// TODO: sort based on votedAt timestamp and not postedAt, if possible // TODO: sort based on votedAt timestamp and not postedAt, if possible
return { return {
find: {_id: {$in: postsIds}}, find: {_id: {$in: postsIds}},

View file

@ -16,6 +16,7 @@ Package.onUse(function (api) {
'accounts-twitter', 'accounts-twitter',
'reactive-var', 'reactive-var',
'http', 'http',
'email',
'aldeed:simple-schema@1.3.2', 'aldeed:simple-schema@1.3.2',
'aldeed:collection2@2.3.3', 'aldeed:collection2@2.3.3',
'aldeed:autoform@5.1.2', 'aldeed:autoform@5.1.2',

View file

@ -117,7 +117,7 @@ Comments.registerField(
function setNotificationDefaults (user) { function setNotificationDefaults (user) {
// set notifications default preferences // set notifications default preferences
user.profile.notifications = { user.telescope.notifications = {
users: false, users: false,
posts: false, posts: false,
comments: true, comments: true,

View file

@ -1,7 +1,7 @@
<template name="post_edit"> <template name="post_edit">
<div class="grid grid-module"> <div class="grid grid-module">
{{> quickForm collection="Posts" doc=post id="editPostForm" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls" type="method-update" meteormethod="editPost"}} {{> quickForm collection="Posts" doc=post id="editPostForm" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls" type="method-update" meteormethod="editPost" fields=postFields}}
</div> </div>
<div class="grid grid-module"> <div class="grid grid-module">

View file

@ -1,10 +1,20 @@
Template.post_edit.helpers({
postFields: function () {
var schema = Posts.simpleSchema()._schema;
var post = this.post;
var fields = _.filter(_.keys(schema), function (fieldName) {
var field = schema[fieldName];
return Users.can.editField(Meteor.user(), field, post);
});
return fields;
}
});
AutoForm.hooks({ AutoForm.hooks({
editPostForm: { editPostForm: {
before: { before: {
editPost: function(modifier) { editPost: function(modifier) {
console.log(modifier)
console.log(template)
var post = doc; var post = doc;
// ------------------------------ Checks ------------------------------ // // ------------------------------ Checks ------------------------------ //
@ -17,9 +27,8 @@ AutoForm.hooks({
// ------------------------------ Callbacks ------------------------------ // // ------------------------------ Callbacks ------------------------------ //
// run all post edit client callbacks on modifier object successively // run all post edit client callbacks on modifier object successively
post = Telescope.callbacks.postEditClient.reduce(function(result, currentFunction) { post = Telescope.callbacks.run("postEditClient", post);
return currentFunction(result);
}, post);
return post; return post;
} }

View file

@ -96,8 +96,8 @@ Meteor.methods({
if(!hasAdminRights){ if(!hasAdminRights){
var timeSinceLastPost=timeSinceLast(user, Posts), var timeSinceLastPost = Users.timeSinceLast(user, Posts),
numberOfPostsInPast24Hours=numberOfItemsInPast24Hours(user, Posts), numberOfPostsInPast24Hours = Users.numberOfItemsInPast24Hours(user, Posts),
postInterval = Math.abs(parseInt(Settings.get('postInterval', 30))), postInterval = Math.abs(parseInt(Settings.get('postInterval', 30))),
maxPostsPer24Hours = Math.abs(parseInt(Settings.get('maxPostsPerDay', 30))); maxPostsPer24Hours = Math.abs(parseInt(Settings.get('maxPostsPerDay', 30)));
@ -123,7 +123,7 @@ Meteor.methods({
_.keys(post).forEach(function (fieldName) { _.keys(post).forEach(function (fieldName) {
var field = schema[fieldName]; var field = schema[fieldName];
if (!Users.can.editField(user, post, field)) { if (!Users.can.submitField(user, field)) {
throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + fieldName); throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + fieldName);
} }
@ -163,7 +163,7 @@ Meteor.methods({
_.keys(operation).forEach(function (fieldName) { _.keys(operation).forEach(function (fieldName) {
var field = schema[fieldName]; var field = schema[fieldName];
if (!Users.can.editField(user, post, field)) { if (!Users.can.editField(user, field, post)) {
throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + fieldName); throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + fieldName);
} }

View file

@ -1,6 +1,6 @@
<template name="userAccount"> <template name="userAccount">
<div class="grid-small grid-module dialog user-edit"> <div class="grid-small grid-module dialog user-edit">
{{> quickForm collection="Meteor.users" doc=user id="editUserForm" template="bootstrap3-horizontal" input-col-class="controls" type="update"}} {{> quickForm collection="Meteor.users" doc=user id="editUserForm" template="bootstrap3-horizontal" input-col-class="controls" type="update" fields=userFields}}
<!-- {{#if profileIncomplete}} <!-- {{#if profileIncomplete}}
<div> <div>

View file

@ -32,7 +32,7 @@ Template.userDownvotedPosts.helpers({
var user = this; var user = this;
var posts = Template.instance().posts.get().fetch(); var posts = Template.instance().posts.get().fetch();
posts = _.map(posts, function (post) { posts = _.map(posts, function (post) {
var vote = _.findWhere(user.votes.downvotedPosts, {itemId: post._id}); var vote = _.findWhere(user.telescope.downvotedPosts, {itemId: post._id});
post.votedAt = vote.votedAt; post.votedAt = vote.votedAt;
return post; return post;
}); });

View file

@ -11,7 +11,7 @@ Template.userInfo.helpers({
return Meteor.user() && Meteor.user()._id != this._id && !Users.is.invited(this) && Telescope.utils.invitesEnabled() && Users.can.invite(Meteor.user()); return Meteor.user() && Meteor.user()._id != this._id && !Users.is.invited(this) && Telescope.utils.invitesEnabled() && Users.can.invite(Meteor.user());
}, },
inviteCount: function() { inviteCount: function() {
return Meteor.user().inviteCount; return Meteor.user().telescope.inviteCount;
}, },
getTwitterName: function () { getTwitterName: function () {
return Users.getTwitterName(this); return Users.getTwitterName(this);

View file

@ -32,7 +32,7 @@ Template.userUpvotedPosts.helpers({
var user = this; var user = this;
var posts = Template.instance().posts.get().fetch(); var posts = Template.instance().posts.get().fetch();
posts = _.map(posts, function (post) { posts = _.map(posts, function (post) {
var vote = _.findWhere(user.votes.upvotedPosts, {itemId: post._id}); var vote = _.findWhere(user.telescope.upvotedPosts, {itemId: post._id});
post.votedAt = vote.votedAt; post.votedAt = vote.votedAt;
return post; return post;
}); });

View file

@ -40,7 +40,7 @@ Users.getAuthorName = function(item) {
}; };
Users.getProfileUrl = function (user) { Users.getProfileUrl = function (user) {
return this.getProfileUrlBySlugOrId(user.slug); return this.getProfileUrlBySlugOrId(user.telescope.slug);
}; };
Users.getProfileUrlBySlugOrId = function (slugOrId) { Users.getProfileUrlBySlugOrId = function (slugOrId) {
@ -134,7 +134,7 @@ Users.numberOfItemsInPast24Hours = function (user, collection) {
Users.getUserSetting = function (setting, defaultValue, user) { Users.getUserSetting = function (setting, defaultValue, user) {
var user = (typeof user == 'undefined') ? Meteor.user() : user; var user = (typeof user == 'undefined') ? Meteor.user() : user;
var defaultValue = (typeof defaultValue == "undefined") ? null: defaultValue; var defaultValue = (typeof defaultValue == "undefined") ? null: defaultValue;
var settingValue = this.getProperty(user.profile, setting); var settingValue = this.getProperty(user.telescope, setting);
return (settingValue == null) ? defaultValue : settingValue; return (settingValue == null) ? defaultValue : settingValue;
}; };
@ -155,7 +155,7 @@ Users.setUserSetting = function (setting, value, userArgument) {
console.log('Setting user setting "' + setting + '" to "' + value + '" for ' + Users.getUserName(user)); console.log('Setting user setting "' + setting + '" to "' + value + '" for ' + Users.getUserName(user));
var find = {_id: user._id}; var find = {_id: user._id};
var field = {}; var field = {};
field['profile.'+setting] = value; field['telescope.'+setting] = value;
var options = {$set: field}; var options = {$set: field};
var result = Meteor.users.update(find, options, {validate: false}); var result = Meteor.users.update(find, options, {validate: false});
}; };

View file

@ -0,0 +1,27 @@
Telescope.menus.register("userMenu", [
{
route: function () {
return Router.path('user_profile', {_idOrSlug: Meteor.user().telescope.slug});
},
label: 'profile',
description: 'view_your_profile'
},
{
route: function () {
return Router.path('user_edit', {slug: Meteor.user().telescope.slug});
},
label: 'edit_account',
description: 'edit_your_profile'
},
{
route: 'settings',
label: 'settings',
description: 'settings',
adminOnly: true
},
{
route: 'signOut',
label: 'sign_out',
description: 'sign_out'
}
]);

View file

@ -14,29 +14,29 @@ Users.pubsub = {};
*/ */
Users.pubsub.publicProperties = { // true means exposed Users.pubsub.publicProperties = { // true means exposed
_id: true, _id: true,
commentCount: true,
createdAt: true, createdAt: true,
email_hash: true,
isInvited: true,
karma: true,
postCount: true,
slug: true,
username: true, username: true,
'profile.username': true,
'profile.notifications': true,
'profile.bio': true,
'profile.github': true,
'profile.site': true,
'profile.twitter': true,
'services.twitter.profile_image_url': true, 'services.twitter.profile_image_url': true,
'services.twitter.profile_image_url_https': true, 'services.twitter.profile_image_url_https': true,
'services.facebook.id': true, 'services.facebook.id': true,
'services.twitter.screenName': true, 'services.twitter.screenName': true,
'services.github.screenName': true, // Github is not really used, but there are some mentions to it in the code 'services.github.screenName': true, // Github is not really used, but there are some mentions to it in the code
'votes.downvotedComments': true, 'telescope.commentCount': true,
'votes.downvotedPosts': true, 'telescope.email_hash': true,
'votes.upvotedComments': true, 'telescope.isInvited': true,
'votes.upvotedPosts': true 'telescope.karma': true,
'telescope.postCount': true,
'telescope.slug': true,
'telescope.username': true,
'telescope.notifications': true,
'telescope.bio': true,
'telescope.github': true,
'telescope.site': true,
'telescope.twitter': true,
'telescope.downvotedComments': true,
'telescope.downvotedPosts': true,
'telescope.upvotedComments': true,
'telescope.upvotedPosts': true
}; };
/** /**
@ -53,8 +53,8 @@ Users.pubsub.hiddenProperties = {
*/ */
Users.pubsub.avatarProperties = { Users.pubsub.avatarProperties = {
_id: true, _id: true,
email_hash: true, 'telescope.email_hash': true,
slug: true, 'telescope.slug': true,
username: true, username: true,
'profile.username': true, 'profile.username': true,
'profile.github': true, 'profile.github': true,

View file

@ -49,7 +49,7 @@ Users.controllers.edit = RouteController.extend({
}, },
data: function() { data: function() {
// if there is no slug, default to current user // if there is no slug, default to current user
var user = !!this.params.slug ? Meteor.users.findOne({slug: this.params.slug}) : Meteor.user(); var user = !!this.params.slug ? Meteor.users.findOne({"telescope.slug": this.params.slug}) : Meteor.user();
return { return {
user: user user: user
}; };
@ -89,7 +89,7 @@ Meteor.startup(function () {
// Only allow users with permissions to see the user edit page. // Only allow users with permissions to see the user edit page.
if (Meteor.user() && ( if (Meteor.user() && (
Users.is.admin(Meteor.user()) || Users.is.admin(Meteor.user()) ||
this.params.slug === Meteor.user().slug this.params.slug === Meteor.user().telescope.slug
)) { )) {
this.next(); this.next();
} else { } else {

View file

@ -4,12 +4,12 @@ Accounts.onCreateUser(function(options, user){
var userProperties = { var userProperties = {
profile: options.profile || {}, profile: options.profile || {},
karma: 0, telescope: {
isInvited: false, karma: 0,
postCount: 0, isInvited: false,
commentCount: 0, postCount: 0,
invitedCount: 0, commentCount: 0,
votes: { invitedCount: 0,
upvotedPosts: [], upvotedPosts: [],
downvotedPosts: [], downvotedPosts: [],
upvotedComments: [], upvotedComments: [],
@ -24,14 +24,14 @@ Accounts.onCreateUser(function(options, user){
// if email is set, use it to generate email hash // if email is set, use it to generate email hash
if (Users.getEmail(user)) if (Users.getEmail(user))
user.email_hash = Users.getEmailHash(user); user.telescope.email_hash = Users.getEmailHash(user);
// set username on profile // set username on profile
if (!user.profile.username) if (!user.profile.username)
user.profile.username = user.username; user.profile.username = user.username;
// create slug from username // create slug from username
user.slug = Telescope.utils.slugify(Users.getUserName(user)); user.telescope.slug = Telescope.utils.slugify(Users.getUserName(user));
// if this is not a dummy account, and is the first user ever, make them an admin // if this is not a dummy account, and is the first user ever, make them an admin
user.isAdmin = (!user.profile.isDummy && Meteor.users.find({'profile.isDummy': {$ne: true}}).count() === 0) ? true : false; user.isAdmin = (!user.profile.isDummy && Meteor.users.find({'profile.isDummy': {$ne: true}}).count() === 0) ? true : false;

View file

@ -23,6 +23,7 @@ Package.onUse(function (api) {
'lib/callbacks.js', 'lib/callbacks.js',
'lib/modules.js', 'lib/modules.js',
'lib/helpers.js', 'lib/helpers.js',
'lib/menu.js',
'lib/pubsub.js', 'lib/pubsub.js',
'lib/routes.js' 'lib/routes.js'
], ['client', 'server']); ], ['client', 'server']);