From 774521df7a4e08830eb267c86cb815746d9a24e4 Mon Sep 17 00:00:00 2001 From: Urban Rotnik Date: Sat, 20 Apr 2019 10:59:59 +0200 Subject: [PATCH 1/2] Changes to comments section in Task Steps --- app/assets/javascripts/comments.js.erb | 193 +++++++++++++++++++- app/assets/stylesheets/steps.scss | 145 +++++++++++++++ app/controllers/step_comments_controller.rb | 4 +- app/views/steps/_comments.html.erb | 42 +++++ app/views/steps/_step.html.erb | 16 +- app/views/steps/comments/_item.html.erb | 70 +++++++ app/views/steps/comments/_list.html.erb | 4 + config/locales/en.yml | 2 + 8 files changed, 462 insertions(+), 14 deletions(-) create mode 100644 app/views/steps/_comments.html.erb create mode 100644 app/views/steps/comments/_item.html.erb create mode 100644 app/views/steps/comments/_list.html.erb diff --git a/app/assets/javascripts/comments.js.erb b/app/assets/javascripts/comments.js.erb index 8297c23f9..16ad746d1 100644 --- a/app/assets/javascripts/comments.js.erb +++ b/app/assets/javascripts/comments.js.erb @@ -28,8 +28,17 @@ var Comments = (function() { }); } initCommentForm(that); + initCommentFormNew(that); initCommentsLink(that); - scrollBottom(that.find('.content-comments')); + initCommentsLinkNew(that); + initDeleteCommentNew(that); + initEditCommentNew(that); + + var scrollItem = that.find('.content-comments'); // Check for new version of comments + if (that.hasClass('content-comments')) { + scrollItem = that + } + scrollBottom(scrollItem); }); } } @@ -84,6 +93,29 @@ var Comments = (function() { }); } + function initCommentsLinkNew($el) { + $el.find('.btn-more-comments-new').off() + .on('ajax:success', function (e, data) { + if (data.html) { + + var stepId = $(this).data('step-id') + var list = $('#comments-list-' + stepId); + var moreBtn = $(this); + + list.prepend(data.html); + + if (data.resultsNumber < data.perPage) { + moreBtn.remove(); + } else { + moreBtn.attr('href', data.moreUrl); + moreBtn.trigger('blur'); + } + } else { + $('.btn-more-comments').remove(); + } + }); + } + // Initialize comment form. function initCommentForm($el) { @@ -163,6 +195,55 @@ var Comments = (function() { }); } + function initCommentFormNew($el) { + var stepId = $el.data('step-id'); + var $form = $('#new-message-' + stepId).find('form'); + var $list = $el.find('.comments-list'); + var $submitBtn = $form.find('.new-comment-button') + + $submitBtn.on('click', function() { + $form.submit(); + }); + + $('.help-block', $form).addClass('hide'); + + $form.off().on('ajax:send', function () { + $('#comment_message', $form).attr('readonly', true); + $submitBtn.off('click'); + }) + .on('ajax:success', function (e, data) { + if (data.html) { + $list.append(data.html).scrollTop(0); + scrollBottom($el); + $('#comment_message', $form).val(''); + $('.form-group', $form).removeClass('has-error'); + $('.help-block', $form).html('').addClass('hide'); + $submitBtn.removeClass('has-error'); + + var currnetCount = $('#counter-' + stepId).html() + $('#counter-' + stepId).html(parseInt(currnetCount) + 1) + } + }) + .on('ajax:error', function (ev, xhr) { + if (xhr.status === 400) { + var messageError = xhr.responseJSON.errors.message; + + if (messageError) { + $('.form-group', $form).addClass('has-error'); + $('.help-block', $form).html(messageError[0]).removeClass('hide'); + $submitBtn.addClass('has-error'); + } + } + }) + .on('ajax:complete', function () { + scrollBottom($('#comment_message', $form)); + $('#comment_message', $form).attr('readonly', false).focus(); + $submitBtn.on('click', function() { + $form.submit(); + }); + }); + } + function initCommentOptions(scrollableContainer, useParentOffset) { if ( ! _.isUndefined(useParentOffset) ) { useParentOffset = useParentOffset; @@ -260,6 +341,35 @@ var Comments = (function() { }); } + function initDeleteCommentNew($el) { + var parents = $el.find('.comments-list') + + $(parents).unbind('click').on('click', '[data-action=delete-comment-new]', function(e) { + e.preventDefault(); + var $this = $(this) + + if (confirm($this.attr('data-confirm-message'))) { + $.ajax({ + url: $this.attr('data-url'), + type: 'DELETE', + dataType: 'json', + success: function(data) { + var commentEl = $this.closest('.comment-row').hide() + commentEl.remove(); + + var stepID = $this.attr('data-step-id') + var currnetCount = $('#counter-' + stepID).html() + $('#counter-' + stepID).html(parseInt(currnetCount) - 1) + }, + error: function(data) { + // Display alert + alert(data.responseJSON.message); + } + }); + } + }); + } + function initEditComments(parent) { $(parent).unbind('click').on('click', '[data-action=edit-comment]', function() { @@ -346,10 +456,91 @@ var Comments = (function() { }); } + function initEditCommentNew($el) { + var editBtnSelector = '[data-action=edit-comment-new]'; + var cancelBtnSelector = '[data-action=cancel-comment-new]'; + var saveBtnSelector = '[data-action=save-comment-new]'; + var commentMessageSelector = '.comment-message'; + + var parents = $el.find('.comments-list') + + $(parents).on('click', commentMessageSelector, function(e) { + var $this = $(this); + startEditingMode($this.data('comment-id')) + }) + + $(parents).on('click', editBtnSelector, function(e) { + e.preventDefault(); + var $this = $(this); + startEditingMode($this.closest('.comment-row').data('comment-id')) + }) + + $(parents).on('click', cancelBtnSelector, function(e) { + e.preventDefault(); + var $this = $(this); + var commentId = $this.closest('.comment-row').data('comment-id') + var $commentTextArea = $('#comment-textarea-' + commentId); + $commentTextArea.val($commentTextArea.data('message')); + resetAllEditingModes(); + }) + + $(parents).on('click', saveBtnSelector, function(e) { + e.preventDefault(); + var $this = $(this); + var $comment = $this.closest('.comment-row'); + var commentId = $comment.data('comment-id'); + var $form = $comment.find('form'); + var $textarea = $form.find('textarea'); + var $commentMessage = $('#comment-message-' + commentId); + + $form + .on('ajax:send', function() { + $textarea.attr('readonly', true); + $this.addClass('hidden'); + }) + .on('ajax:success', function(ev, data) { + $commentMessage.html(data.comment); + $textarea.data('message', $textarea.val()); + resetAllEditingModes(); + }) + .on('ajax:error', function(ev, xhr) { + if (xhr.status === 422) { + alert(xhr.responseJSON.errors.message) + } + else{ + alert('Error. Cannot update comment!') + } + }) + .on('ajax:complete', function() { + $textarea.attr('readonly', false).focus(); + $this.removeClass('hidden'); + }); + $form.submit(); + }); + } + + function startEditingMode(commentId){ + resetAllEditingModes(); + var $commentTextArea = $('#comment-textarea-' + commentId); + var tempContent = $commentTextArea.val(); + + $('#comment-'+commentId + ' > .comment-container').addClass('edit'); + $commentTextArea.focus().val('').val(tempContent); + } + + function resetAllEditingModes() { + $('.comment-container').removeClass('edit'); + } + + return { initialize: initializeComments, scrollBottom: scrollBottom, moreComments: initCommentsLink, + initCommentsLinkNew: initCommentsLinkNew, + initCommentFormNew: initCommentFormNew, + initDeleteCommentNew: initDeleteCommentNew, + initEditCommentNew: initEditCommentNew, form: initCommentForm, initCommentOptions: initCommentOptions, initDeleteComments: initDeleteComments, diff --git a/app/assets/stylesheets/steps.scss b/app/assets/stylesheets/steps.scss index 70f6b5196..28313bf51 100644 --- a/app/assets/stylesheets/steps.scss +++ b/app/assets/stylesheets/steps.scss @@ -184,5 +184,150 @@ } } +.step .textarea-sm { + border-radius: 0; +} +.comments-title { + color: $color-emperor; +} + +.comment-container { + display: flex; + padding: 5px 0 5px 5px; + + &:hover { + background-color: $brand-primary-light; + + .comment-actions { + display: inline; + + a { + color: $color-silver-chalice; + + &:hover { + text-decoration: none; + } + } + + a:active { + color: $color-dove-gray; + } + } + } + + &.edit { + .comment-textarea { + display: block; + } + + .comment-message { + display: none; + } + + .edit-actions { + display: none; + } + + .editing-actions { + display: inline-block; + } + } + + .comment-textarea { + display: none; + } + + .comment-message { + display: block; + } + + .edit-actions { + display: inline-block; + } + + .editing-actions { + display: none; + } +} + +.avatar-placehodler { + height: 30px; + width: 30px; + + img { + border-radius: 30px; + position: relative; + top: 7px; + } +} + +.content-placeholder { + flex-grow: 1; + + .row-content { + margin-left: -25px; + } +} + +.comment-right { + line-height: 16px; +} + +.comment-datetime { + font-size: 12px; +} + +.comment-name { + color: $color-silver-chalice; + font-size: 16px; + + p { + margin-bottom: 3px; + } +} + +.comment-message { + font-size: 14px; + line-height: 17px; + margin-top: -4px; + padding-left: 10px; +} + +.comment-actions { + display: none; + font-size: 16px; + margin-right: 20px; + + .action-icon-delete { + margin-left: 20px; + } + + .edit-label { + font-size: 14px; + margin-left: 3px; + } +} + +.step-comments { + overflow: auto; +} + +.new-comment-button { + cursor: pointer; + font-size: 18px; + left: calc(100% - 29px); + position: relative; + top: -41px; + + &.has-error { + top: -64px; + } +} + +.new-message-continer { + float: left; + margin-top: 20px; + width: 100%; +} diff --git a/app/controllers/step_comments_controller.rb b/app/controllers/step_comments_controller.rb index 31ca5864a..ee47a41c3 100644 --- a/app/controllers/step_comments_controller.rb +++ b/app/controllers/step_comments_controller.rb @@ -19,7 +19,7 @@ class StepCommentsController < ApplicationController # messages. 'list' partial is used for showing more # comments. partial = 'index.html.erb' - partial = 'list.html.erb' if @last_comment_id.positive? + partial = 'steps/comments/list.html.erb' if @last_comment_id.positive? more_url = '' if @comments.size.positive? more_url = url_for(step_step_comments_path(@step, @@ -57,7 +57,7 @@ class StepCommentsController < ApplicationController format.json { render json: { html: render_to_string( - partial: "comment.html.erb", + partial: 'steps/comments/item.html.erb', locals: { comment: @comment } diff --git a/app/views/steps/_comments.html.erb b/app/views/steps/_comments.html.erb new file mode 100644 index 000000000..e27010684 --- /dev/null +++ b/app/views/steps/_comments.html.erb @@ -0,0 +1,42 @@ +<% per_page = Constants::COMMENTS_SEARCH_LIMIT %> +<% if can_read_protocol_in_module?(@protocol) %> + +
+

+ <%=t('protocols.steps.comments') %> + (<%= comments_count %>) +

+
+ +
+ <% if comments.size == per_page %> + + <% end %> +
+ <%= render partial: 'steps/comments/list.html.erb', locals: { comments: comments} %> +
+
+
+ <% if can_create_comments_in_module?(@protocol.my_module) %> + <%= bootstrap_form_for( :comment, url: step_step_comments_path(step, format: :json), html: { method: :post }, remote: true) do |f| %> + <%= f.smart_text_area :message, + single_line: true, + hide_label: true, + placeholder: t('general.comment_placeholder_new'), + help: '.', + data: { 'atwho-edit' => '' } %> + + <% end %> + <% end %> +
+<% end %> diff --git a/app/views/steps/_step.html.erb b/app/views/steps/_step.html.erb index b97266bbd..cf940f724 100644 --- a/app/views/steps/_step.html.erb +++ b/app/views/steps/_step.html.erb @@ -143,17 +143,11 @@ <% end %> - - <% if can_read_protocol_in_module?(@protocol) %> -
-
- <%= render partial: "step_comments/index.html.erb", - locals: { step: step, comments: step.last_comments, per_page: Constants::COMMENTS_SEARCH_LIMIT } %> -
-
- <% end %> +
+
+ <%= render partial: 'steps/comments.html.erb', locals: { comments: step.last_comments, + comments_count: step.step_comments.count, + step: step } %> diff --git a/app/views/steps/comments/_item.html.erb b/app/views/steps/comments/_item.html.erb new file mode 100644 index 000000000..73c8bb91e --- /dev/null +++ b/app/views/steps/comments/_item.html.erb @@ -0,0 +1,70 @@ +
+
+
+ <%= image_tag avatar_path(comment.user, :icon_small), class: 'avatar' %> +
+
+
+

<%= comment.user.full_name %>

+
+
+
+

<%= l(comment.created_at, format: :full) %>

+
+ <% if comment.user == current_user %> + + <% end %> +
+
+
+
+ <%= custom_auto_link(comment.message, team: current_team) %> +
+ <%= bootstrap_form_for( :comment, url: step_step_comment_path(comment.step, comment, format: :json), + html: { method: :put }, remote: true) do |f| %> + <%= f.smart_text_area :message, + single_line: true, + hide_label: true, + value: comment.message, + data: { 'atwho-edit': '', message: comment.message }, + class: 'comment-textarea', + id: 'comment-textarea-' + comment.id.to_s %> + <% end %> +
+
+
+
+
+ + diff --git a/app/views/steps/comments/_list.html.erb b/app/views/steps/comments/_list.html.erb new file mode 100644 index 000000000..2881376d9 --- /dev/null +++ b/app/views/steps/comments/_list.html.erb @@ -0,0 +1,4 @@ +<% comments.each do |comment| %> + <%= render partial: 'steps/comments/item.html.erb', + locals: { comment: comment } %> +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 17e178ae8..c6908eb3b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1739,6 +1739,7 @@ en: no_description: "This step has no description." tables: "Tables" files: "Attachments (%{count})" + comments: "Comments" empty_checklist: "No items" comment_title: "%{user} at %{time}:" options: @@ -1959,6 +1960,7 @@ en: no_comments: "No comments!" more_comments: "More comments" comment_placeholder: "Your Message" + comment_placeholder_new: "Add new comment…" module: one: "task" other: "tasks" From f894bca694ffa57f817c7c5681a04810de68147f Mon Sep 17 00:00:00 2001 From: Urban Rotnik Date: Tue, 23 Apr 2019 14:43:52 +0200 Subject: [PATCH 2/2] Add saving event when user clicks out of textarea --- app/assets/javascripts/comments.js.erb | 79 ++++++++++++++++---------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/comments.js.erb b/app/assets/javascripts/comments.js.erb index 16ad746d1..1eb833abc 100644 --- a/app/assets/javascripts/comments.js.erb +++ b/app/assets/javascripts/comments.js.erb @@ -461,6 +461,7 @@ var Comments = (function() { var cancelBtnSelector = '[data-action=cancel-comment-new]'; var saveBtnSelector = '[data-action=save-comment-new]'; var commentMessageSelector = '.comment-message'; + var textareaSelector = '.comment-textarea' var parents = $el.find('.comments-list') @@ -484,45 +485,63 @@ var Comments = (function() { resetAllEditingModes(); }) + $(parents).on('focusout', textareaSelector, function(e) { + var $this = $(this); + var $comment = $this.closest('.comment-row') + var commentId = $comment.data('comment-id'); + + if($this[0].dataset.editing == 0) return; + + updateComment(commentId); + }) + + $(parents).on('click', saveBtnSelector, function(e) { e.preventDefault(); - var $this = $(this); - var $comment = $this.closest('.comment-row'); - var commentId = $comment.data('comment-id'); - var $form = $comment.find('form'); - var $textarea = $form.find('textarea'); - var $commentMessage = $('#comment-message-' + commentId); - - $form - .on('ajax:send', function() { - $textarea.attr('readonly', true); - $this.addClass('hidden'); - }) - .on('ajax:success', function(ev, data) { - $commentMessage.html(data.comment); - $textarea.data('message', $textarea.val()); - resetAllEditingModes(); - }) - .on('ajax:error', function(ev, xhr) { - if (xhr.status === 422) { - alert(xhr.responseJSON.errors.message) - } - else{ - alert('Error. Cannot update comment!') - } - }) - .on('ajax:complete', function() { - $textarea.attr('readonly', false).focus(); - $this.removeClass('hidden'); - }); - $form.submit(); + var commentId = $(this).closest('.comment-row').data('comment-id'); + updateComment(commentId); }); } + function updateComment(commentId){ + var $comment = $('#comment-' + commentId); + var $form = $comment.find('form'); + var $textarea = $form.find('textarea'); + var $commentMessage = $('#comment-message-' + commentId); + var $saveBtn = $comment.find('.save-comment-new'); + + $form + .off('ajax:send').on('ajax:send', function() { + $textarea.attr('readonly', true); + $saveBtn.addClass('hidden'); + $textarea[0].dataset.editing = 0; + }) + .off('ajax:success').on('ajax:success', function(ev, data) { + $commentMessage.html(data.comment); + $textarea.data('message', $textarea.val()); + resetAllEditingModes(); + }) + .off('ajax:error').on('ajax:error', function(ev, xhr) { + if (xhr.status === 422) { + alert(xhr.responseJSON.errors.message) + } + else{ + alert('Error. Cannot update comment!') + } + }) + .off('ajax:complete').on('ajax:complete', function() { + $textarea.attr('readonly', false).focus(); + $saveBtn.removeClass('hidden'); + }); + + $form.submit(); + } + function startEditingMode(commentId){ resetAllEditingModes(); var $commentTextArea = $('#comment-textarea-' + commentId); var tempContent = $commentTextArea.val(); + $commentTextArea[0].dataset.editing = 1; $('#comment-'+commentId + ' > .comment-container').addClass('edit'); $commentTextArea.focus().val('').val(tempContent);