Skip to content

Commit 1afe2b3

Browse files
authored
Merge pull request #1647 from codidact/0valt/1579/comments
General improvements to comment threads
2 parents 548a9ad + 4dcaf45 commit 1afe2b3

140 files changed

Lines changed: 3304 additions & 1373 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Layout/LineLength:
2323
Layout/SpaceAroundMethodCallOperator:
2424
Enabled: true
2525
Layout/SpaceInsideArrayLiteralBrackets:
26-
Enabled: false
26+
Enabled: true
2727

2828
Lint/DeprecatedOpenSSLConstant:
2929
Enabled: true

app/assets/javascripts/comments.js

Lines changed: 115 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,70 @@
11
$(() => {
2-
$('.js-more-comments').on('click', async (evt) => {
2+
$(document).on('click', '.js-more-comments', async (evt) => {
33
evt.preventDefault();
44
const $tgt = $(evt.target);
55
const $anchor = $tgt.is('a') ? $tgt : $tgt.parents('a');
66
const postId = $anchor.attr('data-post-id');
77

8-
const resp = await fetch(`/comments/post/${postId}`, {
9-
headers: { 'Accept': 'text/html' }
10-
});
11-
const data = await resp.text();
8+
const data = await QPixel.getThreadsListContent(postId);
9+
1210
$tgt.parents('.post--comments').find('.post--comments-container').html(data).trigger('ajax:success');
1311
$tgt.parents('.post--comments').find('.js-more-comments').remove();
1412
});
1513

14+
/**
15+
* @param {JQuery<HTMLElement>} $tgt
16+
* @returns {HTMLElement | null}
17+
*/
18+
const getCommentThreadWrapper = ($tgt) => {
19+
return $tgt.closest('.js-comment-thread-wrapper')[0] ?? null;
20+
};
21+
1622
$(document).on('click', '.post--comments-thread.is-inline a', async (evt) => {
1723
if (evt.ctrlKey) { return; }
1824

1925
evt.preventDefault();
2026

2127
const $tgt = $(evt.target);
22-
openThread($tgt.closest('.post--comments-thread-wrapper')[0], $tgt.attr("href"));
23-
});
28+
const $threadId = $tgt.data('thread');
29+
const wrapper = getCommentThreadWrapper($tgt);
2430

25-
async function openThread(wrapper, targetUrl, showDeleted = false) {
26-
const resp = await fetch(`${targetUrl}?inline=true&show_deleted_comments=${showDeleted ? 1 : 0}`, {
27-
headers: { 'Accept': 'text/html' }
28-
});
29-
let data = await resp.text();
31+
openThread(wrapper, $threadId);
32+
});
3033

31-
data = data.split("<!-- THREAD STARTS BELOW -->")[1];
32-
data = data.split("<!-- THREAD ENDS ABOVE -->")[0];
34+
/**
35+
* @param {HTMLElement} wrapper
36+
* @param {string} threadId
37+
* @param {GetThreadContentOptions} [options]
38+
*/
39+
async function openThread(wrapper, threadId, options) {
40+
const data = await QPixel.getThreadContent(threadId, options);
3341

3442
wrapper.innerHTML = data;
3543

36-
$('a.show-deleted-comments').click(async (evt) => {
37-
if (evt.ctrlKey) { return; }
38-
evt.preventDefault();
39-
openThread(wrapper, targetUrl, true);
40-
});
41-
4244
window.MathJax && MathJax.typeset();
4345
window.hljs && hljs.highlightAll();
4446
}
4547

48+
$(document).on('click', '.js-show-deleted-comments', (ev) => {
49+
if (ev.ctrlKey) { return; } // do we really need it?
50+
51+
ev.preventDefault();
52+
53+
const $tgt = $(ev.target);
54+
const $inline = $tgt.data('inline');
55+
const $threadId = $tgt.data('thread');
56+
const wrapper = getCommentThreadWrapper($tgt);
57+
58+
openThread(wrapper, $threadId, { inline: $inline, showDeleted: true });
59+
});
60+
4661
$(document).on('click', '.js-collapse-thread', async (ev) => {
4762
const $tgt = $(ev.target);
4863
const $widget = $tgt.parents('.widget');
4964
const $embed = $tgt.parents('.post--comments-thread');
5065

5166
const threadId = $widget.data('thread');
67+
const isLocked = $widget.data('locked');
5268
const isDeleted = $widget.data('deleted');
5369
const isArchived = $widget.data('archived');
5470
const threadTitle = $widget.find('.js-thread-title').text();
@@ -58,14 +74,21 @@ $(() => {
5874
const $link = $(`<a href="/comments/thread/${threadId}" class="js--comment-link" data-thread=${threadId}></a>`);
5975
$link.text(threadTitle);
6076

77+
if (isLocked) {
78+
$container.append(`<i class="fas fa-lock fa-fw" title="Locked thread" aria-label="Locked thread"></i>`);
79+
$container.addClass('is-locked');
80+
}
81+
6182
if (isDeleted) {
6283
$container.append(`<i class="fas fa-trash h-c-red-600 fa-fw" title="Deleted thread" aria-label="Deleted thread"></i>`);
6384
$container.addClass('is-deleted');
6485
}
86+
6587
if (isArchived) {
6688
$container.append(`<i class="fas fa-archive fa-fw" title="Archived thread" aria-label="Archived thread"></i>`);
6789
$container.addClass('is-archived');
6890
}
91+
6992
$container.append($link);
7093
$container.append(`(${replyCount} comment${replyCount !== 1 ? 's' : ''})`);
7194
$embed[0].outerHTML = $container[0].outerHTML;
@@ -78,35 +101,48 @@ $(() => {
78101
const $comment = $tgt.parents('.comment');
79102
const $commentBody = $comment.find('.comment--body');
80103
const $thread = $comment.parents('.thread');
104+
81105
const commentId = $comment.attr('data-id');
82106
const postId = $thread.attr('data-post');
83107
const threadId = $thread.attr('data-thread');
108+
109+
// if this matches, this means we are already in edit mode
110+
if ($(`.js-discard-edit[data-comment-id="${commentId}"]`).length) {
111+
return;
112+
}
113+
84114
const originalComment = $commentBody.clone();
85115

86-
const resp = await fetch(`/comments/${commentId}`, {
87-
credentials: 'include',
88-
headers: { 'Accept': 'application/json' }
89-
});
90-
const data = await resp.json();
91-
const content = data.content;
116+
const data = await QPixel.getComment(commentId);
92117

93118
const formTemplate = `<form action="/comments/${commentId}/edit" method="POST" class="comment-edit-form" data-remote="true">
94119
<label for="comment-content" class="form-element">Comment body:</label>
95-
<textarea id="comment-content" rows="6" class="form-element is-small" data-thread="${threadId}" data-post="${postId}" data-character-count=".js-character-count-comment-body" name="comment[content]">${content}</textarea>
120+
<textarea id="comment-content"
121+
class="form-element is-small"
122+
data-character-count=".js-character-count-comment-body"
123+
data-post="${postId}"
124+
data-thread="${threadId}"
125+
name="comment[content]"
126+
rows="6">${data.content}</textarea>
96127
<input type="submit" class="button is-muted is-filled" value="Update comment" />
97-
<input type="button" name="js-discard-edit" data-comment-id="${commentId}" value="Discard Edit" class="button is-danger is-outlined js-discard-edit" />
128+
<input type="button"
129+
class="button is-danger is-outlined js-discard-edit"
130+
data-comment-id="${commentId}"
131+
name="js-discard-edit"
132+
value="Discard Edit" />
98133
<span class="has-float-right has-font-size-caption js-character-count-comment-body"
99134
data-max="1000" data-min="15">
100135
<i class="fas fa-ellipsis-h js-character-count__icon"></i>
101-
<span class="js-character-count__count">${content.length} / 1000</span>
136+
<span class="js-character-count__count">${data.content.length} / 1000</span>
102137
</span>
103138
</form>`;
104139

105140
$commentBody.html(formTemplate);
141+
$commentBody.find('textarea#comment-content').trigger('focus');
106142

107143
$commentBody.find(`#comment-content`).on('keyup', pingable_popup);
108144

109-
$(`.js-discard-edit[data-comment-id="${commentId}"]`).click(() => {
145+
$(`.js-discard-edit[data-comment-id="${commentId}"]`).on('click', () => {
110146
$commentBody.html(originalComment.html());
111147
});
112148
});
@@ -115,13 +151,10 @@ $(() => {
115151
const $tgt = $(evt.target);
116152
const $comment = $tgt.parents('.comment');
117153

118-
if (data.status === 'success') {
154+
QPixel.handleJSONResponse(data, (data) => {
119155
const newComment = $(data.comment);
120156
$comment.html(newComment[0].innerHTML);
121-
}
122-
else {
123-
QPixel.createNotification('danger', data.message);
124-
}
157+
});
125158
});
126159

127160
$(document).on('click', '.js-comment-delete, .js-comment-undelete', async (evt) => {
@@ -132,23 +165,18 @@ $(() => {
132165
const commentId = $comment.attr('data-id');
133166
const isDelete = !$comment.hasClass('deleted-content');
134167

135-
const resp = await QPixel.fetchJSON(`/comments/${commentId}/delete`, {}, { method: isDelete ? 'DELETE' : 'PATCH' });
136-
137-
const data = await resp.json();
168+
const data = await (isDelete ? QPixel.deleteComment(commentId) : QPixel.undeleteComment(commentId));
138169

139-
if (data.status === 'success') {
170+
QPixel.handleJSONResponse(data, () => {
140171
if (isDelete) {
141172
$comment.addClass('deleted-content');
142-
$tgt.removeClass('js-comment-delete').addClass('js-comment-undelete').text('undelete');
173+
$tgt.removeClass('js-comment-delete').addClass('js-comment-undelete').val('undelete');
143174
}
144175
else {
145176
$comment.removeClass('deleted-content');
146-
$tgt.removeClass('js-comment-undelete').addClass('js-comment-delete').text('delete');
177+
$tgt.removeClass('js-comment-undelete').addClass('js-comment-delete').val('delete');
147178
}
148-
}
149-
else {
150-
QPixel.createNotification('danger', data.message);
151-
}
179+
});
152180
});
153181

154182
$(document).on('click', '.js--show-followers', async (evt) => {
@@ -163,38 +191,56 @@ $(() => {
163191
credentials: 'include',
164192
headers: { 'Accept': 'text/html' }
165193
});
194+
166195
const data = await resp.text();
196+
167197
$modal.find('.js-follower-display').html(data);
168198
});
169199

200+
$(document).on('click', '[class*=js--lock-thread] form', async (evt) => {
201+
evt.preventDefault();
202+
203+
const $tgt = $(evt.target);
204+
const threadID = $tgt.data("thread");
205+
206+
const data = await QPixel.lockThread(threadID);
207+
208+
QPixel.handleJSONResponse(data, () => {
209+
window.location.reload();
210+
});
211+
});
212+
170213
$(document).on('click', '.js--restrict-thread, .js--unrestrict-thread', async (evt) => {
171214
evt.preventDefault();
172215

173216
const $tgt = $(evt.target);
174-
const threadID = $tgt.data("thread")
175-
const action = $tgt.data("action")
217+
const threadID = $tgt.data("thread");
218+
const action = $tgt.data("action");
176219
const route = $tgt.hasClass("js--restrict-thread") ? 'restrict' : 'unrestrict';
177220

178221
const resp = await QPixel.fetchJSON(`/comments/thread/${threadID}/${route}`, { type: action });
179222

180223
const data = await resp.json();
181224

182-
if (data.status === 'success') {
225+
QPixel.handleJSONResponse(data, () => {
183226
window.location.reload();
184-
}
185-
else {
186-
QPixel.createNotification('danger', data.message);
187-
}
227+
});
188228
});
189229

190230
$(document).on('click', '.comment-form input[type="submit"]', async (evt) => {
191231
// Comment posting has been clicked.
192232
$(evt.target).attr('data-disable-with', 'Posting...');
193233
});
194234

235+
/**
236+
* @type {Record<`${number}-${number}`, Record<string, number>>}
237+
*/
195238
const pingable = {};
196239
$(document).on('keyup', '.js-comment-field', pingable_popup);
197240

241+
/**
242+
* @type {QPixelPingablePopupCallback}
243+
*/
198244
async function pingable_popup(ev) {
199245
if (QPixel.Popup.isSpecialKey(ev.keyCode)) {
200246
return;
@@ -248,20 +294,36 @@ $(() => {
248294
}
249295
}
250296

251-
$('.js-new-thread-link').on('click', async (ev) => {
297+
$(document).on('click', '.js-new-thread-link', async (ev) => {
252298
ev.preventDefault();
253299
const $tgt = $(ev.target);
254300
const postId = $tgt.attr('data-post');
255301
const $thread = $(`#new-thread-modal-${postId}`);
256302

257303
if ($thread.is(':hidden')) {
258304
$thread.show();
305+
$thread.find('.js-comment-field').trigger('focus');
259306
}
260307
else {
261308
$thread.hide();
262309
}
263310
});
264311

312+
$(document).on('click', '.js-reply-to-thread-link', async (ev) => {
313+
ev.preventDefault();
314+
const $tgt = $(ev.target);
315+
const postId = $tgt.attr('data-post');
316+
const $reply = $(`#reply-to-thread-form-${postId}`);
317+
318+
if ($reply.is(':hidden')) {
319+
$reply.show();
320+
$reply.find('.js-comment-field').trigger('focus');
321+
}
322+
else {
323+
$reply.hide();
324+
}
325+
});
326+
265327
$('.js-comment-permalink > .js-text').text('copy link');
266328
$(document).on('click', '.js-comment-permalink', (ev) => {
267329
ev.preventDefault();

0 commit comments

Comments
 (0)