From 54844c85786014a1e32bf469b28e4ca98d3d5b81 Mon Sep 17 00:00:00 2001 From: walidmujahid Date: Wed, 8 Apr 2020 07:32:05 -0400 Subject: [PATCH] feat: enable editing townsquare comments Comments can be edited. Editing can be cancled via Cancel button or ESC. This is part of the townsquare improvements: https://github.com/gitcoinco/web/issues/6003 fixes https://github.com/gitcoinco/web/issues/6010 --- app/assets/v2/js/activity.js | 214 ++++++++++++++---- .../migrations/0017_comment_is_edited.py | 18 ++ app/townsquare/models.py | 3 +- app/townsquare/views.py | 24 ++ 4 files changed, 218 insertions(+), 41 deletions(-) create mode 100644 app/townsquare/migrations/0017_comment_is_edited.py diff --git a/app/assets/v2/js/activity.js b/app/assets/v2/js/activity.js index c1d5a9f2028..a0ba90882d4 100644 --- a/app/assets/v2/js/activity.js +++ b/app/assets/v2/js/activity.js @@ -58,9 +58,9 @@ $(document).ready(function() { const $target = $(this).parents('.activity_detail_content'); const html = `

- Full Screen | - Pop Out | - Open in New Tab | + Full Screen | + Pop Out | + Open in New Tab | Leave Video Call

`; @@ -535,45 +535,91 @@ $(document).ready(function() { return; } - // user input - var comment = $parent.parents('.box').find('.comment_container textarea').val().trim(); + if ($('.editableComment')[0]) { + // edited content + const comment = $parent.parents('.box').find('.editableComment').val(); - // validation - if (!comment) { - return; - } + $('.editableComment').prop('disabled', true); - $parent.parents('.box').find('.comment_container textarea').prop('disabled', true); - $('.post_comment').prop('disabled', true); + // validation + if (!comment) { + $('.editableComment').prop('disabled', false); + return; + } - $parent.parents('.activity.box').find('.loading').removeClass('hidden'); - var has_hidden_comments = $parent.parents('.activity.box').find('.row.comment_row.hidden').length; - // increment number - var num = $parent.find('span.num').html(); + $parent.parents('.activity.box').find('.loading').removeClass('hidden'); - num = parseInt(num) + 1; - $parent.find('span.num').html(num); + const has_hidden_comments = $parent.parents('.activity.box').find('.row.comment_row.hidden').length; + const num = $parent.find('span.num').html(); + const comment_id = $('.editableComment').parent().closest('.comment_row').data('id'); + const url = '/api/v0.1/comment/' + comment_id; + const params = { + 'method': 'EDIT', + 'comment': comment, + 'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]').val() + }; - // remote post - var params = { - 'method': 'comment', - 'comment': comment, - 'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]').val() - }; - var url = '/api/v0.1/activity/' + $parent.data('pk'); + $.post(url, params, function(response) { + const success_callback = function($parent) { + $.noop(); // do nothing + }; - $.post(url, params, function(response) { - var success_callback = function($parent) { - $parent.parents('.box').find('.comment_container textarea').val(''); - $parent.find('textarea').focus(); + view_comments($parent, allow_close_comment_container, success_callback); + }).always(function() { + $parent.parents('.activity.box').find('.loading').addClass('hidden'); + $parent.parents('.box').find('.comment_container textarea').prop('disabled', false); + $('.editableComment').prop('disabled', false); + $(`.comment_row[data-id="${comment_id}"] .comment_options`).prop('disabled', false).removeClass('hidden'); + $(`.comment_row[data-id="${comment_id}"]`).parent().closest('.box') + .find('#container-post_comment') + .find('*').prop('disabled', false).removeClass('hidden'); + // TODO: automatically cancel editing when another edit_comment button is clicked + $('.edit_comment').prop('disabled', false); + }); + } else { + // user input + const comment = $parent.parents('.box').find('.comment_container textarea').val().trim(); + $parent.parents('.box').find('.comment_container textarea').prop('disabled', true); + $('.post_comment').prop('disabled', true); + + // validation + if (!comment) { + $parent.parents('.box').find('.comment_container textarea').prop('disabled', false); + $('.post_comment').prop('disabled', false); + return; + } + + + $parent.parents('.activity.box').find('.loading').removeClass('hidden'); + const has_hidden_comments = $parent.parents('.activity.box').find('.row.comment_row.hidden').length; + + // increment number + let num = $parent.find('span.num').html(); + + num = parseInt(num) + 1; + $parent.find('span.num').html(num); + + // remote post + const params = { + 'method': 'comment', + 'comment': comment, + 'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]').val() }; - var override_hide_comments = !has_hidden_comments; + const url = '/api/v0.1/activity/' + $parent.data('pk'); + + $.post(url, params, function(response) { + const success_callback = function($parent) { + $parent.parents('.box').find('.comment_container textarea').val(''); + $parent.find('textarea').focus(); - view_comments($parent, allow_close_comment_container, success_callback, override_hide_comments); - }).done(function() { - // pass - }) + }; + const override_hide_comments = !has_hidden_comments; + + view_comments($parent, allow_close_comment_container, success_callback, override_hide_comments); + }).done(function() { + // pass + }) .fail(function() { $parent.parents('.activity.box').find('.error').removeClass('hidden'); }) @@ -582,6 +628,7 @@ $(document).ready(function() { $parent.parents('.box').find('.comment_container textarea').prop('disabled', false); $('.post_comment').prop('disabled', false); }); + } }; // converts an object to a dict @@ -637,6 +684,7 @@ $(document).ready(function() { const timeAgo = timedifferenceCvrt(new Date(comment['created_on'])); const show_tip = true; const is_comment_owner = document.contxt.github_handle == comment['profile_handle']; + const is_edited = typeof comment['is_edited'] !== 'undefined' ? comment['is_edited'] : false; var sorted_match_curve_html = ''; if (comment['sorted_match_curve']) { @@ -646,6 +694,7 @@ $(document).ready(function() { for (let j = 0; j < match_curve.length; j++) { let ele = match_curve[j]; + sorted_match_curve_html += '
  • '; sorted_match_curve_html += `Your contribution of ${ele.name} could yield $${Math.round(ele.value * 1000) / 1000} in matching.`; sorted_match_curve_html += '
  • '; @@ -680,7 +729,7 @@ $(document).ready(function() { - + ${comment['name']} @${comment['profile_handle']} @@ -707,11 +756,14 @@ $(document).ready(function() { - ${timeAgo} + ${timeAgo} ${is_edited ? '(edited)' : ''} - + ${is_comment_owner ? `| ` + : ''} + ${is_comment_owner ? + `| ` : ''} ${show_tip ? `
    +
    @@ -836,6 +888,59 @@ $(document).ready(function() { post_comment($target, false); }); + + // cancel editing comment + $(document).on('click', '.cancel_edit', function(e) { + e.preventDefault(); + const comment_id = $(this).data('id'); + const editableContainer = $('#editableContainer'); + + const url = '/api/v0.1/comment/' + comment_id; + const params = { + 'method': 'GET_COMMENT' + }; + + $.get(url, params, function(response) { + let the_comment = response['comment']; + + the_comment = urlify(the_comment); + the_comment = linkify(the_comment); + the_comment = the_comment.replace(/\r\n|\r|\n/g, '
    '); + + editableContainer.replaceWith(`
    ${the_comment}
    `); + + }).always(function() { + $(`.comment_row[data-id="${comment_id}"] .comment_options`).prop('disabled', false).removeClass('hidden'); + $(`.comment_row[data-id="${comment_id}"]`).parent().closest('.box') + .find('#container-post_comment') + .find('*').prop('disabled', false).removeClass('hidden'); + // TODO: automatically cancel editing when another edit_comment button is clicked + $('.edit_comment').prop('disabled', false); + }); + }); + + + $(document).on('click', '.edit_comment', function(e) { + e.preventDefault(); + let comment_id = $(this).data('pk'); + let commentContainer = $(`.comment_row[data-id="${comment_id}"] .activity_comments_main_comment`); + let content = commentContainer.html().replace(/
    /g, '\n').trim(); + let editableContainer = $(`
    `); + + $(`.comment_row[data-id="${comment_id}"] .comment_options`).prop('disabled', true).addClass('hidden'); + $(`.comment_row[data-id="${comment_id}"]`).parent().closest('.box') + .find('#container-post_comment') + .find('*').prop('disabled', true).addClass('hidden'); + + commentContainer.replaceWith(editableContainer); + + $(`#editableContainer textarea[data-id="${comment_id}`).focus(); + + // TODO: automatically cancel editing when another edit_comment button is clicked + $('.edit_comment').prop('disabled', true); + }); + + $(document).on('click', '.delete_comment', function(e) { e.preventDefault(); const comment_id = $(this).data('pk'); @@ -865,12 +970,42 @@ $(document).ready(function() { }); - $(document).on('keypress', '.enter-activity-comment', function(e) { + $(document).on('keyup keydown keypress', '.enter-activity-comment, .editableComment', function(e) { if (e.which == 13 && !e.shiftKey) { const $target = $(this).parents('.activity.box').find('.comment_activity'); post_comment($target, false); } + + if (e.key === 'Escape' && typeof $('.editableComment')[0] != 'undefined') { + const comment_id = $(this).data('id'); + const editableContainer = $('#editableContainer'); + + const url = '/api/v0.1/comment/' + comment_id; + const params = { + 'method': 'GET_COMMENT' + }; + + $.get(url, params, function(response) { + let the_comment = response['comment']; + + the_comment = urlify(the_comment); + the_comment = linkify(the_comment); + the_comment = the_comment.replace(/\r\n|\r|\n/g, '
    '); + + editableContainer.replaceWith(`
    ${the_comment}
    `); + + }).always(function() { + $(`.comment_row[data-id="${comment_id}"] .comment_options`) + .prop('disabled', false) + .removeClass('hidden'); + $(`.comment_row[data-id="${comment_id}"]`).parent().closest('.box') + .find('#container-post_comment') + .find('*').prop('disabled', false).removeClass('hidden'); + // TODO: automatically cancel editing when another edit_comment button is clicked + $('.edit_comment').prop('disabled', false); + }); + } }); // post comment activity @@ -950,7 +1085,7 @@ function throttle(fn, wait) { } }; } - + window.addEventListener('scroll', throttle(function() { var offset = 800; @@ -959,4 +1094,3 @@ window.addEventListener('scroll', throttle(function() { $('.infinite-more-link').click(); } }, 500)); - diff --git a/app/townsquare/migrations/0017_comment_is_edited.py b/app/townsquare/migrations/0017_comment_is_edited.py new file mode 100644 index 00000000000..a44fd630294 --- /dev/null +++ b/app/townsquare/migrations/0017_comment_is_edited.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2020-04-06 15:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('townsquare', '0016_favorite'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='is_edited', + field=models.BooleanField(default=False), + ), + ] diff --git a/app/townsquare/models.py b/app/townsquare/models.py index d89fd609117..48b672d70f0 100644 --- a/app/townsquare/models.py +++ b/app/townsquare/models.py @@ -60,6 +60,7 @@ class Comment(SuperModel): likes = ArrayField(models.IntegerField(), default=list, blank=True) #pks of users who like this post likes_handles = ArrayField(models.CharField(max_length=200, blank=True), default=list, blank=True) #handles of users who like this post tip_count_eth = models.DecimalField(default=0, decimal_places=5, max_digits=50) + is_edited = models.BooleanField(default=False) def __str__(self): return f"Comment of {self.activity.pk} by {self.profile.handle}: {self.comment}" @@ -375,4 +376,4 @@ class Favorite(SuperModel): created = models.DateTimeField(auto_now=True) def __str__(self): - return f"Favorite {self.activity.activity_type}:{self.activity_id} by {self.user}" \ No newline at end of file + return f"Favorite {self.activity.activity_type}:{self.activity_id} by {self.user}" diff --git a/app/townsquare/views.py b/app/townsquare/views.py index ad3444247d7..fb02910748d 100644 --- a/app/townsquare/views.py +++ b/app/townsquare/views.py @@ -466,6 +466,8 @@ def api(request, activity_id): counter += 1; results[counter] += time.time() - start_time; start_time = time.time() comment_dict['sorted_match_curve'] = comment.profile.matchranking_this_round.sorted_match_curve if comment.profile.matchranking_this_round else None counter += 1; results[counter] += time.time() - start_time; start_time = time.time() + if comment.is_edited: + comment_dict['is_edited'] = comment.is_edited response['comments'].append(comment_dict) for key, val in results.items(): if settings.DEBUG: @@ -595,6 +597,28 @@ def comment_v1(request, comment_id): } return JsonResponse(response) + if method == 'EDIT': + content = request.POST.get('comment') + title = request.POST.get('comment') + + comment.comment = content + comment.is_edited = True + comment.save() + response = { + 'status': 203, + 'message': 'comment successfully updated' + } + return JsonResponse(response) + + # no perms needed responses go here + if request.GET.get('method') == 'GET_COMMENT': + response = { + 'status': 202, + 'message': 'comment successfully retrieved', + 'comment': comment.comment, + } + return JsonResponse(response) + return JsonResponse(response)