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("
");
+ }
+
+ // 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| %>
+
+<% 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 %>
-
+
<% end %>
- <%= render 'project_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 %>
-
+
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| %>
-
<% end %>
- <%= 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 %>
-
+
<% end %>
- <%= 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 %>
-
+
<% end %>
- <%= 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]
+- <%= I18n.t('comments.options_dropdown.header') %>
+ <% if can_edit_module_comment(comment) %>
+ -
+
+ <%= t('comments.options_dropdown.edit') %>
+
+
+ <% end %>
+ <% if can_delete_module_comment(comment) %>
+ -
+
+ <%= t('comments.options_dropdown.delete') %>
+
+
+ <% end %>
+
+