diff --git a/.rubocop.yml b/.rubocop.yml index e3306ee3d..0f69beccf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -73,6 +73,11 @@ Layout/HashAlignment: EnforcedHashRocketStyle: 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. Style/HashSyntax: EnforcedStyle: ruby19_no_mixed_keys diff --git a/Changelog.md b/Changelog.md index 69412a734..65cd65ae0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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) * 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) +* Re-introduce likes on comments [#8203](https://github.com/diaspora/diaspora/pull/8203) # 0.7.18.2 diff --git a/app/assets/javascripts/app/collections/comments.js b/app/assets/javascripts/app/collections/comments.js index 9ecf4f047..52f98d181 100644 --- a/app/assets/javascripts/app/collections/comments.js +++ b/app/assets/javascripts/app/collections/comments.js @@ -12,12 +12,17 @@ app.collections.Comments = Backbone.Collection.extend({ make : function(text) { 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({}, { url: "/posts/"+ this.post.id +"/comments", success: function() { 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); } }); diff --git a/app/assets/javascripts/app/collections/likes.js b/app/assets/javascripts/app/collections/likes.js index 76168237b..7f42f9eaf 100644 --- a/app/assets/javascripts/app/collections/likes.js +++ b/app/assets/javascripts/app/collections/likes.js @@ -4,7 +4,11 @@ app.collections.Likes = Backbone.Collection.extend({ model: app.models.Like, 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 diff --git a/app/assets/javascripts/app/models/comment.js b/app/assets/javascripts/app/models/comment.js index d382b731f..5b4b19598 100644 --- a/app/assets/javascripts/app/models/comment.js +++ b/app/assets/javascripts/app/models/comment.js @@ -1,6 +1,17 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later 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 diff --git a/app/assets/javascripts/app/models/like_interactions.js b/app/assets/javascripts/app/models/like_interactions.js new file mode 100644 index 000000000..4642d663b --- /dev/null +++ b/app/assets/javascripts/app/models/like_interactions.js @@ -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); + }}); + } +}); diff --git a/app/assets/javascripts/app/models/post.js b/app/assets/javascripts/app/models/post.js index b693d7b2d..3289fcc57 100644 --- a/app/assets/javascripts/app/models/post.js +++ b/app/assets/javascripts/app/models/post.js @@ -4,7 +4,7 @@ app.models.Post = Backbone.Model.extend(_.extend({}, app.models.formatDateMixin, urlRoot : "/posts", 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(); }, diff --git a/app/assets/javascripts/app/models/post/interactions.js b/app/assets/javascripts/app/models/post_interactions.js similarity index 61% rename from app/assets/javascripts/app/models/post/interactions.js rename to app/assets/javascripts/app/models/post_interactions.js index 572375c73..8380ba974 100644 --- a/app/assets/javascripts/app/models/post/interactions.js +++ b/app/assets/javascripts/app/models/post_interactions.js @@ -1,75 +1,27 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later -//require ../post - -app.models.Post.Interactions = Backbone.Model.extend({ - initialize : function(options){ +app.models.PostInteractions = app.models.LikeInteractions.extend({ + initialize: function(options) { + app.models.LikeInteractions.prototype.initialize.apply(this, arguments); this.post = options.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.comments = new app.collections.Comments(this.get("comments"), {post: this.post}); + this.reshares = new app.collections.Reshares(this.get("reshares"), {post: this.post}); }, - likesCount : function(){ - return this.get("likes_count"); - }, - - resharesCount : function(){ + resharesCount: function() { return this.get("reshares_count"); }, - commentsCount : function(){ + commentsCount: function() { return this.get("comments_count"); }, - userLike : function(){ - return this.likes.select(function(like){ - return like.get("author") && like.get("author").guid === app.currentUser.get("guid"); - })[0]; - }, - - userReshare : function(){ + userReshare: function() { return this.reshares.select(function(reshare){ return reshare.get("author") && reshare.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() { - 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) { var self = this; 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" , rootExists = (isReshare ? this.post.get("root") : true) , publicPost = this.post.get("public") diff --git a/app/assets/javascripts/app/views/comment_view.js b/app/assets/javascripts/app/views/comment_view.js index a75150290..f02258d60 100644 --- a/app/assets/javascripts/app/views/comment_view.js +++ b/app/assets/javascripts/app/views/comment_view.js @@ -6,35 +6,52 @@ app.views.Comment = app.views.Content.extend({ className : "comment media", tooltipSelector: "time", - events : function() { + subviews: { + ".likes-on-comment": "likesInfoView" + }, + + events: function() { return _.extend({}, app.views.Content.prototype.events, { "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.model.interactions.on("change", this.render, this); this.model.on("change", this.render, this); }, - presenter : function() { + presenter: function() { return _.extend(this.defaultPresenter(), { 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"); }, - postOwner : function() { + postOwner: function() { 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()); + }, + + toggleLike: function(evt) { + if (evt) { evt.preventDefault(); } + this.model.interactions.toggleLike(); + }, + + likesInfoView: function() { + return new app.views.LikesInfo({model: this.model}); } }); diff --git a/app/assets/javascripts/app/views/stream_post_views.js b/app/assets/javascripts/app/views/stream_post_views.js index c454462ac..9843874d2 100644 --- a/app/assets/javascripts/app/views/stream_post_views.js +++ b/app/assets/javascripts/app/views/stream_post_views.js @@ -7,7 +7,7 @@ app.views.StreamPost = app.views.Post.extend({ subviews : { ".feedback": "feedbackView", ".comments": "commentStreamView", - ".likes": "likesInfoView", + ".likes-on-post": "likesInfoView", ".reshares": "resharesInfoView", ".post-controls": "postControlsView", ".post-content": "postContentView", diff --git a/app/assets/javascripts/mobile/mobile_comments.js b/app/assets/javascripts/mobile/mobile_comments.js index 270799f7b..6eaf9ca61 100644 --- a/app/assets/javascripts/mobile/mobile_comments.js +++ b/app/assets/javascripts/mobile/mobile_comments.js @@ -52,6 +52,8 @@ $.post(form.attr("action") + "?format=mobile", form.serialize(), function(data){ Diaspora.Mobile.Comments.updateStream(form, data); + // Register new comments + $(".stream").trigger("comments.loaded"); }, "html").fail(function(response) { Diaspora.Mobile.Alert.handleAjaxError(response); Diaspora.Mobile.Comments.resetCommentBox(form); @@ -107,10 +109,12 @@ url: toggleReactionsLink.attr("href"), success: function (data) { toggleReactionsLink.addClass("active").removeClass("loading"); - $(data).insertAfter(bottomBar.children(".show-comments").first()); + $(data).insertAfter(bottomBar.children(".post-actions-container").first()); self.showCommentBox(commentActionLink); bottomBarContainer.getCommentsContainer().find("time.timeago").timeago(); bottomBarContainer.activate(); + // Inform the comment action for new comments + $(".stream").trigger("comments.loaded"); }, error: function(){ bottomBarContainer.deactivate(); diff --git a/app/assets/javascripts/mobile/mobile_post_actions.js b/app/assets/javascripts/mobile/mobile_post_actions.js index 76f1126d5..6698e6641 100644 --- a/app/assets/javascripts/mobile/mobile_post_actions.js +++ b/app/assets/javascripts/mobile/mobile_post_actions.js @@ -3,6 +3,11 @@ initialize: function() { $(".like-action", ".stream").bind("tap click", this.onLike); $(".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) { @@ -75,8 +80,8 @@ onLike: function(evt){ evt.preventDefault(); - var link = $(evt.target).closest(".like-action"), - likeCounter = $(evt.target).closest(".stream-element").find(".like-count"); + var link = $(evt.target).closest(".like-action").first(), + likeCounter = $(evt.target).find(".like-count").first(); if(!link.hasClass("loading") && link.hasClass("inactive")) { Diaspora.Mobile.PostActions.like(likeCounter, link); diff --git a/app/assets/stylesheets/comments.scss b/app/assets/stylesheets/comments.scss index 88d4b16b5..f47e0bae4 100644 --- a/app/assets/stylesheets/comments.scss +++ b/app/assets/stylesheets/comments.scss @@ -24,13 +24,9 @@ .comments > .comment, .comment.new-comment-form-wrapper { - .avatar { - height: 35px; - width: 35px; - } margin: 0; border-top: 1px dotted $border-grey; - padding: 10px 0; + padding: 10px 0 0; .info { margin-top: 5px; @@ -57,8 +53,6 @@ } } - .comment.new-comment-form-wrapper { padding-bottom: 0; } - .submit-button { margin-top: 10px; 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 { &:not(.open) .submit-button, &:not(.open) .md-header { diff --git a/app/assets/stylesheets/mobile/comments.scss b/app/assets/stylesheets/mobile/comments.scss index 62db27354..feed33d84 100644 --- a/app/assets/stylesheets/mobile/comments.scss +++ b/app/assets/stylesheets/mobile/comments.scss @@ -1,8 +1,5 @@ .bottom-bar { - border-radius: 0 0 5px 5px; z-index: 3; - display: block; - position: relative; padding: 8px 10px 10px; background: $background-grey; margin-top: 10px; @@ -10,6 +7,17 @@ min-height: 22px; 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, .show-comments, .show-comments > [class^="entypo"] { @@ -37,8 +45,7 @@ } } - .post-stats { - float: right; + %stats { position: relative; display: flex; @@ -46,9 +53,9 @@ color: $text-color; font-family: $font-family-base; font-size: $font-size-base; - line-height: 22px; + line-height: 24px; margin-left: 5px; - vertical-align: top; + vertical-align: text-bottom; z-index: 2; } @@ -67,17 +74,36 @@ } .entypo-reshare.active { color: $blue; } - .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; margin: 0 7px; .disabled { color: $medium-gray; } } + .post-action { + @extend %action; + } + + .comment-action { + @extend %action; + } + .add-comment-switcher { padding-top: 10px; } &.inactive { @@ -91,16 +117,19 @@ .stream-element .comments { margin: 0; - margin-top: 10px; padding: 0; width: 100%; .content { padding: 0; } .comment { - border-top: 1px solid $border-medium-grey; - padding: 10px 0 0; + background-color: $framed-background; + border: 1px solid $border-medium-grey; + border-radius: 5px; + margin-top: 10px; - &:first-child { padding-top: 20px; } + .media { + padding: 6px; + } } } diff --git a/app/assets/stylesheets/mobile/markdown_editor.scss b/app/assets/stylesheets/mobile/markdown_editor.scss index b39f5a0ba..2cc314d1e 100644 --- a/app/assets/stylesheets/mobile/markdown_editor.scss +++ b/app/assets/stylesheets/mobile/markdown_editor.scss @@ -1,5 +1,5 @@ .md-editor { - border: 1px solid $light-grey; + border: 1px solid $border-medium-grey; border-radius: $btn-border-radius-base; &.active { border-color: $text-grey; } diff --git a/app/assets/stylesheets/mobile/mobile.scss b/app/assets/stylesheets/mobile/mobile.scss index 2a88055e6..7ee53069e 100644 --- a/app/assets/stylesheets/mobile/mobile.scss +++ b/app/assets/stylesheets/mobile/mobile.scss @@ -516,9 +516,13 @@ h3.ltr { font-style: inherit; font-weight: inherit; margin: 0; - padding: 15px 15px; + padding: 12px; vertical-align: baseline; word-wrap: break-word; + + p:last-child { + margin: 0; + } } form#new_conversation.new_conversation { diff --git a/app/assets/stylesheets/single-post-view.scss b/app/assets/stylesheets/single-post-view.scss index ceaabc4fa..843cc33f0 100644 --- a/app/assets/stylesheets/single-post-view.scss +++ b/app/assets/stylesheets/single-post-view.scss @@ -116,9 +116,6 @@ .no-comments { text-align: center; } - a { - color: $link-color; - } .count { float: left; i { @@ -144,6 +141,7 @@ .comment.new-comment-form-wrapper { padding: 10px; } + .comments > .comment { padding-bottom: 0; } .count, .interaction-avatars { @@ -164,4 +162,19 @@ 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; + } } diff --git a/app/assets/stylesheets/stream_element.scss b/app/assets/stylesheets/stream_element.scss index 6f876ba15..24e1c5bcf 100644 --- a/app/assets/stylesheets/stream_element.scss +++ b/app/assets/stylesheets/stream_element.scss @@ -191,6 +191,16 @@ } } + .comments { + .likes { + line-height: 10px; + } + + .expand-likes { + line-height: 20px; + } + } + .status-message-location { color: $text-grey; font-size: $font-size-small; diff --git a/app/assets/templates/comment_tpl.jst.hbs b/app/assets/templates/comment_tpl.jst.hbs index 36d56ef1c..f515e0c45 100644 --- a/app/assets/templates/comment_tpl.jst.hbs +++ b/app/assets/templates/comment_tpl.jst.hbs @@ -36,5 +36,19 @@