Skip to content

Commit a4d096c

Browse files
committed
Merge branch 'develop' into MoshiKoi/1025/remove-special-case-notifying-author-of-threads
2 parents 4341e29 + 8f93b46 commit a4d096c

23 files changed

Lines changed: 302 additions & 121 deletions

app/assets/javascripts/comments.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,4 +339,41 @@ $(() => {
339339
$tgt.find('.js-text').text('copy link');
340340
}, 1000);
341341
});
342+
343+
QPixel.DOM.addSelectorListener('click', '.js-follow-comments', async (ev) => {
344+
ev.preventDefault();
345+
346+
const { target } = ev;
347+
348+
if (!QPixel.DOM.isHTMLElement(target)) {
349+
return;
350+
}
351+
352+
const { postId, action } = target.dataset;
353+
354+
if (!postId || !action) {
355+
return;
356+
}
357+
358+
const shouldFollow = action === 'follow';
359+
360+
const data = shouldFollow ?
361+
await QPixel.followComments(postId) :
362+
await QPixel.unfollowComments(postId);
363+
364+
QPixel.handleJSONResponse(data, () => {
365+
target.dataset.action = shouldFollow ? 'unfollow' : 'follow';
366+
367+
const icon = document.createElement('i');
368+
icon.classList.add('fas', 'fa-fw', shouldFollow ? 'fa-bell-slash' : 'fa-bell');
369+
const text = document.createTextNode(` ${shouldFollow ? 'Unfollow' : 'Follow'} new`);
370+
target.replaceChildren(icon, text);
371+
372+
const form = target.closest('form');
373+
374+
if (form) {
375+
form.action = `/comments/post/${postId}/${shouldFollow ? 'unfollow' : 'follow'}`;
376+
}
377+
});
378+
});
342379
});

app/assets/javascripts/qpixel_api.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,16 @@ window.QPixel = {
471471
return data;
472472
},
473473

474+
followComments: async (postId) => {
475+
const resp = await QPixel.fetchJSON(`/comments/post/${postId}/follow`, {}, {
476+
headers: { 'Accept': 'application/json' }
477+
});
478+
479+
const data = await resp.json();
480+
481+
return data;
482+
},
483+
474484
undeleteComment: async (id) => {
475485
const resp = await QPixel.fetchJSON(`/comments/${id}/delete`, {}, {
476486
headers: { 'Accept': 'application/json' },
@@ -482,6 +492,16 @@ window.QPixel = {
482492
return data;
483493
},
484494

495+
unfollowComments: async (postId) => {
496+
const resp = await QPixel.fetchJSON(`/comments/post/${postId}/unfollow`, {}, {
497+
headers: { 'Accept': 'application/json' }
498+
});
499+
500+
const data = await resp.json();
501+
502+
return data;
503+
},
504+
485505
lockThread: async (id) => {
486506
const resp = await QPixel.fetchJSON(`/comments/thread/${id}/restrict`, {
487507
type: 'lock'

app/assets/javascripts/qpixel_dom.js

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@ QPixel.DOM = {
44
_delegatedListeners: [],
55
_eventListeners: {},
66

7-
/**
8-
* Add a delegated event listener. Use when an event listener is required that will fire for elements added to the
9-
* DOM dynamically after the delegated listener is added.
10-
* @param {string} event An event name to listen for.
11-
* @param {string} selector A CSS selector representing elements on which to apply the listener.
12-
* @param {EventCallback} callback A callback function to pass to the event listener.
13-
*/
147
addDelegatedListener: (event, selector, callback) => {
158
if (!QPixel.DOM._eventListeners[event]) {
169
const listener = (ev) => {
@@ -26,23 +19,12 @@ QPixel.DOM = {
2619
QPixel.DOM._delegatedListeners.push({ event, selector, callback });
2720
},
2821

29-
/**
30-
* Convenience method. Add an event listener to _all_ elements that currently match a selector.
31-
* @param {string} event An event name to listen for.
32-
* @param {string} selector A CSS selector representing elements on which to apply the listener.
33-
* @param {EventCallback} callback A callback function to pass to the event listener.
34-
*/
3522
addSelectorListener: (event, selector, callback) => {
3623
document.querySelectorAll(selector).forEach((el) => {
3724
el.addEventListener(event, callback);
3825
});
3926
},
4027

41-
/**
42-
* Smoothly fade an element out of view, then remove it.
43-
* @param {HTMLElement} element The element to fade out.
44-
* @param {number} duration A duration for the effect in milliseconds.
45-
*/
4628
fadeOut: (element, duration) => {
4729
element.style.transition = `${duration}ms`;
4830
element.style.opacity = '0';
@@ -51,12 +33,10 @@ QPixel.DOM = {
5133
}, duration);
5234
},
5335

54-
/**
55-
* Helper to set the visibility of an element or array of elements. Uses display: none so should work with screen
56-
* readers.
57-
* @param {HTMLElement|HTMLElement[]} elements An element or array of elements to set visibility for.
58-
* @param {boolean} visible Whether or not the elements should be visible.
59-
*/
36+
isHTMLElement: (node) => {
37+
return node instanceof HTMLElement;
38+
},
39+
6040
setVisible: (elements, visible) => {
6141
if (!Array.isArray(elements)) {
6242
elements = [elements];

app/controllers/comments_controller.rb

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ class CommentsController < ApplicationController
33
before_action :authenticate_user!, except: [:post, :show, :thread, :thread_content]
44

55
before_action :set_comment, only: [:update, :destroy, :undelete, :show]
6-
before_action :set_post, only: [:create_thread]
6+
before_action :set_post, only: [:create_thread, :post_follow, :post_unfollow]
77
before_action :set_thread,
88
only: [:create, :thread, :thread_content, :thread_rename, :thread_restrict, :thread_unrestrict,
99
:thread_followers]
@@ -259,29 +259,33 @@ def thread_unrestrict
259259

260260
def post
261261
@post = Post.find(params[:post_id])
262-
@comment_threads = if current_user&.at_least_moderator? || current_user&.post_privilege?('flag_curate', @post)
263-
CommentThread
264-
else
265-
CommentThread.undeleted
266-
end.where(post: @post).order(deleted: :asc, archived: :asc, reply_count: :desc)
262+
@comment_threads = CommentThread.accessible_to(current_user, @post)
263+
.where(post: @post)
264+
.order(deleted: :asc, archived: :asc, reply_count: :desc)
267265
respond_to do |format|
268266
format.html { render layout: false }
269267
format.json { render json: @comment_threads }
270268
end
271269
end
272270

273271
def post_follow
274-
@post = Post.find(params[:post_id])
275272
if ThreadFollower.where(post: @post, user: current_user).none?
276273
ThreadFollower.create(post: @post, user: current_user)
277274
end
278-
redirect_to post_path(@post)
275+
276+
respond_to do |format|
277+
format.html { redirect_to post_path(@post) }
278+
format.json { render json: { status: 'success' } }
279+
end
279280
end
280281

281282
def post_unfollow
282-
@post = Post.find(params[:post_id])
283283
ThreadFollower.where(post: @post, user: current_user).destroy_all
284-
redirect_to post_path(@post)
284+
285+
respond_to do |format|
286+
format.html { redirect_to post_path(@post) }
287+
format.json { render json: { status: 'success' } }
288+
end
285289
end
286290

287291
def pingable
@@ -297,7 +301,7 @@ def comment_params
297301
end
298302

299303
def set_comment
300-
@comment = Comment.unscoped.find params[:id]
304+
@comment = Comment.unscoped.find(params[:id])
301305
end
302306

303307
def set_post

app/controllers/notifications_controller.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ def index
1111
format.html { render :index, layout: 'without_sidebar' }
1212
format.json do
1313
render json: (@notifications.to_a.map do |notif|
14-
notif.as_json.merge(content: helpers.render_pings_text(notif.content))
15-
end), methods: :community_name
14+
notif.as_json.merge(content: helpers.render_pings_text(notif.content),
15+
community_name: notif.community_name)
16+
end)
1617
end
1718
end
1819
end

app/models/comment_thread.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ class CommentThread < ApplicationRecord
1414
validate :maximum_title_length
1515
validates :title, presence: { message: I18n.t('comments.errors.title_presence') }
1616

17+
# Gets threads appropriately scoped for a given user & post
18+
# @param user [User, nil] user to check
19+
# @para post [Post] post to check
20+
# @return [ActiveRecord::Relation<CommentThread>]
21+
def self.accessible_to(user, post)
22+
if user&.at_least_moderator? || user&.post_privilege?('flag_curate', post)
23+
CommentThread
24+
else
25+
CommentThread.undeleted
26+
end
27+
end
28+
1729
# Is the thread read-only (can't be edited)?
1830
# @return [Boolean] check result
1931
def read_only?

app/views/comments/_new_thread.html.erb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<%#
2-
Renders new comment thread button & modal
2+
"Renders new comment thread button & modal
33
4-
Variables:
5-
post : post to create a new thread for
6-
text : text to display on the button
7-
user : user that will create a new thread
8-
%>
4+
Variables:
5+
post : post to create a new thread for
6+
text : text to display on the button
7+
user : user that will create a new thread
8+
"%>
99

1010
<%
1111
can_comment = user.can_comment_on?(post)

app/views/comments/_new_thread_modal.html.erb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,10 @@
4343
class: 'form-element js-thread-title-field',
4444
data: { post: post.id,
4545
thread: '-1',
46-
character_count: '.js-character-count-thread-title',
46+
character_count: ".js-character-count-thread-title-#{post.id}",
4747
markdown: 'strip' } %>
4848
<%= render 'shared/char_count',
49-
type: 'thread-title',
50-
min: 1,
49+
type: "thread-title-#{post.id}",
5150
max: maximum_thread_title_length %>
5251

5352
<%= submit_tag 'Create thread',

app/views/comments/_rename_thread_modal.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<%#
22
"Helper for rendering comment thread rename action modal
33
4-
Variables:
5-
thread : Comment thread to create the modal for
4+
Variables:
5+
thread : Comment thread to create the modal for
66
"%>
77

88
<div class="modal is-with-backdrop is-small js--rename-thread-<%= thread.id %>">

app/views/comments/_reply_to_thread.html.erb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<%#
2-
Renders reply to comment thread button & input
2+
"Renders reply to comment thread button & input
33
4-
Variables:
5-
inline : whether the reply is done via inline thread view
6-
post : post the thread is for
7-
text : text to display on the button
8-
thread : thread to create a reply for
9-
user : user that will create a reply to the thread
10-
%>
4+
Variables:
5+
inline : whether the reply is done via inline thread view
6+
post : post the thread is for
7+
text : text to display on the button
8+
thread : thread to create a reply for
9+
user : user that will create a reply to the thread
10+
"%>
1111

1212
<%
1313
# TODO: make configurable

0 commit comments

Comments
 (0)