Merge pull request #8203 from tclaus/2999-likes-comment
Re-introduce likes on comments
This commit is contained in:
commit
42ffd6322f
64 changed files with 920 additions and 281 deletions
|
|
@ -73,6 +73,11 @@ Layout/HashAlignment:
|
||||||
EnforcedHashRocketStyle: table
|
EnforcedHashRocketStyle: table
|
||||||
EnforcedColonStyle: table
|
EnforcedColonStyle: table
|
||||||
|
|
||||||
|
# This rule makes haml files less readable, as there is no 'end' there.
|
||||||
|
Layout/CaseIndentation:
|
||||||
|
Exclude:
|
||||||
|
- "app/views/**/*"
|
||||||
|
|
||||||
# Mixing the styles looks just silly.
|
# Mixing the styles looks just silly.
|
||||||
Style/HashSyntax:
|
Style/HashSyntax:
|
||||||
EnforcedStyle: ruby19_no_mixed_keys
|
EnforcedStyle: ruby19_no_mixed_keys
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ We recommend setting up new pods using Ruby 3.1, and updating existing pods to t
|
||||||
* Tell users that there is no help in mobile version, allow to switch to desktop [#8407](https://github.com/diaspora/diaspora/pull/8407)
|
* Tell users that there is no help in mobile version, allow to switch to desktop [#8407](https://github.com/diaspora/diaspora/pull/8407)
|
||||||
* Add Smart App Banner on iOS devices [#8409](https://github.com/diaspora/diaspora/pull/8409)
|
* Add Smart App Banner on iOS devices [#8409](https://github.com/diaspora/diaspora/pull/8409)
|
||||||
* Add a more detailed modal when reporting a post or a comment [#8035](https://github.com/diaspora/diaspora/pull/8035)
|
* Add a more detailed modal when reporting a post or a comment [#8035](https://github.com/diaspora/diaspora/pull/8035)
|
||||||
|
* Re-introduce likes on comments [#8203](https://github.com/diaspora/diaspora/pull/8203)
|
||||||
|
|
||||||
# 0.7.18.2
|
# 0.7.18.2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,17 @@ app.collections.Comments = Backbone.Collection.extend({
|
||||||
|
|
||||||
make : function(text) {
|
make : function(text) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var comment = new app.models.Comment({ "text": text });
|
var comment = new app.models.Comment({"text": text}, {post: this.post});
|
||||||
|
|
||||||
var deferred = comment.save({}, {
|
var deferred = comment.save({}, {
|
||||||
url: "/posts/"+ this.post.id +"/comments",
|
url: "/posts/"+ this.post.id +"/comments",
|
||||||
success: function() {
|
success: function() {
|
||||||
comment.set({author: app.currentUser.toJSON(), parent: self.post });
|
comment.set({author: app.currentUser.toJSON(), parent: self.post });
|
||||||
|
|
||||||
|
// Need interactions after make
|
||||||
|
comment.interactions = new app.models.LikeInteractions(
|
||||||
|
_.extend({comment: comment, post: self.post}, comment.get("interactions"))
|
||||||
|
);
|
||||||
self.add(comment);
|
self.add(comment);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ app.collections.Likes = Backbone.Collection.extend({
|
||||||
model: app.models.Like,
|
model: app.models.Like,
|
||||||
|
|
||||||
initialize : function(models, options) {
|
initialize : function(models, options) {
|
||||||
this.url = "/posts/" + options.post.id + "/likes"; //not delegating to post.url() because when it is in a stream collection it delegates to that url
|
// A comment- like has a post reference and a comment reference
|
||||||
|
this.url = (options.comment != null) ?
|
||||||
|
// not delegating to post.url() because when it is in a stream collection it delegates to that url
|
||||||
|
"/comments/" + options.comment.id + "/likes" :
|
||||||
|
"/posts/" + options.post.id + "/likes";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// @license-end
|
// @license-end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,17 @@
|
||||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||||
|
|
||||||
app.models.Comment = Backbone.Model.extend({
|
app.models.Comment = Backbone.Model.extend({
|
||||||
urlRoot: "/comments"
|
urlRoot: "/comments",
|
||||||
|
|
||||||
|
initialize: function(model, options) {
|
||||||
|
options = options || {};
|
||||||
|
this.post = model.post || options.post || this.collection.post;
|
||||||
|
this.interactions = new app.models.LikeInteractions(
|
||||||
|
_.extend({comment: this, post: this.post}, this.get("interactions"))
|
||||||
|
);
|
||||||
|
this.likes = this.interactions.likes;
|
||||||
|
this.likesCount = this.attributes.likes_count;
|
||||||
|
this.userLike = this.interactions.userLike();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// @license-end
|
// @license-end
|
||||||
|
|
|
||||||
58
app/assets/javascripts/app/models/like_interactions.js
Normal file
58
app/assets/javascripts/app/models/like_interactions.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// This class contains code extracted from interactions.js to factorize likes management between posts and comments
|
||||||
|
|
||||||
|
app.models.LikeInteractions = Backbone.Model.extend({
|
||||||
|
|
||||||
|
initialize: function(options) {
|
||||||
|
this.likes = new app.collections.Likes(this.get("likes"), options);
|
||||||
|
this.post = options.post;
|
||||||
|
},
|
||||||
|
|
||||||
|
likesCount: function() {
|
||||||
|
return this.get("likes_count");
|
||||||
|
},
|
||||||
|
|
||||||
|
userLike: function() {
|
||||||
|
return this.likes.select(function(like) {
|
||||||
|
return like.get("author") && like.get("author").guid === app.currentUser.get("guid");
|
||||||
|
})[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleLike: function() {
|
||||||
|
if (this.userLike()) {
|
||||||
|
this.unlike();
|
||||||
|
} else {
|
||||||
|
this.like();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
like: function() {
|
||||||
|
var self = this;
|
||||||
|
this.likes.create({}, {
|
||||||
|
success: function() {
|
||||||
|
self.post.set({participation: true});
|
||||||
|
self.trigger("change");
|
||||||
|
self.set({"likes_count": self.get("likes_count") + 1});
|
||||||
|
self.likes.trigger("change");
|
||||||
|
},
|
||||||
|
error: function(model, response) {
|
||||||
|
app.flashMessages.handleAjaxError(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unlike: function() {
|
||||||
|
var self = this;
|
||||||
|
this.userLike().destroy({
|
||||||
|
success: function() {
|
||||||
|
// TODO: unlike always sets participation to false in the UI, even if there are more participations left
|
||||||
|
// in the backend (from manually participating, other likes or comments)
|
||||||
|
self.post.set({participation: false});
|
||||||
|
self.trigger("change");
|
||||||
|
self.set({"likes_count": self.get("likes_count") - 1});
|
||||||
|
self.likes.trigger("change");
|
||||||
|
},
|
||||||
|
error: function(model, response) {
|
||||||
|
app.flashMessages.handleAjaxError(response);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -4,7 +4,7 @@ app.models.Post = Backbone.Model.extend(_.extend({}, app.models.formatDateMixin,
|
||||||
urlRoot : "/posts",
|
urlRoot : "/posts",
|
||||||
|
|
||||||
initialize : function() {
|
initialize : function() {
|
||||||
this.interactions = new app.models.Post.Interactions(_.extend({post : this}, this.get("interactions")));
|
this.interactions = new app.models.PostInteractions(_.extend({post: this}, this.get("interactions")));
|
||||||
this.delegateToInteractions();
|
this.delegateToInteractions();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,27 @@
|
||||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||||
|
|
||||||
//require ../post
|
app.models.PostInteractions = app.models.LikeInteractions.extend({
|
||||||
|
initialize: function(options) {
|
||||||
app.models.Post.Interactions = Backbone.Model.extend({
|
app.models.LikeInteractions.prototype.initialize.apply(this, arguments);
|
||||||
initialize : function(options){
|
|
||||||
this.post = options.post;
|
this.post = options.post;
|
||||||
this.comments = new app.collections.Comments(this.get("comments"), {post : this.post});
|
this.comments = new app.collections.Comments(this.get("comments"), {post: this.post});
|
||||||
this.likes = new app.collections.Likes(this.get("likes"), {post : this.post});
|
this.reshares = new app.collections.Reshares(this.get("reshares"), {post: this.post});
|
||||||
this.reshares = new app.collections.Reshares(this.get("reshares"), {post : this.post});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
likesCount : function(){
|
resharesCount: function() {
|
||||||
return this.get("likes_count");
|
|
||||||
},
|
|
||||||
|
|
||||||
resharesCount : function(){
|
|
||||||
return this.get("reshares_count");
|
return this.get("reshares_count");
|
||||||
},
|
},
|
||||||
|
|
||||||
commentsCount : function(){
|
commentsCount: function() {
|
||||||
return this.get("comments_count");
|
return this.get("comments_count");
|
||||||
},
|
},
|
||||||
|
|
||||||
userLike : function(){
|
userReshare: function() {
|
||||||
return this.likes.select(function(like){
|
|
||||||
return like.get("author") && like.get("author").guid === app.currentUser.get("guid");
|
|
||||||
})[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
userReshare : function(){
|
|
||||||
return this.reshares.select(function(reshare){
|
return this.reshares.select(function(reshare){
|
||||||
return reshare.get("author") && reshare.get("author").guid === app.currentUser.get("guid");
|
return reshare.get("author") && reshare.get("author").guid === app.currentUser.get("guid");
|
||||||
})[0];
|
})[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleLike : function() {
|
|
||||||
if(this.userLike()) {
|
|
||||||
this.unlike();
|
|
||||||
} else {
|
|
||||||
this.like();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
like : function() {
|
|
||||||
var self = this;
|
|
||||||
this.likes.create({}, {
|
|
||||||
success: function() {
|
|
||||||
self.post.set({participation: true});
|
|
||||||
self.trigger("change");
|
|
||||||
self.set({"likes_count" : self.get("likes_count") + 1});
|
|
||||||
self.likes.trigger("change");
|
|
||||||
},
|
|
||||||
error: function(model, response) {
|
|
||||||
app.flashMessages.handleAjaxError(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
unlike : function() {
|
|
||||||
var self = this;
|
|
||||||
this.userLike().destroy({success : function() {
|
|
||||||
self.post.set({participation: false});
|
|
||||||
self.trigger('change');
|
|
||||||
self.set({"likes_count" : self.get("likes_count") - 1});
|
|
||||||
self.likes.trigger("change");
|
|
||||||
},
|
|
||||||
error: function(model, response) {
|
|
||||||
app.flashMessages.handleAjaxError(response);
|
|
||||||
}});
|
|
||||||
},
|
|
||||||
|
|
||||||
comment: function(text, options) {
|
comment: function(text, options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
@ -109,7 +61,7 @@ app.models.Post.Interactions = Backbone.Model.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
userCanReshare : function(){
|
userCanReshare: function() {
|
||||||
var isReshare = this.post.get("post_type") === "Reshare"
|
var isReshare = this.post.get("post_type") === "Reshare"
|
||||||
, rootExists = (isReshare ? this.post.get("root") : true)
|
, rootExists = (isReshare ? this.post.get("root") : true)
|
||||||
, publicPost = this.post.get("public")
|
, publicPost = this.post.get("public")
|
||||||
|
|
@ -6,35 +6,52 @@ app.views.Comment = app.views.Content.extend({
|
||||||
className : "comment media",
|
className : "comment media",
|
||||||
tooltipSelector: "time",
|
tooltipSelector: "time",
|
||||||
|
|
||||||
events : function() {
|
subviews: {
|
||||||
|
".likes-on-comment": "likesInfoView"
|
||||||
|
},
|
||||||
|
|
||||||
|
events: function() {
|
||||||
return _.extend({}, app.views.Content.prototype.events, {
|
return _.extend({}, app.views.Content.prototype.events, {
|
||||||
"click .comment_delete": "destroyModel",
|
"click .comment_delete": "destroyModel",
|
||||||
"click .comment_report": "report"
|
"click .comment_report": "report",
|
||||||
|
"click .like": "toggleLike"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize : function(options){
|
initialize: function(options) {
|
||||||
this.templateName = options.templateName || this.templateName;
|
this.templateName = options.templateName || this.templateName;
|
||||||
|
this.model.interactions.on("change", this.render, this);
|
||||||
this.model.on("change", this.render, this);
|
this.model.on("change", this.render, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
presenter : function() {
|
presenter: function() {
|
||||||
return _.extend(this.defaultPresenter(), {
|
return _.extend(this.defaultPresenter(), {
|
||||||
canRemove: this.canRemove(),
|
canRemove: this.canRemove(),
|
||||||
text: app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people"))
|
text: app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people")),
|
||||||
|
likesCount: this.model.attributes.likesCount,
|
||||||
|
userLike: this.model.interactions.userLike()
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
ownComment : function() {
|
ownComment: function() {
|
||||||
return app.currentUser.authenticated() && this.model.get("author").diaspora_id === app.currentUser.get("diaspora_id");
|
return app.currentUser.authenticated() && this.model.get("author").diaspora_id === app.currentUser.get("diaspora_id");
|
||||||
},
|
},
|
||||||
|
|
||||||
postOwner : function() {
|
postOwner: function() {
|
||||||
return app.currentUser.authenticated() && this.model.get("parent").author.diaspora_id === app.currentUser.get("diaspora_id");
|
return app.currentUser.authenticated() && this.model.get("parent").author.diaspora_id === app.currentUser.get("diaspora_id");
|
||||||
},
|
},
|
||||||
|
|
||||||
canRemove : function() {
|
canRemove: function() {
|
||||||
return app.currentUser.authenticated() && (this.ownComment() || this.postOwner());
|
return app.currentUser.authenticated() && (this.ownComment() || this.postOwner());
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleLike: function(evt) {
|
||||||
|
if (evt) { evt.preventDefault(); }
|
||||||
|
this.model.interactions.toggleLike();
|
||||||
|
},
|
||||||
|
|
||||||
|
likesInfoView: function() {
|
||||||
|
return new app.views.LikesInfo({model: this.model});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ app.views.StreamPost = app.views.Post.extend({
|
||||||
subviews : {
|
subviews : {
|
||||||
".feedback": "feedbackView",
|
".feedback": "feedbackView",
|
||||||
".comments": "commentStreamView",
|
".comments": "commentStreamView",
|
||||||
".likes": "likesInfoView",
|
".likes-on-post": "likesInfoView",
|
||||||
".reshares": "resharesInfoView",
|
".reshares": "resharesInfoView",
|
||||||
".post-controls": "postControlsView",
|
".post-controls": "postControlsView",
|
||||||
".post-content": "postContentView",
|
".post-content": "postContentView",
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@
|
||||||
|
|
||||||
$.post(form.attr("action") + "?format=mobile", form.serialize(), function(data){
|
$.post(form.attr("action") + "?format=mobile", form.serialize(), function(data){
|
||||||
Diaspora.Mobile.Comments.updateStream(form, data);
|
Diaspora.Mobile.Comments.updateStream(form, data);
|
||||||
|
// Register new comments
|
||||||
|
$(".stream").trigger("comments.loaded");
|
||||||
}, "html").fail(function(response) {
|
}, "html").fail(function(response) {
|
||||||
Diaspora.Mobile.Alert.handleAjaxError(response);
|
Diaspora.Mobile.Alert.handleAjaxError(response);
|
||||||
Diaspora.Mobile.Comments.resetCommentBox(form);
|
Diaspora.Mobile.Comments.resetCommentBox(form);
|
||||||
|
|
@ -107,10 +109,12 @@
|
||||||
url: toggleReactionsLink.attr("href"),
|
url: toggleReactionsLink.attr("href"),
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
toggleReactionsLink.addClass("active").removeClass("loading");
|
toggleReactionsLink.addClass("active").removeClass("loading");
|
||||||
$(data).insertAfter(bottomBar.children(".show-comments").first());
|
$(data).insertAfter(bottomBar.children(".post-actions-container").first());
|
||||||
self.showCommentBox(commentActionLink);
|
self.showCommentBox(commentActionLink);
|
||||||
bottomBarContainer.getCommentsContainer().find("time.timeago").timeago();
|
bottomBarContainer.getCommentsContainer().find("time.timeago").timeago();
|
||||||
bottomBarContainer.activate();
|
bottomBarContainer.activate();
|
||||||
|
// Inform the comment action for new comments
|
||||||
|
$(".stream").trigger("comments.loaded");
|
||||||
},
|
},
|
||||||
error: function(){
|
error: function(){
|
||||||
bottomBarContainer.deactivate();
|
bottomBarContainer.deactivate();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
$(".like-action", ".stream").bind("tap click", this.onLike);
|
$(".like-action", ".stream").bind("tap click", this.onLike);
|
||||||
$(".reshare-action", ".stream").bind("tap click", this.onReshare);
|
$(".reshare-action", ".stream").bind("tap click", this.onReshare);
|
||||||
|
// Add handler to newly loaded comments
|
||||||
|
var self = this;
|
||||||
|
$(".stream").bind("comments.loaded", function() {
|
||||||
|
$(".like-action", ".stream").bind("tap click", self.onLike);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
showLoader: function(link) {
|
showLoader: function(link) {
|
||||||
|
|
@ -75,8 +80,8 @@
|
||||||
|
|
||||||
onLike: function(evt){
|
onLike: function(evt){
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
var link = $(evt.target).closest(".like-action"),
|
var link = $(evt.target).closest(".like-action").first(),
|
||||||
likeCounter = $(evt.target).closest(".stream-element").find(".like-count");
|
likeCounter = $(evt.target).find(".like-count").first();
|
||||||
|
|
||||||
if(!link.hasClass("loading") && link.hasClass("inactive")) {
|
if(!link.hasClass("loading") && link.hasClass("inactive")) {
|
||||||
Diaspora.Mobile.PostActions.like(likeCounter, link);
|
Diaspora.Mobile.PostActions.like(likeCounter, link);
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,9 @@
|
||||||
|
|
||||||
.comments > .comment,
|
.comments > .comment,
|
||||||
.comment.new-comment-form-wrapper {
|
.comment.new-comment-form-wrapper {
|
||||||
.avatar {
|
|
||||||
height: 35px;
|
|
||||||
width: 35px;
|
|
||||||
}
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-top: 1px dotted $border-grey;
|
border-top: 1px dotted $border-grey;
|
||||||
padding: 10px 0;
|
padding: 10px 0 0;
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
|
@ -57,8 +53,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment.new-comment-form-wrapper { padding-bottom: 0; }
|
|
||||||
|
|
||||||
.submit-button {
|
.submit-button {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
input {
|
input {
|
||||||
|
|
@ -84,6 +78,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.likes-on-comment {
|
||||||
|
&.likes {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media {
|
||||||
|
margin: 0 0 2px;
|
||||||
|
|
||||||
|
&:not(.display-avatars) .entypo-heart {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-likes {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.new-comment {
|
.new-comment {
|
||||||
&:not(.open) .submit-button,
|
&:not(.open) .submit-button,
|
||||||
&:not(.open) .md-header {
|
&:not(.open) .md-header {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
border-radius: 0 0 5px 5px;
|
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
padding: 8px 10px 10px;
|
padding: 8px 10px 10px;
|
||||||
background: $background-grey;
|
background: $background-grey;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
|
@ -10,6 +7,17 @@
|
||||||
min-height: 22px;
|
min-height: 22px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&,
|
||||||
|
.comment-stats {
|
||||||
|
border-bottom-left-radius: $border-radius-small;
|
||||||
|
border-bottom-right-radius: $border-radius-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-actions-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
> a,
|
> a,
|
||||||
.show-comments,
|
.show-comments,
|
||||||
.show-comments > [class^="entypo"] {
|
.show-comments > [class^="entypo"] {
|
||||||
|
|
@ -37,8 +45,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-stats {
|
%stats {
|
||||||
float: right;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
@ -46,9 +53,9 @@
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
font-family: $font-family-base;
|
font-family: $font-family-base;
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
line-height: 22px;
|
line-height: 24px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
vertical-align: top;
|
vertical-align: text-bottom;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,17 +74,36 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.entypo-reshare.active { color: $blue; }
|
.entypo-reshare.active { color: $blue; }
|
||||||
|
|
||||||
.entypo-heart.active { color: $red; }
|
.entypo-heart.active { color: $red; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-action {
|
.post-stats {
|
||||||
|
@extend %stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-stats {
|
||||||
|
@extend %stats;
|
||||||
|
background: $background-grey;
|
||||||
|
border-top: 1px solid $border-grey;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
%action {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 7px;
|
margin: 0 7px;
|
||||||
|
|
||||||
.disabled { color: $medium-gray; }
|
.disabled { color: $medium-gray; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-action {
|
||||||
|
@extend %action;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-action {
|
||||||
|
@extend %action;
|
||||||
|
}
|
||||||
|
|
||||||
.add-comment-switcher { padding-top: 10px; }
|
.add-comment-switcher { padding-top: 10px; }
|
||||||
|
|
||||||
&.inactive {
|
&.inactive {
|
||||||
|
|
@ -91,16 +117,19 @@
|
||||||
|
|
||||||
.stream-element .comments {
|
.stream-element .comments {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 10px;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.content { padding: 0; }
|
.content { padding: 0; }
|
||||||
|
|
||||||
.comment {
|
.comment {
|
||||||
border-top: 1px solid $border-medium-grey;
|
background-color: $framed-background;
|
||||||
padding: 10px 0 0;
|
border: 1px solid $border-medium-grey;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
&:first-child { padding-top: 20px; }
|
.media {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.md-editor {
|
.md-editor {
|
||||||
border: 1px solid $light-grey;
|
border: 1px solid $border-medium-grey;
|
||||||
border-radius: $btn-border-radius-base;
|
border-radius: $btn-border-radius-base;
|
||||||
|
|
||||||
&.active { border-color: $text-grey; }
|
&.active { border-color: $text-grey; }
|
||||||
|
|
|
||||||
|
|
@ -516,9 +516,13 @@ h3.ltr {
|
||||||
font-style: inherit;
|
font-style: inherit;
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 15px 15px;
|
padding: 12px;
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form#new_conversation.new_conversation {
|
form#new_conversation.new_conversation {
|
||||||
|
|
|
||||||
|
|
@ -116,9 +116,6 @@
|
||||||
|
|
||||||
.no-comments { text-align: center; }
|
.no-comments { text-align: center; }
|
||||||
|
|
||||||
a {
|
|
||||||
color: $link-color;
|
|
||||||
}
|
|
||||||
.count {
|
.count {
|
||||||
float: left;
|
float: left;
|
||||||
i {
|
i {
|
||||||
|
|
@ -144,6 +141,7 @@
|
||||||
.comment.new-comment-form-wrapper {
|
.comment.new-comment-form-wrapper {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
.comments > .comment { padding-bottom: 0; }
|
||||||
|
|
||||||
.count,
|
.count,
|
||||||
.interaction-avatars {
|
.interaction-avatars {
|
||||||
|
|
@ -164,4 +162,19 @@
|
||||||
width: $line-height-computed;
|
width: $line-height-computed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.likes {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
|
||||||
|
.bd { display: inline-block; }
|
||||||
|
img { display: inline; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-avatars .entypo-heart {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
margin-right: 5px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comments {
|
||||||
|
.likes {
|
||||||
|
line-height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-likes {
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status-message-location {
|
.status-message-location {
|
||||||
color: $text-grey;
|
color: $text-grey;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
|
|
||||||
|
|
@ -36,5 +36,19 @@
|
||||||
<div class="collapsible comment-content markdown-content">
|
<div class="collapsible comment-content markdown-content">
|
||||||
{{{text}}}
|
{{{text}}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if loggedIn}}
|
||||||
|
<div class="info">
|
||||||
|
<a href="#" class="like" rel='nofollow'>
|
||||||
|
{{~#if userLike~}}
|
||||||
|
{{~t "stream.unlike"~}}
|
||||||
|
{{~else~}}
|
||||||
|
{{~t "stream.like"~}}
|
||||||
|
{{~/if~}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="likes likes-on-comment"> </div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{{#if likesCount}}
|
{{#if likesCount}}
|
||||||
<div class="comment">
|
<div class="comment">
|
||||||
<div class="media">
|
<div class="media{{#if displayAvatars}} display-avatars{{/if}}">
|
||||||
<i class="entypo-heart"></i>
|
<i class="entypo-heart"></i>
|
||||||
|
|
||||||
<div class="bd">
|
<div class="bd">
|
||||||
|
|
@ -8,9 +8,7 @@
|
||||||
<a href="#" class="expand-likes gray">
|
<a href="#" class="expand-likes gray">
|
||||||
{{t "stream.likes" count=likesCount}}
|
{{t "stream.likes" count=likesCount}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
||||||
{{#each likes}}
|
{{#each likes}}
|
||||||
{{#linkToAuthor author}}
|
{{#linkToAuthor author}}
|
||||||
{{{personImage this 'small' 'micro'}}}
|
{{{personImage this 'small' 'micro'}}}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
{{#unless preview}}
|
{{#unless preview}}
|
||||||
<div class="feedback nsfw-hidden"> </div>
|
<div class="feedback nsfw-hidden"> </div>
|
||||||
<div class="likes nsfw-hidden"> </div>
|
<div class="likes likes-on-post nsfw-hidden"> </div>
|
||||||
<div class="reshares nsfw-hidden"> </div>
|
<div class="reshares nsfw-hidden"> </div>
|
||||||
<div class="comments nsfw-hidden"> </div>
|
<div class="comments nsfw-hidden"> </div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ module Api
|
||||||
post = post_service.find!(params.require(:post_id))
|
post = post_service.find!(params.require(:post_id))
|
||||||
raise ActiveRecord::RecordInvalid unless post.public? || private_read?
|
raise ActiveRecord::RecordInvalid unless post.public? || private_read?
|
||||||
|
|
||||||
like_service.create(params[:post_id])
|
like_service.create_for_post(params[:post_id])
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
if e.message == "Validation failed: Target has already been taken"
|
if e.message == "Validation failed: Target has already been taken"
|
||||||
return render_error 409, "Like already exists"
|
return render_error 409, "Like already exists"
|
||||||
|
|
|
||||||
|
|
@ -17,26 +17,11 @@ class CommentsController < ApplicationController
|
||||||
authenticate_user!
|
authenticate_user!
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def index
|
||||||
begin
|
comments = comment_service.find_for_post(params[:post_id])
|
||||||
comment = comment_service.create(params[:post_id], params[:text])
|
respond_with do |format|
|
||||||
rescue ActiveRecord::RecordNotFound
|
format.json { render json: CommentPresenter.as_collection(comments, :as_json, current_user), status: :ok }
|
||||||
render plain: I18n.t("comments.create.error"), status: 404
|
format.mobile { render layout: false, locals: {comments: comments} }
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if comment
|
|
||||||
respond_create_success(comment)
|
|
||||||
else
|
|
||||||
render plain: I18n.t("comments.create.error"), status: 422
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
if comment_service.destroy(params[:id])
|
|
||||||
respond_destroy_success
|
|
||||||
else
|
|
||||||
respond_destroy_error
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -46,11 +31,26 @@ class CommentsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def create
|
||||||
comments = comment_service.find_for_post(params[:post_id])
|
begin
|
||||||
respond_with do |format|
|
comment = comment_service.create(params[:post_id], params[:text])
|
||||||
format.json { render json: CommentPresenter.as_collection(comments), status: 200 }
|
rescue ActiveRecord::RecordNotFound
|
||||||
format.mobile { render layout: false, locals: {comments: comments} }
|
render plain: I18n.t("comments.create.error"), status: :not_found
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if comment
|
||||||
|
respond_create_success(comment)
|
||||||
|
else
|
||||||
|
render plain: I18n.t("comments.create.error"), status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
if comment_service.destroy(params[:id])
|
||||||
|
respond_destroy_success
|
||||||
|
else
|
||||||
|
respond_destroy_error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,23 @@ class LikesController < ApplicationController
|
||||||
authenticate_user!
|
authenticate_user!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
like = if params[:post_id]
|
||||||
|
like_service.find_for_post(params[:post_id])
|
||||||
|
else
|
||||||
|
like_service.find_for_comment(params[:comment_id])
|
||||||
|
end
|
||||||
|
render json: like
|
||||||
|
.includes(author: :profile)
|
||||||
|
.as_api_response(:backbone)
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
like = like_service.create(params[:post_id])
|
like = if params[:post_id]
|
||||||
|
like_service.create_for_post(params[:post_id])
|
||||||
|
else
|
||||||
|
like_service.create_for_comment(params[:comment_id])
|
||||||
|
end
|
||||||
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
|
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
|
||||||
render plain: I18n.t("likes.create.error"), status: 422
|
render plain: I18n.t("likes.create.error"), status: 422
|
||||||
else
|
else
|
||||||
|
|
@ -36,12 +51,6 @@ class LikesController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
|
||||||
render json: like_service.find_for_post(params[:post_id])
|
|
||||||
.includes(author: :profile)
|
|
||||||
.as_api_response(:backbone)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def like_service
|
def like_service
|
||||||
|
|
|
||||||
|
|
@ -7,24 +7,9 @@
|
||||||
class NotificationsController < ApplicationController
|
class NotificationsController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
|
|
||||||
def update
|
|
||||||
note = Notification.where(:recipient_id => current_user.id, :id => params[:id]).first
|
|
||||||
if note
|
|
||||||
note.set_read_state(params[:set_unread] != "true" )
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.json { render :json => { :guid => note.id, :unread => note.unread } }
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
respond_to do |format|
|
|
||||||
format.json { render :json => {}.to_json }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
conditions = {:recipient_id => current_user.id}
|
conditions = {recipient_id: current_user.id}
|
||||||
|
types = NotificationService::NOTIFICATIONS_JSON_TYPES
|
||||||
if params[:type] && types.has_key?(params[:type])
|
if params[:type] && types.has_key?(params[:type])
|
||||||
conditions[:type] = types[params[:type]]
|
conditions[:type] = types[params[:type]]
|
||||||
end
|
end
|
||||||
|
|
@ -33,7 +18,7 @@ class NotificationsController < ApplicationController
|
||||||
per_page = params[:per_page] || 25
|
per_page = params[:per_page] || 25
|
||||||
@notifications = WillPaginate::Collection.create(page, per_page, Notification.where(conditions).count ) do |pager|
|
@notifications = WillPaginate::Collection.create(page, per_page, Notification.where(conditions).count ) do |pager|
|
||||||
result = Notification.where(conditions)
|
result = Notification.where(conditions)
|
||||||
.includes(:target, :actors => :profile)
|
.includes(:target, actors: :profile)
|
||||||
.order("updated_at desc")
|
.order("updated_at desc")
|
||||||
.limit(pager.per_page)
|
.limit(pager.per_page)
|
||||||
.offset(pager.offset)
|
.offset(pager.offset)
|
||||||
|
|
@ -52,13 +37,28 @@ class NotificationsController < ApplicationController
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
format.xml { render :xml => @notifications.to_xml }
|
format.xml { render xml: @notifications.to_xml }
|
||||||
format.json {
|
format.json {
|
||||||
render json: render_as_json(@unread_notification_count, @grouped_unread_notification_counts, @notifications)
|
render json: render_as_json(@unread_notification_count, @grouped_unread_notification_counts, @notifications)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
note = Notification.where(recipient_id: current_user.id, id: params[:id]).first
|
||||||
|
if note
|
||||||
|
note.set_read_state(params[:set_unread] != "true")
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.json { render json: {guid: note.id, unread: note.unread} }
|
||||||
|
end
|
||||||
|
else
|
||||||
|
respond_to do |format|
|
||||||
|
format.json { render json: {}.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def default_serializer_options
|
def default_serializer_options
|
||||||
{
|
{
|
||||||
context: self,
|
context: self,
|
||||||
|
|
@ -67,7 +67,7 @@ class NotificationsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_all
|
def read_all
|
||||||
current_type = types[params[:type]]
|
current_type = NotificationService::NOTIFICATIONS_JSON_TYPES[params[:type]]
|
||||||
notifications = Notification.where(recipient_id: current_user.id, unread: true)
|
notifications = Notification.where(recipient_id: current_user.id, unread: true)
|
||||||
notifications = notifications.where(type: current_type) if params[:type]
|
notifications = notifications.where(type: current_type) if params[:type]
|
||||||
notifications.update_all(unread: false)
|
notifications.update_all(unread: false)
|
||||||
|
|
@ -79,8 +79,8 @@ class NotificationsController < ApplicationController
|
||||||
format.html { redirect_to stream_path }
|
format.html { redirect_to stream_path }
|
||||||
format.mobile { redirect_to stream_path }
|
format.mobile { redirect_to stream_path }
|
||||||
end
|
end
|
||||||
format.xml { render :xml => {}.to_xml }
|
format.xml { render xml: {}.to_xml }
|
||||||
format.json { render :json => {}.to_json }
|
format.json { render json: {}.to_json }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -95,18 +95,4 @@ class NotificationsController < ApplicationController
|
||||||
}
|
}
|
||||||
}.as_json
|
}.as_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def types
|
|
||||||
{
|
|
||||||
"also_commented" => "Notifications::AlsoCommented",
|
|
||||||
"comment_on_post" => "Notifications::CommentOnPost",
|
|
||||||
"liked" => "Notifications::Liked",
|
|
||||||
"mentioned" => "Notifications::MentionedInPost",
|
|
||||||
"mentioned_in_comment" => "Notifications::MentionedInComment",
|
|
||||||
"reshared" => "Notifications::Reshared",
|
|
||||||
"started_sharing" => "Notifications::StartedSharing",
|
|
||||||
"contacts_birthday" => "Notifications::ContactsBirthday"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
helper_method :types
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ module MobileHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def mobile_like_icon(post)
|
def mobile_like_icon(post)
|
||||||
if current_user && current_user.liked?(post)
|
if current_user&.liked?(post)
|
||||||
link_to content_tag(:span, post.likes.size, class: "count like-count"),
|
link_to content_tag(:span, post.likes.size, class: "count like-count"),
|
||||||
"#",
|
"#",
|
||||||
data: {url: post_like_path(post.id, current_user.like_for(post).id)},
|
data: {url: post_like_path(post.id, current_user.like_for(post).id)},
|
||||||
|
|
@ -39,6 +39,20 @@ module MobileHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mobile_like_comment_icon(comment)
|
||||||
|
if current_user&.liked?(comment)
|
||||||
|
link_to content_tag(:span, comment.likes.size, class: "count like-count"),
|
||||||
|
"#",
|
||||||
|
data: {url: comment_like_path(comment.id, current_user.like_for(comment).id)},
|
||||||
|
class: "entypo-heart like-action active"
|
||||||
|
else
|
||||||
|
link_to content_tag(:span, comment.likes.size, class: "count like-count"),
|
||||||
|
"#",
|
||||||
|
data: {url: comment_likes_path(comment.id)},
|
||||||
|
class: "entypo-heart like-action inactive"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def mobile_comment_icon(post)
|
def mobile_comment_icon(post)
|
||||||
link_to content_tag(:span, post.comments.size, class: "count comment-count"),
|
link_to content_tag(:span, post.comments.size, class: "count comment-count"),
|
||||||
new_post_comment_path(post),
|
new_post_comment_path(post),
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ module NotificationsHelper
|
||||||
elsif %w(Notifications::CommentOnPost Notifications::AlsoCommented Notifications::Reshared Notifications::Liked)
|
elsif %w(Notifications::CommentOnPost Notifications::AlsoCommented Notifications::Reshared Notifications::Liked)
|
||||||
.include?(note.type)
|
.include?(note.type)
|
||||||
opts.merge!(opts_for_post(note.linked_object))
|
opts.merge!(opts_for_post(note.linked_object))
|
||||||
|
elsif note.is_a?(Notifications::LikedComment)
|
||||||
|
opts.merge!(opts_for_comment(note.linked_object))
|
||||||
elsif note.is_a?(Notifications::ContactsBirthday)
|
elsif note.is_a?(Notifications::ContactsBirthday)
|
||||||
opts.merge!(opts_for_birthday(note))
|
opts.merge!(opts_for_birthday(note))
|
||||||
end
|
end
|
||||||
|
|
@ -33,7 +35,16 @@ module NotificationsHelper
|
||||||
post_link: link_to(post_page_title(post),
|
post_link: link_to(post_page_title(post),
|
||||||
post_path(post),
|
post_path(post),
|
||||||
data: {ref: post.id},
|
data: {ref: post.id},
|
||||||
class: "hard_object_link").html_safe
|
class: "hard_object_link")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def opts_for_comment(comment)
|
||||||
|
{
|
||||||
|
comment_link: link_to(comment.message.title,
|
||||||
|
post_path(comment.post, anchor: comment.guid),
|
||||||
|
data: {ref: comment.id},
|
||||||
|
class: "hard_object_link")
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
16
app/mailers/notification_mailers/liked_comment.rb
Normal file
16
app/mailers/notification_mailers/liked_comment.rb
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module NotificationMailers
|
||||||
|
class LikedComment < NotificationMailers::Base
|
||||||
|
attr_accessor :like
|
||||||
|
|
||||||
|
delegate :target, to: :like, prefix: true
|
||||||
|
|
||||||
|
def set_headers(like_id) # rubocop:disable Naming/AccessorMethodName
|
||||||
|
@like = Like.find(like_id)
|
||||||
|
|
||||||
|
@headers[:subject] = I18n.t("notifier.liked_comment.liked", name: @sender.name)
|
||||||
|
@headers[:in_reply_to] = @headers[:references] = "<#{@like.parent.commentable.guid}@#{AppConfig.pod_uri.host}>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -32,7 +32,8 @@ class Like < ApplicationRecord
|
||||||
|
|
||||||
after_destroy do
|
after_destroy do
|
||||||
self.parent.update_likes_counter
|
self.parent.update_likes_counter
|
||||||
participation = author.participations.find_by(target_id: target.id)
|
participation_target_id = parent.is_a?(Comment) ? parent.commentable.id : parent.id
|
||||||
|
participation = author.participations.find_by(target_id: participation_target_id)
|
||||||
participation.unparticipate! if participation.present?
|
participation.unparticipate! if participation.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
26
app/models/notifications/liked_comment.rb
Normal file
26
app/models/notifications/liked_comment.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Notifications
|
||||||
|
class LikedComment < Notification
|
||||||
|
def mail_job
|
||||||
|
Workers::Mail::LikedComment
|
||||||
|
end
|
||||||
|
|
||||||
|
def popup_translation_key
|
||||||
|
"notifications.liked_comment"
|
||||||
|
end
|
||||||
|
|
||||||
|
def deleted_translation_key
|
||||||
|
"notifications.liked_comment_deleted"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.notify(like, _recipient_user_ids)
|
||||||
|
actor = like.author
|
||||||
|
target_author = like.target.author
|
||||||
|
|
||||||
|
return unless like.target_type == "Comment" && target_author.local? && actor != target_author
|
||||||
|
|
||||||
|
concatenate_or_create(target_author.owner, like.target, actor).email_the_user(like, actor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -17,6 +17,12 @@ module User::SocialActions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def like_comment!(target, opts={})
|
||||||
|
Like::Generator.new(self, target).create!(opts).tap do
|
||||||
|
update_or_create_participation!(target.commentable)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def participate_in_poll!(target, answer, opts={})
|
def participate_in_poll!(target, answer, opts={})
|
||||||
PollParticipation::Generator.new(self, target, answer).create!(opts).tap do
|
PollParticipation::Generator.new(self, target, answer).create!(opts).tap do
|
||||||
update_or_create_participation!(target)
|
update_or_create_participation!(target)
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,19 @@ class UserPreference < ApplicationRecord
|
||||||
validate :must_be_valid_email_type
|
validate :must_be_valid_email_type
|
||||||
|
|
||||||
VALID_EMAIL_TYPES =
|
VALID_EMAIL_TYPES =
|
||||||
["someone_reported",
|
%w[
|
||||||
"mentioned",
|
someone_reported
|
||||||
"mentioned_in_comment",
|
mentioned
|
||||||
"comment_on_post",
|
mentioned_in_comment
|
||||||
"private_message",
|
comment_on_post
|
||||||
"started_sharing",
|
private_message
|
||||||
"also_commented",
|
started_sharing
|
||||||
"liked",
|
also_commented
|
||||||
"reshared",
|
liked
|
||||||
"contacts_birthday"]
|
liked_comment
|
||||||
|
reshared
|
||||||
|
contacts_birthday
|
||||||
|
].freeze
|
||||||
|
|
||||||
def must_be_valid_email_type
|
def must_be_valid_email_type
|
||||||
unless VALID_EMAIL_TYPES.include?(self.email_type)
|
unless VALID_EMAIL_TYPES.include?(self.email_type)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class CommentPresenter < BasePresenter
|
class CommentPresenter < BasePresenter
|
||||||
def as_json(opts={})
|
def as_json(_opts={})
|
||||||
{
|
{
|
||||||
id: id,
|
id: id,
|
||||||
guid: guid,
|
guid: guid,
|
||||||
text: message.plain_text_for_json,
|
text: message.plain_text_for_json,
|
||||||
author: author.as_api_response(:backbone),
|
author: author.as_api_response(:backbone),
|
||||||
created_at: created_at,
|
created_at: created_at,
|
||||||
mentioned_people: mentioned_people.as_api_response(:backbone)
|
mentioned_people: mentioned_people.as_api_response(:backbone),
|
||||||
|
interactions: build_interactions_json
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -19,11 +20,44 @@ class CommentPresenter < BasePresenter
|
||||||
author: PersonPresenter.new(author).as_api_json,
|
author: PersonPresenter.new(author).as_api_json,
|
||||||
created_at: created_at,
|
created_at: created_at,
|
||||||
mentioned_people: build_mentioned_people_json,
|
mentioned_people: build_mentioned_people_json,
|
||||||
reported: current_user.present? && reports.where(user: current_user).exists?
|
reported: current_user.present? && reports.exists?(user: current_user),
|
||||||
|
interactions: build_interaction_state
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_interaction_state
|
||||||
|
{
|
||||||
|
liked: current_user.present? && likes.exists?(author: current_user.person),
|
||||||
|
likes_count: likes_count
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_interactions_json
|
||||||
|
{
|
||||||
|
likes: as_api(own_likes(likes)),
|
||||||
|
likes_count: likes_count
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_mentioned_people_json
|
def build_mentioned_people_json
|
||||||
mentioned_people.map {|m| PersonPresenter.new(m).as_api_json }
|
mentioned_people.map {|m| PersonPresenter.new(m).as_api_json }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Only send the own_like boolean.
|
||||||
|
# Frontend uses the same methods for post-likes as for comment-likes
|
||||||
|
# Whenever the frontend will be refactored, just send the own_like boolean, instead of a full list of likes
|
||||||
|
# The list of likes is already send when API requests the full list.
|
||||||
|
def own_likes(likes)
|
||||||
|
if current_user
|
||||||
|
likes.where(author: current_user.person)
|
||||||
|
else
|
||||||
|
likes.none
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_api(collection)
|
||||||
|
collection.includes(author: :profile).map {|element|
|
||||||
|
element.as_api_response(:backbone)
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,14 @@ class LastThreeCommentsDecorator
|
||||||
@presenter = presenter
|
@presenter = presenter
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json(options={})
|
def as_json(_options={})
|
||||||
|
current_user = @presenter.current_user
|
||||||
@presenter.as_json.tap do |post|
|
@presenter.as_json.tap do |post|
|
||||||
post[:interactions].merge!(:comments => CommentPresenter.as_collection(@presenter.post.last_three_comments))
|
post[:interactions].merge!(comments: CommentPresenter.as_collection(
|
||||||
|
@presenter.post.last_three_comments,
|
||||||
|
:as_json,
|
||||||
|
current_user
|
||||||
|
))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ class CommentService
|
||||||
post_service.find!(post_id).comments.for_a_stream
|
post_service.find!(post_id).comments.for_a_stream
|
||||||
end
|
end
|
||||||
|
|
||||||
def find!(comment_guid)
|
def find!(id_or_guid)
|
||||||
Comment.find_by!(guid: comment_guid)
|
Comment.find_by!(comment_key(id_or_guid) => id_or_guid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy(comment_id)
|
def destroy(comment_id)
|
||||||
|
|
@ -45,6 +45,11 @@ class CommentService
|
||||||
|
|
||||||
attr_reader :user
|
attr_reader :user
|
||||||
|
|
||||||
|
# We can assume a guid is at least 16 characters long as we have guids set to hex(8) since we started using them.
|
||||||
|
def comment_key(id_or_guid)
|
||||||
|
id_or_guid.to_s.length < 16 ? :id : :guid
|
||||||
|
end
|
||||||
|
|
||||||
def post_service
|
def post_service
|
||||||
@post_service ||= PostService.new(user)
|
@post_service ||= PostService.new(user)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,17 @@ class LikeService
|
||||||
@user = user
|
@user = user
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(post_id)
|
def create_for_post(post_id)
|
||||||
post = post_service.find!(post_id)
|
post = post_service.find!(post_id)
|
||||||
user.like!(post)
|
user.like!(post)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_for_comment(comment_id)
|
||||||
|
comment = comment_service.find!(comment_id)
|
||||||
|
post_service.find!(comment.commentable_id) # checks implicit for visible posts
|
||||||
|
user.like_comment!(comment)
|
||||||
|
end
|
||||||
|
|
||||||
def destroy(like_id)
|
def destroy(like_id)
|
||||||
like = Like.find(like_id)
|
like = Like.find(like_id)
|
||||||
if user.owns?(like)
|
if user.owns?(like)
|
||||||
|
|
@ -25,6 +31,13 @@ class LikeService
|
||||||
user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes
|
user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_for_comment(comment_id)
|
||||||
|
comment = comment_service.find!(comment_id)
|
||||||
|
post_service.find!(comment.post.id) # checks implicit for visible posts
|
||||||
|
likes = comment.likes
|
||||||
|
user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes
|
||||||
|
end
|
||||||
|
|
||||||
def unlike_post(post_id)
|
def unlike_post(post_id)
|
||||||
likes = post_service.find!(post_id).likes
|
likes = post_service.find!(post_id).likes
|
||||||
likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC"))
|
likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC"))
|
||||||
|
|
@ -36,6 +49,17 @@ class LikeService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unlike_comment(comment_id)
|
||||||
|
likes = comment_service.find!(comment_id).likes
|
||||||
|
likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC"))
|
||||||
|
if !likes.empty? && user.owns?(likes[0])
|
||||||
|
user.retract(likes[0])
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :user
|
attr_reader :user
|
||||||
|
|
@ -43,4 +67,8 @@ class LikeService
|
||||||
def post_service
|
def post_service
|
||||||
@post_service ||= PostService.new(user)
|
@post_service ||= PostService.new(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def comment_service
|
||||||
|
@comment_service ||= CommentService.new(user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
class NotificationService
|
class NotificationService
|
||||||
NOTIFICATION_TYPES = {
|
NOTIFICATION_TYPES = {
|
||||||
Comment => [Notifications::MentionedInComment, Notifications::CommentOnPost, Notifications::AlsoCommented],
|
Comment => [Notifications::MentionedInComment, Notifications::CommentOnPost, Notifications::AlsoCommented],
|
||||||
Like => [Notifications::Liked],
|
Like => [Notifications::Liked, Notifications::LikedComment],
|
||||||
StatusMessage => [Notifications::MentionedInPost],
|
StatusMessage => [Notifications::MentionedInPost],
|
||||||
Conversation => [Notifications::PrivateMessage],
|
Conversation => [Notifications::PrivateMessage],
|
||||||
Message => [Notifications::PrivateMessage],
|
Message => [Notifications::PrivateMessage],
|
||||||
|
|
@ -15,6 +15,7 @@ class NotificationService
|
||||||
"also_commented" => "Notifications::AlsoCommented",
|
"also_commented" => "Notifications::AlsoCommented",
|
||||||
"comment_on_post" => "Notifications::CommentOnPost",
|
"comment_on_post" => "Notifications::CommentOnPost",
|
||||||
"liked" => "Notifications::Liked",
|
"liked" => "Notifications::Liked",
|
||||||
|
"liked_comment" => "Notifications::LikedComment",
|
||||||
"mentioned" => "Notifications::MentionedInPost",
|
"mentioned" => "Notifications::MentionedInPost",
|
||||||
"mentioned_in_comment" => "Notifications::MentionedInComment",
|
"mentioned_in_comment" => "Notifications::MentionedInComment",
|
||||||
"reshared" => "Notifications::Reshared",
|
"reshared" => "Notifications::Reshared",
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,6 @@
|
||||||
|
|
||||||
%div{class: direction_for(comment.text)}
|
%div{class: direction_for(comment.text)}
|
||||||
= comment.message.markdownified
|
= comment.message.markdownified
|
||||||
|
.comment-stats
|
||||||
|
.comment-action
|
||||||
|
= mobile_like_comment_icon(comment)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
.media.stream-element{data: {guid: note.id, type: (types.key(note.type) || "")},
|
.media.stream-element{data: {
|
||||||
class: (note.unread ? "unread" : "read")}
|
guid: note.id,
|
||||||
|
type: (NotificationService::NOTIFICATIONS_JSON_TYPES.key(note.type) || "")
|
||||||
|
},
|
||||||
|
class: (note.unread ? "unread" : "read")}
|
||||||
.unread-toggle.pull-right
|
.unread-toggle.pull-right
|
||||||
%i.entypo-eye{title: (note.unread ? t("notifications.index.mark_read") : t("notifications.index.mark_unread"))}
|
%i.entypo-eye{title: (note.unread ? t("notifications.index.mark_read") : t("notifications.index.mark_unread"))}
|
||||||
- if note.type == "Notifications::StartedSharing" && (!defined?(no_aspect_dropdown) || !no_aspect_dropdown)
|
- if note.type == "Notifications::StartedSharing" && (!defined?(no_aspect_dropdown) || !no_aspect_dropdown)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
.notification_element{data: {guid: note.id, type: (types.key(note.type) || "")},
|
.notification_element{data: {
|
||||||
class: (note.unread ? "unread" : "read")}
|
guid: note.id,
|
||||||
|
type: (NotificationService::NOTIFICATIONS_JSON_TYPES.key(note.type) || "")
|
||||||
|
},
|
||||||
|
class: (note.unread ? "unread" : "read")}
|
||||||
.pull-right.unread-toggle
|
.pull-right.unread-toggle
|
||||||
%i.entypo-eye{title: (note.unread ? t("notifications.index.mark_read") : t("notifications.index.mark_unread"))}
|
%i.entypo-eye{title: (note.unread ? t("notifications.index.mark_read") : t("notifications.index.mark_unread"))}
|
||||||
= person_image_tag note.actors.first, :thumb_small
|
= person_image_tag note.actors.first, :thumb_small
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
- case key
|
- case key
|
||||||
- when "also_commented", "comment_on_post"
|
- when "also_commented", "comment_on_post"
|
||||||
%i.entypo-comment
|
%i.entypo-comment
|
||||||
- when "liked"
|
- when "liked", "liked_comment"
|
||||||
%i.entypo-heart
|
%i.entypo-heart
|
||||||
- when "mentioned", "mentioned_in_comment"
|
- when "mentioned", "mentioned_in_comment"
|
||||||
%span.mentionIcon
|
%span.mentionIcon
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
- if @notification.like_target.public?
|
- if @notification.like_target.public?
|
||||||
%p
|
%p
|
||||||
#{t('.liked', name: @notification.sender_name)}:
|
#{t(".liked", name: @notification.sender_name)}:
|
||||||
= post_message(@notification.like_target, html: true)
|
= post_message(@notification.like_target, html: true)
|
||||||
- else
|
- else
|
||||||
%p
|
%p
|
||||||
#{t('notifier.liked.limited_post', name: @notification.sender_name)}.
|
#{t(".limited_post", name: @notification.sender_name)}.
|
||||||
|
|
||||||
%p
|
%p
|
||||||
= link_to t(".view_post"), post_url(@notification.like_target)
|
= link_to t(".view_post"), post_url(@notification.like_target)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<%= post_message(@notification.like_target) %>
|
<%= post_message(@notification.like_target) %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= "#{t("notifier.liked.limited_post", name: @notification.sender_name)}." %>
|
<%= "#{t(".limited_post", name: @notification.sender_name)}." %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= t(".view_post") %>
|
<%= t(".view_post") %>
|
||||||
|
|
|
||||||
10
app/views/notifier/liked_comment.html.haml
Normal file
10
app/views/notifier/liked_comment.html.haml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
- if @notification.like_target.public?
|
||||||
|
%p
|
||||||
|
#{t(".liked", name: @notification.sender_name)}:
|
||||||
|
= post_message(@notification.like_target, html: true)
|
||||||
|
- else
|
||||||
|
%p
|
||||||
|
#{t(".limited_post", name: @notification.sender_name)}.
|
||||||
|
|
||||||
|
%p
|
||||||
|
= link_to t(".view_comment"), post_url(@notification.like_target.root, anchor: @notification.like_target.guid)
|
||||||
10
app/views/notifier/liked_comment.text.erb
Normal file
10
app/views/notifier/liked_comment.text.erb
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<% if @notification.like_target.public? %>
|
||||||
|
<%= "#{t(".liked", name: @notification.sender_name)}:" %>
|
||||||
|
|
||||||
|
<%= post_message(@notification.like_target) %>
|
||||||
|
<% else %>
|
||||||
|
<%= "#{t(".limited_post", name: @notification.sender_name)}." %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= t(".view_comment") %>
|
||||||
|
<%= post_url(@notification.like_target.root, anchor: @notification.like_target.guid) %>
|
||||||
|
|
@ -19,21 +19,19 @@
|
||||||
- if post.is_a?(StatusMessage)
|
- if post.is_a?(StatusMessage)
|
||||||
= render "status_messages/status_message", post: post, photos: post.photos
|
= render "status_messages/status_message", post: post, photos: post.photos
|
||||||
|
|
||||||
.bottom-bar.nsfw-hidden{class: ("inactive" unless defined?(expanded_info) && expanded_info)}
|
- expanded_info = defined?(expanded_info) && expanded_info
|
||||||
= render partial: "comments/post_stats", locals: {post: post}
|
.bottom-bar.nsfw-hidden{class: ("inactive" unless expanded_info)}
|
||||||
|
.post-actions-container
|
||||||
- if defined?(expanded_info) && expanded_info
|
!= show_comments_link(post, expanded_info ? "active" : "")
|
||||||
!= show_comments_link(post, "active")
|
= render partial: "comments/post_stats", locals: {post: post}
|
||||||
|
- if expanded_info
|
||||||
.comment-container
|
.comment-container
|
||||||
%ul.comments
|
%ul.comments
|
||||||
= render partial: "comments/comment", collection: post.comments.for_a_stream, locals: {post: post}
|
= render partial: "comments/comment", collection: post.comments.for_a_stream, locals: {post: post}
|
||||||
|
|
||||||
- else
|
|
||||||
!= show_comments_link(post)
|
|
||||||
|
|
||||||
.ajax-loader.hidden
|
.ajax-loader.hidden
|
||||||
.loader
|
.loader
|
||||||
.spinner
|
.spinner
|
||||||
|
|
||||||
.add-comment-switcher{class: ("hidden" unless defined?(expanded_info) && expanded_info)}
|
.add-comment-switcher{class: ("hidden" unless expanded_info)}
|
||||||
= render partial: "comments/new_comment", locals: {post_id: post.id}
|
= render partial: "comments/new_comment", locals: {post_id: post.id}
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,11 @@
|
||||||
= t(".liked")
|
= t(".liked")
|
||||||
.small-horizontal-spacer
|
.small-horizontal-spacer
|
||||||
|
|
||||||
|
= type.label :liked_comment, class: "checkbox-inline" do
|
||||||
|
= type.check_box :liked_comment, {checked: email_prefs["liked_comment"]}, false, true
|
||||||
|
= t(".liked_comment")
|
||||||
|
.small-horizontal-spacer
|
||||||
|
|
||||||
= type.label :reshared, class: "checkbox-inline" do
|
= type.label :reshared, class: "checkbox-inline" do
|
||||||
= type.check_box :reshared, {checked: email_prefs["reshared"]}, false, true
|
= type.check_box :reshared, {checked: email_prefs["reshared"]}, false, true
|
||||||
= t(".reshared")
|
= t(".reshared")
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,3 @@ module Workers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
8
app/workers/mail/liked_comment.rb
Normal file
8
app/workers/mail/liked_comment.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Workers
|
||||||
|
module Mail
|
||||||
|
class LikedComment < Liked
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -632,22 +632,8 @@ en:
|
||||||
likes:
|
likes:
|
||||||
create:
|
create:
|
||||||
error: "Failed to like."
|
error: "Failed to like."
|
||||||
fail: "Like creation has failed"
|
|
||||||
destroy:
|
destroy:
|
||||||
error: "Failed to unlike."
|
error: "Failed to unlike."
|
||||||
people_like_this:
|
|
||||||
zero: "No likes"
|
|
||||||
one: "%{count} like"
|
|
||||||
other: "%{count} likes"
|
|
||||||
people_like_this_comment:
|
|
||||||
zero: "No likes"
|
|
||||||
one: "%{count} like"
|
|
||||||
other: "%{count} likes"
|
|
||||||
people_dislike_this:
|
|
||||||
zero: "No dislikes"
|
|
||||||
one: "%{count} dislike"
|
|
||||||
other: "%{count} dislikes"
|
|
||||||
not_found: "Post or like not found"
|
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
started_sharing:
|
started_sharing:
|
||||||
|
|
@ -676,6 +662,10 @@ en:
|
||||||
zero: "%{actors} have liked your post %{post_link}."
|
zero: "%{actors} have liked your post %{post_link}."
|
||||||
one: "%{actors} has liked your post %{post_link}."
|
one: "%{actors} has liked your post %{post_link}."
|
||||||
other: "%{actors} have liked your post %{post_link}."
|
other: "%{actors} have liked your post %{post_link}."
|
||||||
|
liked_comment:
|
||||||
|
zero: "%{actors} have liked your comment %{comment_link}."
|
||||||
|
one: "%{actors} has liked your comment %{comment_link}."
|
||||||
|
other: "%{actors} have liked your comment %{comment_link}."
|
||||||
reshared:
|
reshared:
|
||||||
zero: "%{actors} have reshared your post %{post_link}."
|
zero: "%{actors} have reshared your post %{post_link}."
|
||||||
one: "%{actors} has reshared your post %{post_link}."
|
one: "%{actors} has reshared your post %{post_link}."
|
||||||
|
|
@ -688,6 +678,10 @@ en:
|
||||||
zero: "%{actors} commented on a deleted post."
|
zero: "%{actors} commented on a deleted post."
|
||||||
one: "%{actors} commented on a deleted post."
|
one: "%{actors} commented on a deleted post."
|
||||||
other: "%{actors} commented on a deleted post."
|
other: "%{actors} commented on a deleted post."
|
||||||
|
liked_comment_deleted:
|
||||||
|
zero: "%{actors} liked your deleted comment."
|
||||||
|
one: "%{actors} liked your deleted comment."
|
||||||
|
other: "%{actors} liked your deleted comment."
|
||||||
liked_post_deleted:
|
liked_post_deleted:
|
||||||
zero: "%{actors} liked your deleted post."
|
zero: "%{actors} liked your deleted post."
|
||||||
one: "%{actors} liked your deleted post."
|
one: "%{actors} liked your deleted post."
|
||||||
|
|
@ -713,7 +707,8 @@ en:
|
||||||
all_notifications: "All notifications"
|
all_notifications: "All notifications"
|
||||||
also_commented: "Also commented"
|
also_commented: "Also commented"
|
||||||
comment_on_post: "Comment on post"
|
comment_on_post: "Comment on post"
|
||||||
liked: "Liked"
|
liked: "Liked post"
|
||||||
|
liked_comment: "Liked comment"
|
||||||
mentioned: "Mentioned in post"
|
mentioned: "Mentioned in post"
|
||||||
mentioned_in_comment: "Mentioned in comment"
|
mentioned_in_comment: "Mentioned in comment"
|
||||||
reshared: "Reshared"
|
reshared: "Reshared"
|
||||||
|
|
@ -760,6 +755,10 @@ en:
|
||||||
liked: "%{name} liked your post"
|
liked: "%{name} liked your post"
|
||||||
limited_post: "%{name} liked your limited post"
|
limited_post: "%{name} liked your limited post"
|
||||||
view_post: "View post >"
|
view_post: "View post >"
|
||||||
|
liked_comment:
|
||||||
|
liked: "%{name} liked your comment"
|
||||||
|
limited_post: "%{name} liked your comment on a limited post"
|
||||||
|
view_comment: "View comment >"
|
||||||
reshared:
|
reshared:
|
||||||
reshared: "%{name} reshared your post"
|
reshared: "%{name} reshared your post"
|
||||||
view_post: "View post >"
|
view_post: "View post >"
|
||||||
|
|
@ -1307,6 +1306,7 @@ en:
|
||||||
mentioned: "you are mentioned in a post"
|
mentioned: "you are mentioned in a post"
|
||||||
mentioned_in_comment: "you are mentioned in a comment"
|
mentioned_in_comment: "you are mentioned in a comment"
|
||||||
liked: "someone likes your post"
|
liked: "someone likes your post"
|
||||||
|
liked_comment: "someone likes your comment"
|
||||||
reshared: "someone reshares your post"
|
reshared: "someone reshares your post"
|
||||||
comment_on_post: "someone comments on your post"
|
comment_on_post: "someone comments on your post"
|
||||||
also_commented: "someone comments on a post you’ve commented on"
|
also_commented: "someone comments on a post you’ve commented on"
|
||||||
|
|
|
||||||
|
|
@ -86,3 +86,14 @@ Feature: commenting
|
||||||
|
|
||||||
When I click on selector ".toggle_post_comments"
|
When I click on selector ".toggle_post_comments"
|
||||||
Then I should see "Comment 2"
|
Then I should see "Comment 2"
|
||||||
|
|
||||||
|
Scenario: Like a comment in stream view
|
||||||
|
When "alice@alice.alice" has commented "That's cool" on "Look at this dog"
|
||||||
|
And I am on "alice@alice.alice"'s page
|
||||||
|
And I like the comment "That's cool"
|
||||||
|
Then I should see a like within comment "That's cool"
|
||||||
|
|
||||||
|
When I expand likes within comment "That's cool"
|
||||||
|
Then I should see a micro avatar within comment "That's cool"
|
||||||
|
When I unlike comment "That's cool"
|
||||||
|
Then I should not see a micro avatar within comment "That's cool"
|
||||||
|
|
|
||||||
|
|
@ -43,3 +43,14 @@ Feature: reactions mobile post
|
||||||
And I click on selector "a.comment-action"
|
And I click on selector "a.comment-action"
|
||||||
And I confirm the alert after I click on selector "a.remove"
|
And I confirm the alert after I click on selector "a.remove"
|
||||||
Then I should see "0 comments" within ".show-comments"
|
Then I should see "0 comments" within ".show-comments"
|
||||||
|
|
||||||
|
Scenario: liking and unliking a comment
|
||||||
|
When I click on selector "a.comment-action.inactive"
|
||||||
|
And I fill in the following:
|
||||||
|
| text | is that a poodle? |
|
||||||
|
And I press "Comment"
|
||||||
|
Then I should see "is that a poodle?" within ".comment-container"
|
||||||
|
When I toggle like on comment with text "is that a poodle?"
|
||||||
|
Then I should see a like on comment with text "is that a poodle?"
|
||||||
|
When I toggle like on comment with text "is that a poodle?"
|
||||||
|
Then I should see an unliked comment with text "is that a poodle?"
|
||||||
|
|
|
||||||
|
|
@ -44,3 +44,38 @@ end
|
||||||
When /^I enter "([^"]*)" in the comment field$/ do |comment_text|
|
When /^I enter "([^"]*)" in the comment field$/ do |comment_text|
|
||||||
find("textarea.comment-box.mention-textarea").native.send_keys(comment_text)
|
find("textarea.comment-box.mention-textarea").native.send_keys(comment_text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Then /^I like the comment "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
# Find like like-link within comment-block
|
||||||
|
find(id: comment_guid).click_link("Like")
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should see a like within comment "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
block = find(id: comment_guid)
|
||||||
|
expect(block).to have_css(".expand-likes")
|
||||||
|
end
|
||||||
|
|
||||||
|
When /^I expand likes within comment "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
find(id: comment_guid).click_link("1 Like")
|
||||||
|
find(id: comment_guid).find(".entypo-heart").hover # unfocus avatar to get rid of tooltip
|
||||||
|
end
|
||||||
|
|
||||||
|
When /^I unlike comment "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
find(id: comment_guid).click_link("Unlike")
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should see a micro avatar within comment "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
block = find(id: comment_guid)
|
||||||
|
expect(block).to have_css(".micro.avatar")
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should not see a micro avatar within comment "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
block = find(id: comment_guid)
|
||||||
|
expect(block).not_to have_css(".micro.avatar")
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -23,3 +23,26 @@ Then /^the aspect dropdown within "([^"]*)" should be labeled "([^"]*)"/ do |sel
|
||||||
current_scope.should have_css("option.list_cover", text: label)
|
current_scope.should have_css("option.list_cover", text: label)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
When /^I toggle like on comment with text "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
within(id: comment_guid) do
|
||||||
|
find(".entypo-heart.like-action").click
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should see a like on comment with text "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
within(id: comment_guid) do
|
||||||
|
find(".entypo-heart.like-action.active")
|
||||||
|
expect(find(".count.like-count")).to have_text "1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should see an unliked comment with text "([^"]*)"$/ do |comment_text|
|
||||||
|
comment_guid = Comment.find_by(text: comment_text).guid
|
||||||
|
within(id: comment_guid) do
|
||||||
|
find(".entypo-heart.like-action.inactive")
|
||||||
|
expect(find(".count.like-count")).to have_text "0"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
When "I filter notifications by likes" do
|
When "I filter notifications by likes" do
|
||||||
step %(I follow "Liked" within "#notifications_container .list-group")
|
step %(I follow "Liked post" within "#notifications_container .list-group")
|
||||||
end
|
end
|
||||||
|
|
||||||
When "I filter notifications by mentions" do
|
When "I filter notifications by mentions" do
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,18 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
|
"items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" }
|
||||||
},
|
},
|
||||||
"reported": { "type": "boolean" }
|
"reported": { "type": "boolean" },
|
||||||
|
"interactions": {
|
||||||
|
"type": "object",
|
||||||
|
"properties" : {
|
||||||
|
"liked" : { "type": "boolean" },
|
||||||
|
"likes_count" : { "type": "integer" }
|
||||||
|
},
|
||||||
|
"required": ["liked", "likes_count"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"required": ["guid", "created_at", "author", "body", "reported"],
|
"required": ["guid", "created_at", "author", "body", "reported", "interactions"],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -192,6 +201,7 @@
|
||||||
"also_commented",
|
"also_commented",
|
||||||
"comment_on_post",
|
"comment_on_post",
|
||||||
"liked",
|
"liked",
|
||||||
|
"liked_comment",
|
||||||
"mentioned",
|
"mentioned",
|
||||||
"mentioned_in_comment",
|
"mentioned_in_comment",
|
||||||
"reshared",
|
"reshared",
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ describe NotificationsController, type: :controller do
|
||||||
"comment_on_post" => 0,
|
"comment_on_post" => 0,
|
||||||
"contacts_birthday" => 0,
|
"contacts_birthday" => 0,
|
||||||
"liked" => 0,
|
"liked" => 0,
|
||||||
|
"liked_comment" => 0,
|
||||||
"mentioned" => 0,
|
"mentioned" => 0,
|
||||||
"mentioned_in_comment" => 0,
|
"mentioned_in_comment" => 0,
|
||||||
"reshared" => 0,
|
"reshared" => 0,
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,23 @@ describe Api::V1::CommentsController do
|
||||||
expect_to_match_json_schema(comments.to_json, "#/definitions/comments")
|
expect_to_match_json_schema(comments.to_json, "#/definitions/comments")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "retrieves own like state" do
|
||||||
|
like_service.create_for_comment(@comment1.id)
|
||||||
|
|
||||||
|
get(
|
||||||
|
api_v1_post_comments_path(post_id: @status.guid),
|
||||||
|
params: {access_token: access_token}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
comments = response_body(response)
|
||||||
|
expect(comments[0]["interactions"]["liked"]).to eq(true)
|
||||||
|
expect(comments[0]["interactions"]["likes_count"]).to eq(1)
|
||||||
|
expect(comments[1]["interactions"]["liked"]).to eq(false)
|
||||||
|
expect(comments[1]["interactions"]["likes_count"]).to eq(0)
|
||||||
|
|
||||||
|
expect_to_match_json_schema(comments.to_json, "#/definitions/comments")
|
||||||
|
end
|
||||||
|
|
||||||
it "returns reported status of a comment" do
|
it "returns reported status of a comment" do
|
||||||
auth_minimum_scopes.user.reports.create!(item: @comment1, text: "Meh!")
|
auth_minimum_scopes.user.reports.create!(item: @comment1, text: "Meh!")
|
||||||
get(
|
get(
|
||||||
|
|
@ -443,6 +460,10 @@ describe Api::V1::CommentsController do
|
||||||
CommentService.new(user)
|
CommentService.new(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def like_service(user=auth.user)
|
||||||
|
LikeService.new(user)
|
||||||
|
end
|
||||||
|
|
||||||
def response_body(response)
|
def response_body(response)
|
||||||
JSON.parse(response.body)
|
JSON.parse(response.body)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,9 @@ describe Api::V1::LikesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "succeeds in getting post with likes" do
|
it "succeeds in getting post with likes" do
|
||||||
like_service(bob).create(@status.guid)
|
like_service(bob).create_for_post(@status.guid)
|
||||||
like_service(auth.user).create(@status.guid)
|
like_service(auth.user).create_for_post(@status.guid)
|
||||||
like_service(alice).create(@status.guid)
|
like_service(alice).create_for_post(@status.guid)
|
||||||
get(
|
get(
|
||||||
api_v1_post_likes_path(post_id: @status.guid),
|
api_v1_post_likes_path(post_id: @status.guid),
|
||||||
params: {access_token: access_token_minimum_scopes}
|
params: {access_token: access_token_minimum_scopes}
|
||||||
|
|
@ -112,7 +112,7 @@ describe Api::V1::LikesController do
|
||||||
|
|
||||||
describe "#create" do
|
describe "#create" do
|
||||||
context "with right post id" do
|
context "with right post id" do
|
||||||
it "succeeeds in liking post" do
|
it "succeeds in liking post" do
|
||||||
post(
|
post(
|
||||||
api_v1_post_likes_path(post_id: @status.guid),
|
api_v1_post_likes_path(post_id: @status.guid),
|
||||||
params: {access_token: access_token}
|
params: {access_token: access_token}
|
||||||
|
|
@ -181,7 +181,7 @@ describe Api::V1::LikesController do
|
||||||
|
|
||||||
describe "#delete" do
|
describe "#delete" do
|
||||||
before do
|
before do
|
||||||
like_service.create(@status.guid)
|
like_service.create_for_post(@status.guid)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with right post id" do
|
context "with right post id" do
|
||||||
|
|
@ -225,7 +225,7 @@ describe Api::V1::LikesController do
|
||||||
|
|
||||||
context "with improper credentials" do
|
context "with improper credentials" do
|
||||||
it "fails at unliking private post without private:read" do
|
it "fails at unliking private post without private:read" do
|
||||||
like_service(auth_public_only.user).create(@private_status.guid)
|
like_service(auth_public_only.user).create_for_post(@private_status.guid)
|
||||||
delete(
|
delete(
|
||||||
api_v1_post_likes_path(post_id: @private_status.guid),
|
api_v1_post_likes_path(post_id: @private_status.guid),
|
||||||
params: {access_token: access_token}
|
params: {access_token: access_token}
|
||||||
|
|
@ -234,7 +234,7 @@ describe Api::V1::LikesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "fails in unliking post without interactions" do
|
it "fails in unliking post without interactions" do
|
||||||
like_service(auth_minimum_scopes.user).create(@status.guid)
|
like_service(auth_minimum_scopes.user).create_for_post(@status.guid)
|
||||||
delete(
|
delete(
|
||||||
api_v1_post_likes_path(post_id: @status.guid),
|
api_v1_post_likes_path(post_id: @status.guid),
|
||||||
params: {access_token: access_token_minimum_scopes}
|
params: {access_token: access_token_minimum_scopes}
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,15 @@ var factory = {
|
||||||
|
|
||||||
comment : function(overrides) {
|
comment : function(overrides) {
|
||||||
var defaultAttrs = {
|
var defaultAttrs = {
|
||||||
"created_at" : "2012-01-04T00:55:30Z",
|
"created_at": "2012-01-04T00:55:30Z",
|
||||||
"author" : this.author(),
|
"author": this.author(),
|
||||||
"guid" : this.guid(),
|
"guid": this.guid(),
|
||||||
"id" : this.id.next(),
|
"id": this.id.next(),
|
||||||
"text" : "This is a comment!"
|
"text": "This is a comment!"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
overrides = overrides || {};
|
||||||
|
overrides.post = this.post();
|
||||||
return new app.models.Comment(_.extend(defaultAttrs, overrides));
|
return new app.models.Comment(_.extend(defaultAttrs, overrides));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,27 @@ describe Notifier, type: :mailer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".liked_comment" do
|
||||||
|
before do
|
||||||
|
@post = FactoryBot.create(:status_message, author: alice.person, public: true)
|
||||||
|
@comment = FactoryBot.create(:comment, author: alice.person, post: @post)
|
||||||
|
@like = @comment.likes.create!(author: bob.person)
|
||||||
|
@mail = Notifier.send_notification("liked_comment", alice.id, @like.author.id, @like.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "TO: goes to the right person" do
|
||||||
|
expect(@mail.to).to eq([alice.email])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "BODY: contains the original comment" do
|
||||||
|
expect(@mail.body.encoded).to include(@comment.message.plain_text)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "BODY: contains the name of person liking" do
|
||||||
|
expect(@mail.body.encoded).to include(@like.author.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe ".reshared" do
|
describe ".reshared" do
|
||||||
before do
|
before do
|
||||||
@post = FactoryBot.create(:status_message, author: alice.person, public: true)
|
@post = FactoryBot.create(:status_message, author: alice.person, public: true)
|
||||||
|
|
@ -489,6 +510,42 @@ describe Notifier, type: :mailer do
|
||||||
expect(mail.body.encoded).to include(bob.name)
|
expect(mail.body.encoded).to include(bob.name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".liked_comment" do
|
||||||
|
let(:comment) { alice.comment!(limited_post, "Totally is") }
|
||||||
|
let(:like) { bob.like_comment!(comment) }
|
||||||
|
let(:mail) { Notifier.send_notification("liked_comment", alice.id, bob.person.id, like.id) }
|
||||||
|
|
||||||
|
it "TO: goes to the right person" do
|
||||||
|
expect(mail.to).to eq([alice.email])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "FROM: contains the sender's name" do
|
||||||
|
expect(mail["From"].to_s).to eq("\"#{pod_name} (#{bob.name})\" <#{AppConfig.mail.sender_address}>")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "FROM: removes emojis from sender's name" do
|
||||||
|
bob.person.profile.update!(first_name: "1️⃣2️3️⃣ Numbers 123", last_name: "👍✅👍🏻Emojis😀😇❄️")
|
||||||
|
expect(mail["From"].to_s).to eq("\"#{pod_name} (Numbers 123 Emojis)\" <#{AppConfig.mail.sender_address}>")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "SUBJECT: does not show the limited comment" do
|
||||||
|
expect(mail.subject).not_to include("Totally is")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "IN-REPLY-TO and REFERENCES: references the liked post" do
|
||||||
|
expect(mail.in_reply_to).to eq("#{limited_post.guid}@#{AppConfig.pod_uri.host}")
|
||||||
|
expect(mail.references).to eq("#{limited_post.guid}@#{AppConfig.pod_uri.host}")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "BODY: does not show the limited post" do
|
||||||
|
expect(mail.body.encoded).not_to include("Totally is")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "BODY: contains the name of person liking" do
|
||||||
|
expect(mail.body.encoded).to include(bob.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".confirm_email" do
|
describe ".confirm_email" do
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,34 @@ describe Like, type: :model do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#destroy" do
|
describe "#destroy" do
|
||||||
before do
|
let!(:like) { alice.like!(status) }
|
||||||
@like = alice.like!(status)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should delete a participation" do
|
it "should delete a participation" do
|
||||||
expect { @like.destroy }.to change { Participation.count }.by(-1)
|
expect { like.destroy }.to change { Participation.count }.by(-1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should decrease count participation" do
|
it "should decrease count participation" do
|
||||||
alice.comment!(status, "Are you there?")
|
alice.comment!(status, "Are you there?")
|
||||||
@like.destroy
|
like.destroy
|
||||||
participations = Participation.where(target_id: @like.target_id, author_id: @like.author_id)
|
participations = Participation.where(target_id: status.id, author_id: like.author_id)
|
||||||
expect(participations.first.count).to eq(1)
|
expect(participations.first.count).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "on comment" do
|
||||||
|
let(:comment) { bob.comment!(status, "Are you there?") }
|
||||||
|
let!(:like) { alice.like_comment!(comment) }
|
||||||
|
|
||||||
|
it "should delete a participation" do
|
||||||
|
expect { like.destroy }.to change { Participation.count }.by(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should decrease count participation" do
|
||||||
|
alice.comment!(status, "Yes, I am here!")
|
||||||
|
like.destroy
|
||||||
|
participations = Participation.where(target_id: status.id, author_id: like.author_id)
|
||||||
|
expect(participations.first.count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "counter cache" do
|
describe "counter cache" do
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ describe LikesPresenter do
|
||||||
to: "all"
|
to: "all"
|
||||||
)
|
)
|
||||||
bobs_like_service = LikeService.new(bob)
|
bobs_like_service = LikeService.new(bob)
|
||||||
like = bobs_like_service.create(@status.guid)
|
like = bobs_like_service.create_for_post(@status.guid)
|
||||||
@presenter = LikesPresenter.new(like, bob)
|
@presenter = LikesPresenter.new(like, bob)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,67 +2,129 @@
|
||||||
|
|
||||||
describe LikeService do
|
describe LikeService do
|
||||||
let(:post) { alice.post(:status_message, text: "hello", to: alice.aspects.first) }
|
let(:post) { alice.post(:status_message, text: "hello", to: alice.aspects.first) }
|
||||||
|
let(:alice_comment) { CommentService.new(alice).create(post.id, "This is a wonderful post") }
|
||||||
|
let(:bobs_comment) { CommentService.new(bob).create(post.id, "My post was better than yours") }
|
||||||
|
|
||||||
describe "#create" do
|
describe "#create_for_post" do
|
||||||
it "creates a like on my own post" do
|
it "creates a like on my own post" do
|
||||||
expect {
|
expect {
|
||||||
LikeService.new(alice).create(post.id)
|
LikeService.new(alice).create_for_post(post.id)
|
||||||
}.not_to raise_error
|
}.not_to raise_error
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a like on a post of a contact" do
|
it "creates a like on a post of a contact" do
|
||||||
expect {
|
expect {
|
||||||
LikeService.new(bob).create(post.id)
|
LikeService.new(bob).create_for_post(post.id)
|
||||||
}.not_to raise_error
|
}.not_to raise_error
|
||||||
end
|
end
|
||||||
|
|
||||||
it "attaches the like to the post" do
|
it "attaches the like to the post" do
|
||||||
like = LikeService.new(alice).create(post.id)
|
like = LikeService.new(alice).create_for_post(post.id)
|
||||||
expect(post.likes.first.id).to eq(like.id)
|
expect(post.likes.first.id).to eq(like.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "fails if the post does not exist" do
|
it "fails if the post does not exist" do
|
||||||
expect {
|
expect {
|
||||||
LikeService.new(bob).create("unknown id")
|
LikeService.new(bob).create_for_post("unknown id")
|
||||||
}.to raise_error ActiveRecord::RecordNotFound
|
}.to raise_error ActiveRecord::RecordNotFound
|
||||||
end
|
end
|
||||||
|
|
||||||
it "fails if the user can't see the post" do
|
it "fails if the user can't see the post" do
|
||||||
expect {
|
expect {
|
||||||
LikeService.new(eve).create(post.id)
|
LikeService.new(eve).create_for_post(post.id)
|
||||||
}.to raise_error ActiveRecord::RecordNotFound
|
}.to raise_error ActiveRecord::RecordNotFound
|
||||||
end
|
end
|
||||||
|
|
||||||
it "fails if the user already liked the post" do
|
it "fails if the user already liked the post" do
|
||||||
LikeService.new(alice).create(post.id)
|
LikeService.new(alice).create_for_post(post.id)
|
||||||
expect {
|
expect {
|
||||||
LikeService.new(alice).create(post.id)
|
LikeService.new(alice).create_for_post(post.id)
|
||||||
|
}.to raise_error ActiveRecord::RecordInvalid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#create_for_comment" do
|
||||||
|
it "creates a like on a posts comment" do
|
||||||
|
expect {
|
||||||
|
LikeService.new(alice).create_for_comment(alice_comment.id)
|
||||||
|
}.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a like on someone else comment" do
|
||||||
|
expect {
|
||||||
|
LikeService.new(alice).create_for_comment(bobs_comment.id)
|
||||||
|
}.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "attaches the like to the comment" do
|
||||||
|
like = LikeService.new(alice).create_for_comment(bobs_comment.id)
|
||||||
|
expect(bobs_comment.likes.first.id).to eq(like.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if comment does not exist" do
|
||||||
|
expect {
|
||||||
|
LikeService.new(alice).create_for_comment("unknown_id")
|
||||||
|
}.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if user cant see post and its comments" do
|
||||||
|
expect {
|
||||||
|
LikeService.new(eve).create_for_comment(bobs_comment.id)
|
||||||
|
}.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if user already liked the comment" do
|
||||||
|
LikeService.new(alice).create_for_comment(bobs_comment.id)
|
||||||
|
expect {
|
||||||
|
LikeService.new(alice).create_for_comment(bobs_comment.id)
|
||||||
}.to raise_error ActiveRecord::RecordInvalid
|
}.to raise_error ActiveRecord::RecordInvalid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#destroy" do
|
describe "#destroy" do
|
||||||
let(:like) { LikeService.new(bob).create(post.id) }
|
context "for post like" do
|
||||||
|
let(:like) { LikeService.new(bob).create_for_post(post.id) }
|
||||||
|
|
||||||
it "lets the user destroy their own like" do
|
it "lets the user destroy their own like" do
|
||||||
result = LikeService.new(bob).destroy(like.id)
|
result = LikeService.new(bob).destroy(like.id)
|
||||||
expect(result).to be_truthy
|
expect(result).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't let the parent author destroy others likes" do
|
||||||
|
result = LikeService.new(alice).destroy(like.id)
|
||||||
|
expect(result).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't let someone destroy others likes" do
|
||||||
|
result = LikeService.new(eve).destroy(like.id)
|
||||||
|
expect(result).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if the like doesn't exist" do
|
||||||
|
expect {
|
||||||
|
LikeService.new(bob).destroy("unknown id")
|
||||||
|
}.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't let the parent author destroy others likes" do
|
context "for comment like" do
|
||||||
result = LikeService.new(alice).destroy(like.id)
|
let(:like) { LikeService.new(bob).create_for_comment(alice_comment.id) }
|
||||||
expect(result).to be_falsey
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't let someone destroy others likes" do
|
it "let the user destroy its own comment like" do
|
||||||
result = LikeService.new(eve).destroy(like.id)
|
result = LikeService.new(bob).destroy(like.id)
|
||||||
expect(result).to be_falsey
|
expect(result).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
it "fails if the like doesn't exist" do
|
it "doesn't let the parent author destroy other comment likes" do
|
||||||
expect {
|
result = LikeService.new(alice).destroy(like.id)
|
||||||
LikeService.new(bob).destroy("unknown id")
|
expect(result).to be_falsey
|
||||||
}.to raise_error ActiveRecord::RecordNotFound
|
end
|
||||||
|
|
||||||
|
it "fails if the like doesn't exist" do
|
||||||
|
expect {
|
||||||
|
LikeService.new(alice).destroy("unknown id")
|
||||||
|
}.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -70,17 +132,17 @@ describe LikeService do
|
||||||
context "with user" do
|
context "with user" do
|
||||||
it "returns likes for a public post" do
|
it "returns likes for a public post" do
|
||||||
post = alice.post(:status_message, text: "hello", public: true)
|
post = alice.post(:status_message, text: "hello", public: true)
|
||||||
like = LikeService.new(alice).create(post.id)
|
like = LikeService.new(alice).create_for_post(post.id)
|
||||||
expect(LikeService.new(eve).find_for_post(post.id)).to include(like)
|
expect(LikeService.new(eve).find_for_post(post.id)).to include(like)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns likes for a visible private post" do
|
it "returns likes for a visible private post" do
|
||||||
like = LikeService.new(alice).create(post.id)
|
like = LikeService.new(alice).create_for_post(post.id)
|
||||||
expect(LikeService.new(bob).find_for_post(post.id)).to include(like)
|
expect(LikeService.new(bob).find_for_post(post.id)).to include(like)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't return likes for a private post the user can not see" do
|
it "doesn't return likes for a private post the user can not see" do
|
||||||
LikeService.new(alice).create(post.id)
|
LikeService.new(alice).create_for_post(post.id)
|
||||||
expect {
|
expect {
|
||||||
LikeService.new(eve).find_for_post(post.id)
|
LikeService.new(eve).find_for_post(post.id)
|
||||||
}.to raise_error ActiveRecord::RecordNotFound
|
}.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
@ -88,7 +150,7 @@ describe LikeService do
|
||||||
|
|
||||||
it "returns the user's like first" do
|
it "returns the user's like first" do
|
||||||
post = alice.post(:status_message, text: "hello", public: true)
|
post = alice.post(:status_message, text: "hello", public: true)
|
||||||
[alice, bob, eve].map {|user| LikeService.new(user).create(post.id) }
|
[alice, bob, eve].map {|user| LikeService.new(user).create_for_post(post.id) }
|
||||||
|
|
||||||
[alice, bob, eve].each do |user|
|
[alice, bob, eve].each do |user|
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -101,12 +163,12 @@ describe LikeService do
|
||||||
context "without user" do
|
context "without user" do
|
||||||
it "returns likes for a public post" do
|
it "returns likes for a public post" do
|
||||||
post = alice.post(:status_message, text: "hello", public: true)
|
post = alice.post(:status_message, text: "hello", public: true)
|
||||||
like = LikeService.new(alice).create(post.id)
|
like = LikeService.new(alice).create_for_post(post.id)
|
||||||
expect(LikeService.new.find_for_post(post.id)).to include(like)
|
expect(LikeService.new.find_for_post(post.id)).to include(like)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't return likes a for private post" do
|
it "doesn't return likes a for private post" do
|
||||||
LikeService.new(alice).create(post.id)
|
LikeService.new(alice).create_for_post(post.id)
|
||||||
expect {
|
expect {
|
||||||
LikeService.new.find_for_post(post.id)
|
LikeService.new.find_for_post(post.id)
|
||||||
}.to raise_error Diaspora::NonPublic
|
}.to raise_error Diaspora::NonPublic
|
||||||
|
|
@ -115,15 +177,68 @@ describe LikeService do
|
||||||
|
|
||||||
it "returns all likes of a post" do
|
it "returns all likes of a post" do
|
||||||
post = alice.post(:status_message, text: "hello", public: true)
|
post = alice.post(:status_message, text: "hello", public: true)
|
||||||
likes = [alice, bob, eve].map {|user| LikeService.new(user).create(post.id) }
|
likes = [alice, bob, eve].map {|user| LikeService.new(user).create_for_post(post.id) }
|
||||||
|
|
||||||
expect(LikeService.new.find_for_post(post.id)).to match_array(likes)
|
expect(LikeService.new.find_for_post(post.id)).to match_array(likes)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#find_for_comment" do
|
||||||
|
context "with user" do
|
||||||
|
it "returns likes for a public post comment" do
|
||||||
|
post = alice.post(:status_message, text: "hello", public: true)
|
||||||
|
comment = CommentService.new(bob).create(post.id, "Hello comment")
|
||||||
|
like = LikeService.new(alice).create_for_comment(comment.id)
|
||||||
|
expect(LikeService.new(eve).find_for_comment(comment.id)).to include(like)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns likes for visible private post comments" do
|
||||||
|
comment = CommentService.new(bob).create(post.id, "Hello comment")
|
||||||
|
like = LikeService.new(alice).create_for_comment(comment.id)
|
||||||
|
expect(LikeService.new(bob).find_for_comment(comment.id)).to include(like)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't return likes for a posts comment the user can not see" do
|
||||||
|
expect {
|
||||||
|
LikeService.new(eve).find_for_comment(alice_comment.id)
|
||||||
|
}.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the user's like first" do
|
||||||
|
post = alice.post(:status_message, text: "hello", public: true)
|
||||||
|
comment = CommentService.new(alice).create(post.id, "I like my own post")
|
||||||
|
|
||||||
|
[alice, bob, eve].map {|user| LikeService.new(user).create_for_comment(comment.id) }
|
||||||
|
[alice, bob, eve].each do |user|
|
||||||
|
expect(
|
||||||
|
LikeService.new(user).find_for_comment(comment.id).first.author.id
|
||||||
|
).to be user.person.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "without user" do
|
||||||
|
it "returns likes for a comment on a public post" do
|
||||||
|
post = alice.post(:status_message, text: "hello", public: true)
|
||||||
|
comment = CommentService.new(bob).create(post.id, "I like my own post")
|
||||||
|
like = LikeService.new(alice).create_for_comment(comment.id)
|
||||||
|
expect(
|
||||||
|
LikeService.new.find_for_comment(comment.id)
|
||||||
|
).to include(like)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't return likes for a private post comment" do
|
||||||
|
LikeService.new(alice).create_for_comment(alice_comment.id)
|
||||||
|
expect {
|
||||||
|
LikeService.new.find_for_comment(alice_comment.id)
|
||||||
|
}.to raise_error Diaspora::NonPublic
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#unlike_post" do
|
describe "#unlike_post" do
|
||||||
before do
|
before do
|
||||||
LikeService.new(alice).create(post.id)
|
LikeService.new(alice).create_for_post(post.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "removes the like to the post" do
|
it "removes the like to the post" do
|
||||||
|
|
@ -131,4 +246,16 @@ describe LikeService do
|
||||||
expect(post.likes.length).to eq(0)
|
expect(post.likes.length).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#unlike_comment" do
|
||||||
|
it "removes the like for a comment" do
|
||||||
|
comment = CommentService.new(alice).create(post.id, "I like my own post")
|
||||||
|
LikeService.new(alice).create_for_comment(comment.id)
|
||||||
|
expect(comment.likes.length).to eq(1)
|
||||||
|
|
||||||
|
LikeService.new(alice).unlike_comment(comment.id)
|
||||||
|
comment = CommentService.new(alice).find!(comment.id)
|
||||||
|
expect(comment.likes.length).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue