diff --git a/app/assets/javascripts/comments.js b/app/assets/javascripts/comments.js new file mode 100644 index 000000000..62fb1c5c3 --- /dev/null +++ b/app/assets/javascripts/comments.js @@ -0,0 +1,136 @@ +function initCommentOptions(scrollableContainer, useParentOffset = true) { + scrollCommentOptions($(".dropdown-comment"), useParentOffset); + + document.addEventListener('scroll', function (event) { + var $target = $(event.target); + var parent = $(scrollableContainer); + + if ($target.length) { + scrollCommentOptions(parent.find(".dropdown-comment"), useParentOffset); + } + }, true); +} + +function scrollCommentOptions(selector, useParentOffset = true) { + _.each(selector, function(el) { + var $el = $(el); + var offset = useParentOffset ? $el.offset().top : $el.position().top; + $el.find(".dropdown-menu-fixed") + .offset({ top: (offset + 20) }); + }); +} + +function initDeleteComments(parent) { + $(parent).on("click", "[data-action=delete-comment]", function(e) { + var $this = $(this); + if (confirm($this.attr("data-confirm-message"))) { + $.ajax({ + url: $this.attr("data-url"), + type: "DELETE", + dataType: "json", + success: function(data) { + // There are 3 possible actions: + // - (A) comment is the last comment in project + // - (B) comment is the last comment inside specific date (remove the date separator) + // - (C) comment is a usual comment + + var commentEl = $this.closest(".comment"); + + // Case A + if (commentEl.prevAll(".comment").length == 0 && commentEl.next().length == 0) { + commentEl.after("
  • " + I18n.t("projects.index.no_comments") + "
  • "); + } + + // Case B + if (commentEl.prev(".comment-date-separator").length > 0 && commentEl.next(".comment").length == 0) { + commentEl.prev(".comment-date-separator").remove(); + } + commentEl.remove(); + + scrollCommentOptions($(parent).find(".dropdown-comment")); + }, + error: function(data) { + // Display alert + alert(data.responseJSON.message); + } + }); + } + }); +} + +function initEditComments(parent) { + $(parent).on("click", "[data-action=edit-comment]", function(e) { + var $this = $(this); + $.ajax({ + url: $this.attr("data-url"), + type: "GET", + dataType: "json", + success: function(data) { + var commentEl = $this.closest(".comment"); + var container = commentEl.find("[data-role=comment-message-container]"); + var oldMessage = container.find("[data-role=comment-message]"); + var optionsBtn = commentEl.find("[data-role=comment-options]"); + + // Hide old message, append new HTML + oldMessage.hide(); + optionsBtn.hide(); + container.append(data.html); + + var form = container.find("[data-role=edit-comment-message-form]"); + var input = form.find("input[data-role=message-input]"); + var submitBtn = form.find("[data-action=save]"); + var cancelBtn = form.find("[data-action=cancel]"); + + input.focus(); + + form + .on("ajax:send", function() { + input.attr("readonly", true); + }) + .on("ajax:success", function(e, data) { + var newMessage = input.val(); + oldMessage.html(newMessage); + + form.off("ajax:send ajax:success ajax:error ajax:complete"); + submitBtn.off("click"); + cancelBtn.off("click"); + form.remove(); + oldMessage.show(); + optionsBtn.show(); + }) + .on("ajax:error", function(ev, xhr) { + if (xhr.status === 422) { + var messageError = xhr.responseJSON.errors.message; + if (messageError) { + $(".form-group", form) + .addClass("has-error"); + $(".help-block", form) + .html(messageError[0]) + .removeClass("hide") + .after(" |"); + } + } + }) + .on("ajax:complete", function() { + input.attr("readonly", false).focus(); + }); + + submitBtn.on("click", function() { + form.submit(); + }); + + cancelBtn.on("click", function() { + form.off("ajax:send ajax:success ajax:error ajax:complete"); + submitBtn.off("click"); + cancelBtn.off("click"); + form.remove(); + oldMessage.show(); + optionsBtn.show(); + }); + }, + error: function(data) { + // TODO + } + }); + }); +} \ No newline at end of file diff --git a/app/assets/javascripts/my_modules/results.js b/app/assets/javascripts/my_modules/results.js index aa2c45a98..f71a5b832 100644 --- a/app/assets/javascripts/my_modules/results.js +++ b/app/assets/javascripts/my_modules/results.js @@ -1,3 +1,5 @@ +//= require comments + function initHandsOnTables(root) { root.find("div.hot-table").each(function() { var $container = $(this).find(".step-result-hot-table"); @@ -56,6 +58,9 @@ function initResultCommentForm($el) { $(".help-block", $form) .html("") .addClass("hide"); + scrollCommentOptions( + list.parent().find(".content-comments .dropdown-comment") + ); } }) .on("ajax:error", function (ev, xhr) { @@ -89,10 +94,13 @@ function initResultCommentsLink($el) { var listItem = moreBtn.parents('li'); $(data.html).insertBefore(listItem); if (data.results_number < data.per_page) { - moreBtn.remove(); + moreBtn.remove(); } else { - moreBtn.attr("href", data.more_url); + moreBtn.attr("href", data.more_url); } + + // Reposition dropdown comment options + scrollCommentOptions(listItem.closest(".content-comments").find(".dropdown-comment")); } }); } @@ -186,6 +194,10 @@ expandAllResults(); initTutorial(); applyCollapseLinkCallBack(); +initCommentOptions("ul.content-comments"); +initEditComments("#results"); +initDeleteComments("#results"); + $(function () { $("#results-collapse-btn").click(function () { $('.result .panel-collapse').collapse('hide'); diff --git a/app/assets/javascripts/projects/canvas.js b/app/assets/javascripts/projects/canvas.js index 6085c6d44..0252d9fd1 100644 --- a/app/assets/javascripts/projects/canvas.js +++ b/app/assets/javascripts/projects/canvas.js @@ -1,3 +1,5 @@ +//= require comments + //************************************ // CONSTANTS //************************************ @@ -321,6 +323,11 @@ function initializeFullZoom() { // Restore draggable position restoreDraggablePosition($("#diagram"), $("#canvas-container")); + + // Initialize comments + initCommentOptions("ul.content-comments", false); + initEditComments(".panel.module-large .tab-content"); + initDeleteComments(".panel.module-large .tab-content"); } function destroyFullZoom() { @@ -337,6 +344,13 @@ function destroyFullZoom() { $("li[data-module-id]").off("mouseenter mouseleave"); $("li[data-module-id] > span > a.canvas-center-on").off("click"); + // Clean up comments listeners + $(document).off("scroll"); + $(".panel.module-large .tab-content") + .off("click", "[data-action=edit-comment]"); + $(".panel.module-large .tab-content") + .off("click", "[data-action=delete-comment]"); + // Remember the draggable position rememberDraggablePosition($("#diagram"), $("#canvas-container")); } @@ -691,6 +705,10 @@ function bindFullZoomAjaxTabs() { $(".help-block", $form) .html("") .addClass("hide"); + scrollCommentOptions( + list.parent().find(".content-comments .dropdown-comment"), + false + ); } }) .on("ajax:error", function (ev, xhr) { @@ -728,6 +746,12 @@ function bindFullZoomAjaxTabs() { } else { moreBtn.attr("href", data.more_url); } + + // Reposition dropdown comment options + scrollCommentOptions( + listItem.closest(".content-comments").find(".dropdown-comment"), + false + ); } }); } diff --git a/app/assets/javascripts/projects/index.js b/app/assets/javascripts/projects/index.js index 746b790aa..ad1484605 100644 --- a/app/assets/javascripts/projects/index.js +++ b/app/assets/javascripts/projects/index.js @@ -7,6 +7,7 @@ // - refresh project users tab after manage user modal is closed // - refactor view handling using library, ex. backbone.js +//= require comments (function () { var newProjectModal = null; @@ -230,6 +231,9 @@ $(".help-block", $form) .html("") .addClass("hide"); + scrollCommentOptions( + list.parent().find(".content-comments .dropdown-comment") + ); } }) .on("ajax:error", function (ev, xhr) { @@ -267,6 +271,9 @@ } else { moreBtn.attr("href", data.more_url); } + + // Reposition dropdown comment options + scrollCommentOptions(listItem.closest(".content-comments").find(".dropdown-comment")); } }); } @@ -332,7 +339,6 @@ initUserRoleForms(); } - function init() { newProjectModal = $("#new-project-modal"); @@ -352,6 +358,9 @@ initNewProjectModal(); initEditProjectModal(); initManageUsersModal(); + initCommentOptions("ul.content-comments"); + initEditComments(".panel-project .tab-content"); + initDeleteComments(".panel-project .tab-content"); // initialize project tab remote loading $(".panel-project .panel-footer [role=tab]") diff --git a/app/assets/javascripts/protocols/steps.js b/app/assets/javascripts/protocols/steps.js index 8508b7809..2075521c6 100644 --- a/app/assets/javascripts/protocols/steps.js +++ b/app/assets/javascripts/protocols/steps.js @@ -3,6 +3,7 @@ //= require canvas-to-blob.min //= require direct-upload //= require assets +//= require comments // Sets callbacks for toggling checkboxes function applyCheckboxCallBack() { @@ -401,6 +402,9 @@ function initStepCommentForm($el) { $(".help-block", $form) .html("") .addClass("hide"); + scrollCommentOptions( + list.parent().find(".content-comments .dropdown-comment") + ); } }) .on("ajax:error", function (ev, xhr) { @@ -425,7 +429,6 @@ function initStepCommentForm($el) { // Initialize show more comments link. function initStepCommentsLink($el) { - $el.find(".btn-more-comments") .on("ajax:success", function (e, data) { if (data.html) { @@ -438,6 +441,10 @@ function initStepCommentsLink($el) { } else { moreBtn.attr("href", data.more_url); } + + // Reposition dropdown comment options + scrollCommentOptions(listItem.closest(".content-comments") + .find(".dropdown-comment")); } }); } @@ -645,6 +652,11 @@ initHandsOnTable($(document)); expandAllSteps(); setupAssetsLoading(); +// Init comments edit/delete +initCommentOptions("ul.content-comments"); +initEditComments("#steps"); +initDeleteComments("#steps"); + $(function () { $("[data-action='collapse-steps']").click(function () { diff --git a/app/assets/stylesheets/colors.scss b/app/assets/stylesheets/colors.scss index 1f6e5546b..d70a53e0d 100644 --- a/app/assets/stylesheets/colors.scss +++ b/app/assets/stylesheets/colors.scss @@ -6,6 +6,7 @@ $color-theme-dark: #6d6e71; // Grayscales $color-white: #fff; $color-alabaster: #fcfcfc; +$color-wild-sand: #f5f5f5; $color-concrete: #f2f2f2; $color-gallery: #EEE; $color-alto: #d2d2d2; diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index 138eeaeb1..80e4d5013 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -1168,4 +1168,30 @@ html.turbolinks-progress-bar::before { height: 20px; display: inline-block; margin-right: 5px; +} + +// Comments + +.dropdown.dropdown-comment { + display: inline-block; + + [data-role="comment-options"] { + color: $color-emperor; + } +} + +.dropdown-menu.dropdown-menu-fixed { + left: auto; + position: fixed; + top: auto; + + li:not(.dropdown-header):hover { + background-color: $color-wild-sand; + } +} + +.edit-comment-form { + .help-block { + display: inline; + } } \ No newline at end of file diff --git a/app/controllers/my_module_comments_controller.rb b/app/controllers/my_module_comments_controller.rb index ae755fa3d..46c6342c0 100644 --- a/app/controllers/my_module_comments_controller.rb +++ b/app/controllers/my_module_comments_controller.rb @@ -2,6 +2,8 @@ class MyModuleCommentsController < ApplicationController before_action :load_vars before_action :check_view_permissions, only: [ :index ] before_action :check_add_permissions, only: [ :new, :create ] + before_action :check_edit_permissions, only: [:edit, :update] + before_action :check_destroy_permissions, only: [:destroy] def index @comments = @my_module.last_comments(@last_comment_id, @per_page) @@ -48,6 +50,19 @@ class MyModuleCommentsController < ApplicationController respond_to do |format| if (@comment.valid? && @my_module.comments << @comment) + # Generate activity + Activity.create( + type_of: :add_comment_to_module, + user: current_user, + project: @my_module.experiment.project, + my_module: @my_module, + message: t( + 'activities.add_comment_to_module', + user: current_user.full_name, + module: @my_module.name + ) + ) + format.html { flash[:success] = t( "my_module_comments.create.success_flash", @@ -77,6 +92,71 @@ class MyModuleCommentsController < ApplicationController end end + def edit + @update_url = + my_module_my_module_comment_path(@my_module, @comment, format: :json) + respond_to do |format| + format.json do + render json: { + html: render_to_string( + partial: '/comments/edit.html.erb' + ) + } + end + end + end + + def update + @comment.message = comment_params[:message] + respond_to do |format| + format.json do + if @comment.save + # Generate activity + Activity.create( + type_of: :edit_module_comment, + user: current_user, + project: @my_module.experiment.project, + my_module: @my_module, + message: t( + 'activities.edit_module_comment', + user: current_user.full_name, + module: @my_module.name + ) + ) + render json: {}, status: :ok + else + render json: { errors: @comment.errors.to_hash(true) }, + status: :unprocessable_entity + end + end + end + end + + def destroy + respond_to do |format| + format.json do + if @comment.destroy + # Generate activity + Activity.create( + type_of: :delete_module_comment, + user: current_user, + project: @my_module.experiment.project, + my_module: @my_module, + message: t( + 'activities.delete_module_comment', + user: current_user.full_name, + module: @my_module.name + ) + ) + render json: {}, status: :ok + else + render json: { message: I18n.t('comments.delete_error') }, + status: :unprocessable_entity + end + end + end + end + private def load_vars @@ -101,6 +181,15 @@ class MyModuleCommentsController < ApplicationController end end + def check_edit_permissions + @comment = Comment.find_by_id(params[:id]) + render_403 unless @comment.present? && can_edit_module_comment(@comment) + end + + def check_destroy_permissions + @comment = Comment.find_by_id(params[:id]) + render_403 unless @comment.present? && can_delete_module_comment(@comment) + end def comment_params params.require(:comment).permit(:message) diff --git a/app/controllers/project_comments_controller.rb b/app/controllers/project_comments_controller.rb index 3a868d2e6..2d32abcab 100644 --- a/app/controllers/project_comments_controller.rb +++ b/app/controllers/project_comments_controller.rb @@ -2,6 +2,8 @@ class ProjectCommentsController < ApplicationController before_action :load_vars before_action :check_view_permissions, only: [ :index ] before_action :check_add_permissions, only: [ :new, :create ] + before_action :check_edit_permissions, only: [:edit, :update] + before_action :check_destroy_permissions, only: [:destroy] def index @comments = @project.last_comments(@last_comment_id, @per_page) @@ -48,6 +50,18 @@ class ProjectCommentsController < ApplicationController respond_to do |format| if (@comment.valid? && @project.comments << @comment) + # Generate activity + Activity.create( + type_of: :add_comment_to_project, + user: current_user, + project: @project, + message: t( + 'activities.add_comment_to_project', + user: current_user.full_name, + project: @project.name + ) + ) + format.html { flash[:success] = t( "project_comments.create.success_flash", @@ -76,6 +90,69 @@ class ProjectCommentsController < ApplicationController end end + def edit + @update_url = + project_project_comment_path(@project, @comment, format: :json) + respond_to do |format| + format.json do + render json: { + html: render_to_string( + partial: '/comments/edit.html.erb' + ) + } + end + end + end + + def update + @comment.message = comment_params[:message] + respond_to do |format| + format.json do + if @comment.save + # Generate activity + Activity.create( + type_of: :edit_project_comment, + user: current_user, + project: @project, + message: t( + 'activities.edit_project_comment', + user: current_user.full_name, + project: @project.name + ) + ) + render json: {}, status: :ok + else + render json: { errors: @comment.errors.to_hash(true) }, + status: :unprocessable_entity + end + end + end + end + + def destroy + respond_to do |format| + format.json do + if @comment.destroy + # Generate activity + Activity.create( + type_of: :delete_project_comment, + user: current_user, + project: @project, + message: t( + 'activities.delete_project_comment', + user: current_user.full_name, + project: @project.name + ) + ) + render json: {}, status: :ok + else + render json: { message: I18n.t('comments.delete_error') }, + status: :unprocessable_entity + end + end + end + end + private def load_vars @@ -100,6 +177,16 @@ class ProjectCommentsController < ApplicationController end end + def check_edit_permissions + @comment = Comment.find_by_id(params[:id]) + render_403 unless @comment.present? && can_edit_project_comment(@comment) + end + + def check_destroy_permissions + @comment = Comment.find_by_id(params[:id]) + render_403 unless @comment.present? && can_delete_project_comment(@comment) + end + def comment_params params.require(:comment).permit(:message) end diff --git a/app/controllers/result_comments_controller.rb b/app/controllers/result_comments_controller.rb index 8285a0b60..63d311447 100644 --- a/app/controllers/result_comments_controller.rb +++ b/app/controllers/result_comments_controller.rb @@ -3,6 +3,8 @@ class ResultCommentsController < ApplicationController before_action :check_view_permissions, only: [ :index ] before_action :check_add_permissions, only: [ :new, :create ] + before_action :check_edit_permissions, only: [:edit, :update] + before_action :check_destroy_permissions, only: [:destroy] def index @comments = @result.last_comments(@last_comment_id, @per_page) @@ -54,7 +56,7 @@ class ResultCommentsController < ApplicationController Activity.create( type_of: :add_comment_to_result, user: current_user, - project: @result.my_module.project, + project: @result.my_module.experiment.project, my_module: @result.my_module, message: t( "activities.add_comment_to_result", @@ -91,6 +93,71 @@ class ResultCommentsController < ApplicationController end end + def edit + @update_url = + result_result_comment_path(@result, @comment, format: :json) + respond_to do |format| + format.json do + render json: { + html: render_to_string( + partial: '/comments/edit.html.erb' + ) + } + end + end + end + + def update + @comment.message = comment_params[:message] + respond_to do |format| + format.json do + if @comment.save + # Generate activity + Activity.create( + type_of: :edit_result_comment, + user: current_user, + project: @result.my_module.experiment.project, + my_module: @result.my_module, + message: t( + 'activities.edit_result_comment', + user: current_user.full_name, + result: @result.name + ) + ) + render json: {}, status: :ok + else + render json: { errors: @comment.errors.to_hash(true) }, + status: :unprocessable_entity + end + end + end + end + + def destroy + respond_to do |format| + format.json do + if @comment.destroy + # Generate activity + Activity.create( + type_of: :delete_result_comment, + user: current_user, + project: @result.my_module.experiment.project, + my_module: @result.my_module, + message: t( + 'activities.delete_result_comment', + user: current_user.full_name, + result: @result.name + ) + ) + render json: {}, status: :ok + else + render json: { message: I18n.t('comments.delete_error') }, + status: :unprocessable_entity + end + end + end + end + private def load_vars @@ -116,6 +183,18 @@ class ResultCommentsController < ApplicationController end end + def check_edit_permissions + @comment = Comment.find_by_id(params[:id]) + render_403 unless @comment.present? && + can_edit_result_comment_in_module(@comment) + end + + def check_destroy_permissions + @comment = Comment.find_by_id(params[:id]) + render_403 unless @comment.present? && + can_delete_result_comment_in_module(@comment) + end + def comment_params params.require(:comment).permit(:message) end diff --git a/app/controllers/step_comments_controller.rb b/app/controllers/step_comments_controller.rb index 3acfefacb..fad1c454d 100644 --- a/app/controllers/step_comments_controller.rb +++ b/app/controllers/step_comments_controller.rb @@ -3,6 +3,8 @@ class StepCommentsController < ApplicationController before_action :check_view_permissions, only: [ :index ] before_action :check_add_permissions, only: [ :new, :create ] + before_action :check_edit_permissions, only: [:edit, :update] + before_action :check_destroy_permissions, only: [:destroy] def index @comments = @step.last_comments(@last_comment_id, @per_page) @@ -96,6 +98,76 @@ class StepCommentsController < ApplicationController end end + def edit + @update_url = step_step_comment_path(@step, @comment, format: :json) + respond_to do |format| + format.json do + render json: { + html: render_to_string( + partial: '/comments/edit.html.erb' + ) + } + end + end + end + + def update + @comment.message = comment_params[:message] + respond_to do |format| + format.json do + if @comment.save + # Generate activity + if @protocol.in_module? + Activity.create( + type_of: :edit_step_comment, + user: current_user, + project: @step.my_module.experiment.project, + my_module: @step.my_module, + message: t( + 'activities.edit_step_comment', + user: current_user.full_name, + step: @step.position + 1, + step_name: @step.name + ) + ) + end + render json: {}, status: :ok + else + render json: { errors: @comment.errors.to_hash(true) }, + status: :unprocessable_entity + end + end + end + end + + def destroy + respond_to do |format| + format.json do + if @comment.destroy + # Generate activity + if @protocol.in_module? + Activity.create( + type_of: :delete_step_comment, + user: current_user, + project: @step.my_module.experiment.project, + my_module: @step.my_module, + message: t( + 'activities.delete_step_comment', + user: current_user.full_name, + step: @step.position + 1, + step_name: @step.name + ) + ) + end + render json: {}, status: :ok + else + render json: { message: I18n.t('comments.delete_error') }, + status: :unprocessable_entity + end + end + end + end + private def load_vars @@ -121,8 +193,19 @@ class StepCommentsController < ApplicationController end end + def check_edit_permissions + @comment = Comment.find_by_id(params[:id]) + render_403 unless @comment.present? && + can_edit_step_comment_in_protocol(@comment) + end + + def check_destroy_permissions + @comment = Comment.find_by_id(params[:id]) + render_403 unless @comment.present? && + can_delete_step_comment_in_protocol(@comment) + end + def comment_params params.require(:comment).permit(:message) end - end diff --git a/app/helpers/permission_helper.rb b/app/helpers/permission_helper.rb index 1fd6cf0ab..63aa79059 100644 --- a/app/helpers/permission_helper.rb +++ b/app/helpers/permission_helper.rb @@ -302,6 +302,22 @@ module PermissionHelper is_technician_or_higher_of_project(project) end + def can_edit_project_comment(comment) + comment.project_comment.present? && + ( + comment.user == current_user || + is_owner_of_project(comment.project_comment.project) + ) + end + + def can_delete_project_comment(comment) + comment.project_comment.present? && + ( + comment.user == current_user || + is_owner_of_project(comment.project_comment.project) + ) + end + def can_restore_archived_modules(project) is_user_or_higher_of_project(project) end @@ -475,6 +491,26 @@ module PermissionHelper is_technician_or_higher_of_project(my_module.experiment.project) end + def can_edit_module_comment(comment) + comment.my_module_comment.present? && + ( + comment.user == current_user || + is_owner_of_project( + comment.my_module_comment.my_module.experiment.project + ) + ) + end + + def can_delete_module_comment(comment) + comment.my_module_comment.present? && + ( + comment.user == current_user || + is_owner_of_project( + comment.my_module_comment.my_module.experiment.project + ) + ) + end + def can_view_module_samples(my_module) can_view_module(my_module) and can_view_samples(my_module.experiment.project.organization) @@ -502,6 +538,26 @@ module PermissionHelper is_technician_or_higher_of_project(my_module.experiment.project) end + def can_edit_result_comment_in_module(comment) + comment.result_comment.present? && + ( + comment.user == current_user || + is_owner_of_project( + comment.result_comment.result.my_module.experiment.project + ) + ) + end + + def can_delete_result_comment_in_module(comment) + comment.result_comment.present? && + ( + comment.user == current_user || + is_owner_of_project( + comment.result_comment.result.my_module.experiment.project + ) + ) + end + # ---- RESULT TEXT PERMISSIONS ---- def can_create_result_text_in_module(my_module) @@ -874,6 +930,34 @@ module PermissionHelper end end + def can_edit_step_comment_in_protocol(comment) + return false if comment.step_comment.blank? + + protocol = comment.step_comment.step.protocol + if protocol.in_module? + comment.user == current_user || + is_owner_of_project( + protocol.my_module.experiment.project + ) + else + false + end + end + + def can_delete_step_comment_in_protocol(comment) + return false if comment.step_comment.blank? + + protocol = comment.step_comment.step.protocol + if protocol.in_module? + comment.user == current_user || + is_owner_of_project( + protocol.my_module.experiment.project + ) + else + false + end + end + def can_view_or_download_step_assets(protocol) if protocol.in_module? my_module = protocol.my_module diff --git a/app/models/activity.rb b/app/models/activity.rb index 506fbb0bc..c0a088530 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -28,7 +28,17 @@ class Activity < ActiveRecord::Base :archive_result, :edit_result, :clone_experiment, - :move_experiment + :move_experiment, + :add_comment_to_project, + :edit_project_comment, + :delete_project_comment, + :add_comment_to_module, + :edit_module_comment, + :delete_module_comment, + :edit_step_comment, + :delete_step_comment, + :edit_result_comment, + :delete_result_comment ] validates :type_of, presence: true diff --git a/app/models/comment.rb b/app/models/comment.rb index 1d2ed2dcf..7f6de1029 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -11,11 +11,11 @@ class Comment < ActiveRecord::Base belongs_to :user, inverse_of: :comments belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User' - has_one :step_comment, inverse_of: :comment - has_one :my_module_comment, inverse_of: :comment - has_one :result_comment, inverse_of: :comment - has_one :sample_comment, inverse_of: :comment - has_one :project_comment, inverse_of: :comment + has_one :step_comment, inverse_of: :comment, dependent: :destroy + has_one :my_module_comment, inverse_of: :comment, dependent: :destroy + has_one :result_comment, inverse_of: :comment, dependent: :destroy + has_one :sample_comment, inverse_of: :comment, dependent: :destroy + has_one :project_comment, inverse_of: :comment, dependent: :destroy def self.search( user, diff --git a/app/models/result_comment.rb b/app/models/result_comment.rb index 35821d417..a0a1e7f28 100644 --- a/app/models/result_comment.rb +++ b/app/models/result_comment.rb @@ -4,6 +4,5 @@ class ResultComment < ActiveRecord::Base belongs_to :result, inverse_of: :result_comments belongs_to :comment, - inverse_of: :result_comment, - dependent: :destroy + inverse_of: :result_comment end diff --git a/app/views/comments/_edit.html.erb b/app/views/comments/_edit.html.erb new file mode 100644 index 000000000..696bc76f4 --- /dev/null +++ b/app/views/comments/_edit.html.erb @@ -0,0 +1,14 @@ +<%= bootstrap_form_for(@comment, url: @update_url, remote: true, html: { method: :put, class: 'edit-comment-form' }, data: { role: 'edit-comment-message-form' }) do |f| %> +
    +
    + <%= f.text_field :message, autofocus: true, hide_label: true, data: { role: 'message-input' }, value: @comment.message %> + + + + + +
    + + <%= t('general.cancel') %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/my_module_comments/_comment.html.erb b/app/views/my_module_comments/_comment.html.erb index 95d970ced..5694e8639 100644 --- a/app/views/my_module_comments/_comment.html.erb +++ b/app/views/my_module_comments/_comment.html.erb @@ -1,3 +1,43 @@ -<%= l comment.created_at, format: '%H:%M' %> +
    + <%= l comment.created_at, format: '%H:%M' %> + <% if can_edit_module_comment(comment) || can_delete_module_comment(comment) %> + + <% end %> +
    <%= comment.user.full_name %>: -

    <%= comment.message %>

    \ No newline at end of file +
    +

    <%= comment.message %>

    +
    diff --git a/app/views/my_module_comments/_index.html.erb b/app/views/my_module_comments/_index.html.erb index aff54c799..393a55b45 100644 --- a/app/views/my_module_comments/_index.html.erb +++ b/app/views/my_module_comments/_index.html.erb @@ -7,7 +7,7 @@ <%= render 'my_module_comments/list.html.erb', comments: @comments %> <% end %> <% if @comments.length == @per_page %> -
  • +
  • <%=t "experiments.canvas.popups.more_comments" %> diff --git a/app/views/my_module_comments/_list.html.erb b/app/views/my_module_comments/_list.html.erb index f4224e036..832477c3a 100644 --- a/app/views/my_module_comments/_list.html.erb +++ b/app/views/my_module_comments/_list.html.erb @@ -2,11 +2,14 @@ <% current_day = DateTime.current.strftime('%j').to_i %> <% comments.each do |comment| %> -
  • <% comment_day = comment.created_at.strftime('%j').to_i %> <% if comment_day < current_day and comment_day < day %> <% day = comment.created_at.strftime('%j').to_i %> -

    <%= comment.created_at.strftime('%d.%m.%Y') %>

    +
  • +

    <%= comment.created_at.strftime('%d.%m.%Y') %>

    +
  • <% end %> - <%= render 'project_comments/comment.html.erb', comment: comment %> +
  • + <%= render 'my_module_comments/comment.html.erb', comment: comment %> +
  • <% end %> diff --git a/app/views/project_comments/_comment.html.erb b/app/views/project_comments/_comment.html.erb index adf691da0..e135c1b82 100644 --- a/app/views/project_comments/_comment.html.erb +++ b/app/views/project_comments/_comment.html.erb @@ -1,3 +1,43 @@ -<%= l comment.created_at, format: '%H:%M' %> +
    + <%= l comment.created_at, format: '%H:%M' %> + <% if can_edit_project_comment(comment) || can_delete_project_comment(comment) %> + + <% end %> +
    <%= comment.user.full_name %>: -

    <%= comment.message %>

    +
    +

    <%= comment.message %>

    +
    diff --git a/app/views/project_comments/_index.html.erb b/app/views/project_comments/_index.html.erb index 51894d7e6..42e665ec6 100644 --- a/app/views/project_comments/_index.html.erb +++ b/app/views/project_comments/_index.html.erb @@ -7,7 +7,7 @@ <%= render 'project_comments/list.html.erb', comments: @comments %> <% end %> <% if @comments.length == @per_page %> -
  • +
  • <%= t'projects.index.more_comments' %>
  • diff --git a/app/views/project_comments/_list.html.erb b/app/views/project_comments/_list.html.erb index 68e17ebd8..4653f2689 100644 --- a/app/views/project_comments/_list.html.erb +++ b/app/views/project_comments/_list.html.erb @@ -3,14 +3,17 @@ current_day = DateTime.current.strftime('%j').to_i %> <% comments.each do |comment| %> -
  • <% comment_day = comment.created_at.strftime('%j').to_i - if comment_day < current_day and comment_day < day then + if comment_day < current_day and comment_day < day then day = comment.created_at.strftime('%j').to_i %> -

    <%= comment.created_at.strftime('%d.%m.%Y') %>

    +
  • +

    <%= comment.created_at.strftime('%d.%m.%Y') %>

    +
  • <% end %> - <%= render 'project_comments/comment.html.erb', comment: comment %> +
  • + <%= render 'project_comments/comment.html.erb', comment: comment %> +
  • <% end %> diff --git a/app/views/result_comments/_comment.html.erb b/app/views/result_comments/_comment.html.erb index 74715974a..aa7e3ec4b 100644 --- a/app/views/result_comments/_comment.html.erb +++ b/app/views/result_comments/_comment.html.erb @@ -1,2 +1,44 @@ -<%=t "my_modules.results.comment_title", user: comment.user.full_name, time: l(comment.created_at, format: :time) %> -

    <%= comment.message %>

    +
    + + <%=t "my_modules.results.comment_title", user: comment.user.full_name, time: l(comment.created_at, format: :time) %> + + <% if can_edit_result_comment_in_module(comment) || can_delete_result_comment_in_module(comment) %> + + <% end %> +
    +
    +

    <%= comment.message %>

    +
    \ No newline at end of file diff --git a/app/views/result_comments/_index.html.erb b/app/views/result_comments/_index.html.erb index d840a32f7..6e9ec19f8 100644 --- a/app/views/result_comments/_index.html.erb +++ b/app/views/result_comments/_index.html.erb @@ -7,7 +7,7 @@ <%= render 'result_comments/list.html.erb', comments: @comments %> <% end %> <% if @comments.length == @per_page %> -
  • +
  • <%=t "general.more_comments" %> diff --git a/app/views/result_comments/_list.html.erb b/app/views/result_comments/_list.html.erb index 1d2941643..e427ad847 100644 --- a/app/views/result_comments/_list.html.erb +++ b/app/views/result_comments/_list.html.erb @@ -2,11 +2,14 @@ <% current_day = DateTime.current.strftime('%j').to_i %> <% comments.each do |comment| %> -
  • <% comment_day = comment.created_at.strftime('%j').to_i %> <% if comment_day < current_day and comment_day < day %> <% day = comment.created_at.strftime('%j').to_i %> -

    <%= comment.created_at.strftime('%d.%m.%Y') %>

    +
  • +

    <%= comment.created_at.strftime('%d.%m.%Y') %>

    +
  • <% end %> - <%= render 'result_comments/comment.html.erb', comment: comment %> +
  • + <%= render 'result_comments/comment.html.erb', comment: comment %> +
  • <% end %> diff --git a/app/views/step_comments/_comment.html.erb b/app/views/step_comments/_comment.html.erb index 7a7516ad3..3ea3e1a53 100644 --- a/app/views/step_comments/_comment.html.erb +++ b/app/views/step_comments/_comment.html.erb @@ -1,2 +1,44 @@ -<%=t "protocols.steps.comment_title", user: comment.user.full_name, time: l(comment.created_at, format: :time) %> -

    <%= comment.message %>

    +
    + + <%=t "protocols.steps.comment_title", user: comment.user.full_name, time: l(comment.created_at, format: :time) %> + + <% if can_edit_step_comment_in_protocol(comment) || can_delete_step_comment_in_protocol(comment) %> + + <% end %> +
    +
    +

    <%= comment.message %>

    +
    \ No newline at end of file diff --git a/app/views/step_comments/_index.html.erb b/app/views/step_comments/_index.html.erb index 86c1e3095..e8ae8b27d 100644 --- a/app/views/step_comments/_index.html.erb +++ b/app/views/step_comments/_index.html.erb @@ -7,7 +7,7 @@ <%= render 'step_comments/list.html.erb', comments: @comments %> <% end %> <% if @comments.length == @per_page %> -
  • +
  • <%=t "general.more_comments" %> diff --git a/app/views/step_comments/_list.html.erb b/app/views/step_comments/_list.html.erb index 19683d30d..4a84127ab 100644 --- a/app/views/step_comments/_list.html.erb +++ b/app/views/step_comments/_list.html.erb @@ -2,11 +2,14 @@ <% current_day = DateTime.current.strftime('%j').to_i %> <% comments.each do |comment| %> -
  • <% comment_day = comment.created_at.strftime('%j').to_i %> <% if comment_day < current_day and comment_day < day %> <% day = comment.created_at.strftime('%j').to_i %> -

    <%= comment.created_at.strftime('%d.%m.%Y') %>

    +
  • +

    <%= comment.created_at.strftime('%d.%m.%Y') %>

    +
  • <% end %> - <%= render 'step_comments/comment.html.erb', comment: comment %> +
  • + <%= render 'step_comments/comment.html.erb', comment: comment %> +
  • <% end %> diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index f6132277f..b53673f2c 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -56,4 +56,5 @@ Rails.application.config.assets.precompile += %w( Sortable.min.js ) Rails.application.config.assets.precompile += %w( reports_pdf.css ) Rails.application.config.assets.precompile += %w( jszip.min.js ) Rails.application.config.assets.precompile += %w( assets.js ) +Rails.application.config.assets.precompile += %w( comments.js ) Rails.application.config.assets.precompile += %w( projects/show.js ) diff --git a/config/locales/en.yml b/config/locales/en.yml index 50d785314..ad575e331 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -139,6 +139,14 @@ en: step: "Step comment" result: "Result comment" + comments: + options_dropdown: + header: "Comment options" + edit: "Edit" + delete: "Delete" + delete_confirm: "Are you sure you wish to delete this comment?" + delete_error: "Error occured while deleting comment." + projects: index: head_title: "Home" @@ -1024,6 +1032,16 @@ en: edit_table_result: "%{user} edited table result %{result}." clone_experiment: "%{user} cloned %{experiment_new} from %{experiment_original}." move_experiment: "%{user} moved experiment %{experiment} from project %{project_original} to project %{project_new}." + add_comment_to_project: "%{user} commented on project %{project}." + edit_project_comment: "%{user} edited comment on project %{project}." + delete_project_comment: "%{user} deleted comment on project %{project}." + add_comment_to_module: "%{user} commented on module %{module}." + edit_module_comment: "%{user} edited comment on module %{module}." + delete_module_comment: "%{user} deleted comment on module %{module}." + edit_step_comment: "%{user} edited comment on Step %{step} %{step_name}." + delete_step_comment: "%{user} deleted comment on Step %{step} %{step_name}." + edit_result_comment: "%{user} edited comment on result %{result}." + delete_result_comment: "%{user} deleted comment on result %{result}." user_my_modules: new: diff --git a/config/routes.rb b/config/routes.rb index 7d53e1c61..aa164cc39 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -49,7 +49,9 @@ Rails.application.routes.draw do resources :projects, except: [:new, :destroy] do resources :user_projects, path: "/users", only: [:new, :create, :index, :edit, :update, :destroy] - resources :project_comments, path: "/comments", only: [:new, :create, :index] + resources :project_comments, + path: '/comments', + only: [:new, :create, :index, :edit, :update, :destroy] # Activities popup (JSON) for individual project in projects index, # as well as all activities page for single project (HTML) resources :project_activities, path: "/activities", only: [:index] @@ -145,7 +147,9 @@ Rails.application.routes.draw do resources :my_modules, path: "/modules", only: [:show, :edit, :update, :destroy] do resources :my_module_tags, path: "/tags", only: [:index, :create, :update, :destroy] resources :user_my_modules, path: "/users", only: [:index, :new, :create, :destroy] - resources :my_module_comments, path: "/comments", only: [:index, :new, :create] + resources :my_module_comments, + path: '/comments', + only: [:index, :new, :create, :edit, :update, :destroy] resources :sample_my_modules, path: "/samples_index", only: [:index] resources :result_texts, only: [:new, :create] resources :result_assets, only: [:new, :create] @@ -173,7 +177,9 @@ Rails.application.routes.draw do end resources :steps, only: [:edit, :update, :destroy, :show] do - resources :step_comments, path: "/comments", only: [:new, :create, :index] + resources :step_comments, + path: '/comments', + only: [:new, :create, :index, :edit, :update, :destroy] member do post 'checklistitem_state' post 'toggle_step_state' @@ -183,7 +189,9 @@ Rails.application.routes.draw do end resources :results, only: [:update] do - resources :result_comments, path: "/comments", only: [:new, :create, :index] + resources :result_comments, + path: '/comments', + only: [:new, :create, :index, :edit, :update, :destroy] end resources :samples, only: [:edit, :update, :destroy]