diff --git a/app/assets/images/admin_avatar.png b/app/assets/images/admin_avatar.png new file mode 100644 index 000000000..28ea4a143 Binary files /dev/null and b/app/assets/images/admin_avatar.png differ diff --git a/app/assets/images/moderator_avatar.png b/app/assets/images/moderator_avatar.png new file mode 100644 index 000000000..1c726e57b Binary files /dev/null and b/app/assets/images/moderator_avatar.png differ diff --git a/app/assets/stylesheets/debates.scss b/app/assets/stylesheets/debates.scss index dd29774a5..66ea491af 100644 --- a/app/assets/stylesheets/debates.scss +++ b/app/assets/stylesheets/debates.scss @@ -249,6 +249,7 @@ .panel { border: 0; + border-radius: 0; margin-bottom: 0; min-height: rem-calc(192); padding: 0 rem-calc(12) rem-calc(2) rem-calc(12); @@ -567,6 +568,15 @@ background: $comment-author; padding: rem-calc(6) rem-calc(12); } + + &.is-admin { + background: $comment-admin; + padding: rem-calc(6) rem-calc(12); + } + + &.is-moderator { + @extend .is-admin; + } } } diff --git a/app/assets/stylesheets/participacion.scss b/app/assets/stylesheets/participacion.scss index e6aea4db2..485dbfd60 100644 --- a/app/assets/stylesheets/participacion.scss +++ b/app/assets/stylesheets/participacion.scss @@ -548,19 +548,19 @@ form { line-height: $line-height; } - input[type]:not([type=submit]):not([type=file]) { + input[type]:not([type=submit]):not([type=file]):not([type=checkbox]) { background: $input-bg; height: rem-calc(48); margin-bottom: rem-calc(16); } - input[type="checkbox"] { - height: rem-calc(24) !important; - margin: 0 !important; + input[type="checkbox"] + label, + input[type="radio"] + label { + margin-right: 0; } input[type=file] { - margin: rem-calc(12) 0; + margin: rem-calc(12) 0 rem-calc(12) rem-calc(6); } .note { @@ -579,8 +579,7 @@ form { font-size: rem-calc(14); font-weight: normal; line-height: $line-height; - margin: 0 0 0 rem-calc(6); - vertical-align: top; + vertical-align: middle; } .captcha { @@ -591,6 +590,10 @@ form { margin-bottom: 0 !important; } } + + .button.margin-top { + margin-top: rem-calc(24); + } } // 09. Alerts @@ -689,11 +692,15 @@ form { } } -img.initialjs-avatar, img.avatar { +img.admin-avatar, img.moderator-avatar { border-radius: rem-calc(1000); position: relative; } +img.initialjs-avatar { + @extend .moderator-avatar; +} + .author-deleted { background-color: rgba(255,255,255,.5); color: rgba(0,0,0,.4); diff --git a/app/assets/stylesheets/variables.scss b/app/assets/stylesheets/variables.scss index 93107e3d2..4be7028e3 100644 --- a/app/assets/stylesheets/variables.scss +++ b/app/assets/stylesheets/variables.scss @@ -77,8 +77,9 @@ $author: #008CCF; $association: #C0392B; $comment-author: rgba(45,144,248,.15); -$comment-official: rgba(70,219,145,.3); $comment-level-5: rgba(255,241,204,1); +$comment-admin: rgba(45,144,248,.15); +$comment-official: rgba(70,219,145,.3); // 06. Responsive // - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 20df3bf2d..798196ca0 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -35,11 +35,20 @@ class CommentsController < ApplicationController private def comment_params - params.require(:comment).permit(:commentable_type, :commentable_id, :body) + params.require(:comment).permit(:commentable_type, :commentable_id, :body, :as_moderator, :as_administrator) end def build_comment @comment = Comment.build(debate, current_user, comment_params[:body]) + check_for_special_comments + end + + def check_for_special_comments + if administrator_comment? + @comment.administrator_id = current_user.administrator.id + elsif moderator_comment? + @comment.moderator_id = current_user.moderator.id + end end def debate @@ -62,4 +71,12 @@ class CommentsController < ApplicationController reply? && parent.author.email_on_comment_reply? end + def administrator_comment? + ["1", true].include?(comment_params[:as_administrator]) && can?(:comment_as_administrator, Debate) + end + + def moderator_comment? + ["1", true].include?(comment_params[:as_moderator]) && can?(:comment_as_moderator, Debate) + end + end diff --git a/app/models/ability.rb b/app/models/ability.rb index 9032038a1..a29f09a60 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -66,10 +66,15 @@ class Ability cannot :hide, User, id: user.id end + if user.moderator? + can :comment_as_moderator, [Debate, Comment] + end + if user.administrator? can :restore, Comment can :restore, Debate can :restore, User + can :comment_as_administrator, [Debate, Comment] end end end diff --git a/app/models/comment.rb b/app/models/comment.rb index 7c18efc6b..824f1dbf0 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,10 +1,11 @@ class Comment < ActiveRecord::Base include ActsAsParanoidAliases acts_as_nested_set scope: [:commentable_id, :commentable_type], counter_cache: :children_count - acts_as_paranoid column: :hidden_at acts_as_votable + attr_accessor :as_moderator, :as_administrator + validates :body, presence: true validates :user, presence: true @@ -59,6 +60,14 @@ class Comment < ActiveRecord::Base reviewed_at.present? end + def as_administrator? + administrator_id.present? + end + + def as_moderator? + moderator_id.present? + end + def mark_as_reviewed update(reviewed_at: Time.now) end diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb index 75cc9e6dd..ca7474ebd 100644 --- a/app/views/comments/_comment.html.erb +++ b/app/views/comments/_comment.html.erb @@ -5,50 +5,70 @@ <%= t("debates.comment.deleted") %> <% else %> - <% if comment.user.organization? %> - <%= image_tag("collective_avatar.png", size: 32, class: "avatar left") %> + <% if comment.as_administrator? %> + <%= image_tag("admin_avatar.png", size: 32, class: "admin-avatar left") %> + <% elsif comment.as_moderator? %> + <%= image_tag("moderator_avatar.png", size: 32, class: "moderator-avatar left") %> <% else %> - <%= avatar_image(comment.user, size: 32, class: "left") %> - <% end %> - <% if comment.user.hidden? %> - + + <% if comment.user.organization? %> + <%= image_tag("collective_avatar.png", size: 32, class: "avatar left") %> + <% else %> + <%= avatar_image(comment.user, size: 32, class: "left") %> + <% end %> + <% if comment.user.hidden? %> + + <% end %> + <% end %>
- <% if comment.user.hidden? %> - <%= t("debates.comment.user_deleted") %> + <% if comment.as_administrator? %> + <%= t("debates.comment.admin") %> #<%= comment.administrator_id%> + <% elsif comment.as_moderator? %> + <%= t("debates.comment.moderator") %> #<%= comment.moderator_id%> <% else %> - <%= comment.user.name %> - <% if comment.user.official? %> + + <% if comment.user.hidden? %> + <%= t("debates.comment.user_deleted") %> + <% else %> + <%= comment.user.name %> + <% if comment.user.official? %> +  •  + + <%= comment.user.official_position %> + + <% end %> + <% end %> + <% if comment.user.verified_organization? %>  •  - - <%= comment.user.official_position %> + + <%= t("shared.collective") %> <% end %> + <% if comment.user_id == @debate.author_id %> +  •  + + <%= t("debates.comment.author") %> + + <% end %> + <% end %> - <% if comment.user.verified_organization? %> -  •  - - <%= t("shared.collective") %> - - <% end %> - <% if comment.user_id == @debate.author_id %> -  •  - - <%= t("debates.comment.author") %> - - <% end %> +  • <%= time_ago_in_words(comment.created_at) %> <%= render 'comments/flag_as_inappropiate_actions', comment: comment %> -
- <% if comment.user.official? && comment.user_id == @debate.author_id %> + <% if comment.as_administrator? %> +

<%= comment.body %>

+ <% elsif comment.as_moderator? %> +

<%= comment.body %>

+ <% elsif comment.user.official? && comment.user_id == @debate.author_id %>

<%= comment.body %>

<% elsif comment.user.official? %>

<%= comment.body %>

@@ -81,6 +101,5 @@
<%= render comment.children.with_hidden.reorder('id DESC, lft') %>
-
diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb index 5e34a9f84..b73023c7a 100644 --- a/app/views/comments/_form.html.erb +++ b/app/views/comments/_form.html.erb @@ -4,6 +4,19 @@ <%= f.hidden_field :commentable_type, value: parent.class %> <%= f.hidden_field :commentable_id, value: parent.id %> - <%= f.submit comment_button_text(parent), class: "button radius small" %> + <%= f.submit comment_button_text(parent), class: "button radius small inline-block" %> + + <% if can? :comment_as_moderator, @debate %> +
+ <%= f.check_box :as_moderator, label: false %> + <%= f.label :as_moderator, t("comments.form.comment_as_moderator"), class: "checkbox" %> +
+ <% end %> + <% if can? :comment_as_administrator, @debate %> +
+ <%= f.check_box :as_administrator, label: false %> + <%= f.label :as_administrator, t("comments.form.comment_as_admin"), class: "checkbox" %> +
+ <% end %> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index b47d06c44..ce3339b25 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -49,6 +49,8 @@ en: other: "%{count} votes" comment: author: Debate's author + moderator: Moderator + admin: Administrator deleted: This comment has been deleted user_deleted: Deleted user responses: @@ -94,7 +96,9 @@ en: recommendation_three: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" comments: form: - leave_comment: Write a comment + leave_comment: Write a comment + comment_as_moderator: Comment as moderator + comment_as_admin: Comment as administrator comments_helper: comment_link: Comment comment_button: Publish comment diff --git a/config/locales/es.yml b/config/locales/es.yml index 2d6b7d987..1e2cca785 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -49,6 +49,8 @@ es: other: "%{count} votos" comment: author: Autor del debate + moderator: Moderador + admin: Administrador deleted: Este comentario ha sido eliminado user_deleted: Usuario eliminado responses: @@ -94,7 +96,9 @@ es: recommendation_three: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore" comments: form: - leave_comment: Deja tu comentario + leave_comment: Deja tu comentario + comment_as_moderator: Comentar como moderador + comment_as_admin: Comentar como administrador comments_helper: comment_link: Comentar comment_button: Publicar comentario diff --git a/db/migrate/20150824111844_add_moderator_id_to_comment.rb b/db/migrate/20150824111844_add_moderator_id_to_comment.rb new file mode 100644 index 000000000..014c3ecf3 --- /dev/null +++ b/db/migrate/20150824111844_add_moderator_id_to_comment.rb @@ -0,0 +1,5 @@ +class AddModeratorIdToComment < ActiveRecord::Migration + def change + add_column :comments, :moderator_id, :integer, default: nil + end +end diff --git a/db/migrate/20150824113326_add_administrator_id_to_comment.rb b/db/migrate/20150824113326_add_administrator_id_to_comment.rb new file mode 100644 index 000000000..7be8274cb --- /dev/null +++ b/db/migrate/20150824113326_add_administrator_id_to_comment.rb @@ -0,0 +1,5 @@ +class AddAdministratorIdToComment < ActiveRecord::Migration + def change + add_column :comments, :administrator_id, :integer, default: nil + end +end diff --git a/db/schema.rb b/db/schema.rb index 040d21d10..12e457a1c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150821180155) do +ActiveRecord::Schema.define(version: 20150824113326) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -52,6 +52,8 @@ ActiveRecord::Schema.define(version: 20150821180155) do t.datetime "flagged_as_inappropiate_at" t.integer "inappropiate_flags_count", default: 0 t.datetime "reviewed_at" + t.integer "moderator_id" + t.integer "administrator_id" end add_index "comments", ["commentable_id", "commentable_type"], name: "index_comments_on_commentable_id_and_commentable_type", using: :btree diff --git a/spec/features/comments_spec.rb b/spec/features/comments_spec.rb index ce5403a56..4ed16d915 100644 --- a/spec/features/comments_spec.rb +++ b/spec/features/comments_spec.rb @@ -168,4 +168,122 @@ feature 'Comments' do expect(InappropiateFlag.flagged?(user, comment)).to_not be end + feature "Moderators" do + scenario "can create comment as a moderator", :js do + moderator = create(:moderator) + debate = create(:debate) + + login_as(moderator.user) + visit debate_path(debate) + + fill_in "comment_body", with: "I am moderating!" + check "comment_as_moderator" + click_button "Publish comment" + + within "#comments" do + expect(page).to have_content "I am moderating!" + expect(page).to have_content "Moderator ##{moderator.id}" + expect(page).to have_css "p.is-moderator" + expect(page).to have_css "img.moderator-avatar" + end + end + + scenario "can create reply as a moderator", :js do + citizen = create(:user, username: "Ana") + manuela = create(:user, username: "Manuela") + moderator = create(:moderator, user: manuela) + debate = create(:debate) + comment = create(:comment, commentable: debate, user: citizen) + + login_as(manuela) + visit debate_path(debate) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment_body", with: "I am moderating!" + check "comment_as_moderator" + click_button 'Publish reply' + end + + within "#comment_#{comment.id}" do + expect(page).to have_content "I am moderating!" + expect(page).to have_content "Moderator ##{moderator.id}" + expect(page).to have_css "p.is-moderator" + expect(page).to have_css "img.moderator-avatar" + end + + expect(page).to_not have_selector("#js-comment-form-comment_#{comment.id}", visible: true) + end + + scenario "can not comment as an administrator" do + moderator = create(:moderator) + debate = create(:debate) + + login_as(moderator.user) + visit debate_path(debate) + + expect(page).to_not have_content "Comment as administrator" + end + end + + feature "Administrators" do + scenario "can create comment as an administrator", :js do + admin = create(:administrator) + debate = create(:debate) + + login_as(admin.user) + visit debate_path(debate) + + fill_in "comment_body", with: "I am your Admin!" + check "comment_as_administrator" + click_button "Publish comment" + + within "#comments" do + expect(page).to have_content "I am your Admin!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "p.is-admin" + expect(page).to have_css "img.admin-avatar" + end + end + + scenario "can create reply as an administrator", :js do + citizen = create(:user, username: "Ana") + manuela = create(:user, username: "Manuela") + admin = create(:administrator, user: manuela) + debate = create(:debate) + comment = create(:comment, commentable: debate, user: citizen) + + login_as(manuela) + visit debate_path(debate) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment_body", with: "Top of the world!" + check "comment_as_administrator" + click_button 'Publish reply' + end + + within "#comment_#{comment.id}" do + expect(page).to have_content "Top of the world!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "p.is-admin" + expect(page).to have_css "img.admin-avatar" + end + + expect(page).to_not have_selector("#js-comment-form-comment_#{comment.id}", visible: true) + end + + scenario "can not comment as a moderator" do + admin = create(:administrator) + debate = create(:debate) + + login_as(admin.user) + visit debate_path(debate) + + expect(page).to_not have_content "Comment as moderator" + end + end + end \ No newline at end of file diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 1ff4933b0..ff3e3800c 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -22,13 +22,15 @@ describe Ability do it { should be_able_to(:show, debate) } it { should be_able_to(:vote, debate) } - it { should be_able_to(:show, user) } it { should be_able_to(:edit, user) } it { should be_able_to(:create, Comment) } it { should be_able_to(:vote, Comment) } + it { should_not be_able_to(:comment_as_administrator, debate) } + it { should_not be_able_to(:comment_as_moderator, debate) } + describe 'flagging content as inappropiate' do it { should be_able_to(:flag_as_inappropiate, debate) } it { should_not be_able_to(:undo_flag_as_inappropiate, debate) } @@ -158,6 +160,9 @@ describe Ability do it { should_not be_able_to(:restore, comment) } it { should_not be_able_to(:restore, debate) } it { should_not be_able_to(:restore, other_user) } + + it { should be_able_to(:comment_as_moderator, debate) } + it { should_not be_able_to(:comment_as_administrator, debate) } end end @@ -173,5 +178,8 @@ describe Ability do it { should be_able_to(:restore, comment) } it { should be_able_to(:restore, debate) } it { should be_able_to(:restore, other_user) } + + it { should be_able_to(:comment_as_administrator, debate) } + it { should_not be_able_to(:comment_as_moderator, debate) } end end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index 5af7afc1c..98420c000 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -38,4 +38,24 @@ describe Comment do expect { new_comment.destroy }.to change { comment.children_count }.from(1).to(0) end end + + describe "#as_administrator?" do + it "should be true if comment has administrator_id, false otherway" do + expect(comment).not_to be_as_administrator + + comment.administrator_id = 33 + + expect(comment).to be_as_administrator + end + end + + describe "#as_moderator?" do + it "should be true if comment has moderator_id, false otherway" do + expect(comment).not_to be_as_moderator + + comment.moderator_id = 21 + + expect(comment).to be_as_moderator + end + end end