Skip to content

Commit 05e8eba

Browse files
authored
Merge branch 'develop' into art/mod-spam-tools
2 parents 75566ae + e2a295b commit 05e8eba

45 files changed

Lines changed: 920 additions & 337 deletions

Some content is hidden

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

app/assets/javascripts/comments.js

Lines changed: 111 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,13 @@ $(() => {
1919
return $tgt.closest('.js-comment-thread-wrapper')[0] ?? null;
2020
};
2121

22-
$(document).on('click', '.post--comments-thread.is-inline a', async (evt) => {
23-
if (evt.ctrlKey) { return; }
24-
25-
evt.preventDefault();
26-
27-
const $tgt = $(evt.target);
28-
const $threadId = $tgt.data('thread');
29-
const wrapper = getCommentThreadWrapper($tgt);
30-
31-
openThread(wrapper, $threadId);
32-
});
22+
/**
23+
* @param {HTMLElement} wrapper
24+
* @returns {boolean}
25+
*/
26+
const isInlineCommentThread = (wrapper) => {
27+
return !!wrapper.querySelector('[data-inline=true]');
28+
};
3329

3430
/**
3531
* @param {HTMLElement} wrapper
@@ -45,8 +41,24 @@ $(() => {
4541
window.hljs && hljs.highlightAll();
4642
}
4743

44+
$(document).on('click', '.post--comments-thread.is-inline a', async (evt) => {
45+
if (evt.ctrlKey) {
46+
return; // TODO: do we need this early exit?
47+
}
48+
49+
evt.preventDefault();
50+
51+
const $tgt = $(evt.target);
52+
const $threadId = $tgt.data('thread');
53+
const wrapper = getCommentThreadWrapper($tgt);
54+
55+
openThread(wrapper, $threadId);
56+
});
57+
4858
$(document).on('click', '.js-show-deleted-comments', (ev) => {
49-
if (ev.ctrlKey) { return; } // do we really need it?
59+
if (ev.ctrlKey) {
60+
return;
61+
} // do we really need it?
5062

5163
ev.preventDefault();
5264

@@ -80,7 +92,9 @@ $(() => {
8092
}
8193

8294
if (isDeleted) {
83-
$container.append(`<i class="fas fa-trash h-c-red-600 fa-fw" title="Deleted thread" aria-label="Deleted thread"></i>`);
95+
$container.append(
96+
`<i class="fas fa-trash h-c-red-600 fa-fw" title="Deleted thread" aria-label="Deleted thread"></i>`
97+
);
8498
$container.addClass('is-deleted');
8599
}
86100

@@ -171,8 +185,7 @@ $(() => {
171185
if (isDelete) {
172186
$comment.addClass('deleted-content');
173187
$tgt.removeClass('js-comment-delete').addClass('js-comment-undelete').val('undelete');
174-
}
175-
else {
188+
} else {
176189
$comment.removeClass('deleted-content');
177190
$tgt.removeClass('js-comment-undelete').addClass('js-comment-delete').val('delete');
178191
}
@@ -189,36 +202,90 @@ $(() => {
189202
const $modal = $($tgt.data('modal'));
190203

191204
const resp = await QPixel.fetch(`/comments/thread/${threadId}/followers`, {
192-
headers: { 'Accept': 'text/html' }
205+
headers: { Accept: 'text/html' }
193206
});
194207

195208
const data = await resp.text();
196209

197210
$modal.find('.js-follower-display').html(data);
198211
});
199212

200-
$(document).on('click', '[class*=js--lock-thread] form', async (evt) => {
201-
evt.preventDefault();
213+
$(document).on('click', '.js-archive-thread', async (ev) => {
214+
ev.preventDefault();
202215

203-
const $tgt = $(evt.target);
204-
const threadID = $tgt.data("thread");
216+
const $tgt = $(ev.target);
217+
const threadID = $tgt.data('thread');
205218

206-
const data = await QPixel.lockThread(threadID);
219+
const data = await QPixel.archiveThread(threadID);
207220

208221
QPixel.handleJSONResponse(data, () => {
209-
window.location.reload();
222+
const wrapper = getCommentThreadWrapper($tgt);
223+
const inline = isInlineCommentThread(wrapper);
224+
openThread(wrapper, threadID, { inline });
210225
});
211226
});
212227

213-
$(document).on('click', '.js--restrict-thread, .js--unrestrict-thread', async (evt) => {
228+
$(document).on('click', '.js-delete-thread', async (ev) => {
229+
ev.preventDefault();
230+
231+
const $tgt = $(ev.target);
232+
const threadID = $tgt.data('thread');
233+
234+
const data = await QPixel.deleteThread(threadID);
235+
236+
QPixel.handleJSONResponse(data, () => {
237+
const wrapper = getCommentThreadWrapper($tgt);
238+
const inline = isInlineCommentThread(wrapper);
239+
openThread(wrapper, threadID, { inline });
240+
});
241+
});
242+
243+
$(document).on('click', '.js-follow-thread', async (ev) => {
244+
ev.preventDefault();
245+
246+
const $tgt = $(ev.target);
247+
const threadID = $tgt.data('thread');
248+
249+
const data = await QPixel.followThread(threadID);
250+
251+
QPixel.handleJSONResponse(data, () => {
252+
const wrapper = getCommentThreadWrapper($tgt);
253+
const inline = isInlineCommentThread(wrapper);
254+
openThread(wrapper, threadID, { inline });
255+
});
256+
});
257+
258+
$(document).on('click', '.js-lock-thread', async (ev) => {
259+
ev.preventDefault();
260+
261+
const $tgt = $(ev.target);
262+
const threadID = $tgt.data('thread');
263+
const form = $tgt.closest(`form[data-thread=${threadID}]`).get(0);
264+
265+
if (form instanceof HTMLFormElement) {
266+
const { value: duration } = form.elements['duration'] ?? {};
267+
268+
const data = await QPixel.lockThread(threadID, duration ? Math.round(+duration) : void 0);
269+
270+
QPixel.handleJSONResponse(data, () => {
271+
const wrapper = getCommentThreadWrapper($tgt);
272+
const inline = isInlineCommentThread(wrapper);
273+
openThread(wrapper, threadID, { inline });
274+
});
275+
} else {
276+
QPixel.createNotification('danger', 'Failed to find thread to lock');
277+
}
278+
});
279+
280+
// TODO: split into individual handlers once unrestrict_thread is split
281+
$(document).on('click', '.js--unrestrict-thread', async (evt) => {
214282
evt.preventDefault();
215283

216284
const $tgt = $(evt.target);
217-
const threadID = $tgt.data("thread");
218-
const action = $tgt.data("action");
219-
const route = $tgt.hasClass("js--restrict-thread") ? 'restrict' : 'unrestrict';
285+
const threadID = $tgt.data('thread');
286+
const action = $tgt.data('action');
220287

221-
const resp = await QPixel.fetchJSON(`/comments/thread/${threadID}/${route}`, { type: action });
288+
const resp = await QPixel.fetchJSON(`/comments/thread/${threadID}/unrestrict`, { type: action });
222289

223290
const data = await resp.json();
224291

@@ -261,7 +328,7 @@ $(() => {
261328
const $item = $(ev.target).hasClass('item') ? $(ev.target) : $(ev.target).parents('.item');
262329
const id = $item.data('user-id');
263330
$tgt[0].selectionStart = caretPos - posInWord;
264-
$tgt[0].selectionEnd = (caretPos - posInWord) + currentWord.length;
331+
$tgt[0].selectionEnd = caretPos - posInWord + currentWord.length;
265332
QPixel.replaceSelection($tgt, `@#${id}`);
266333
popup.destroy();
267334
$tgt.focus();
@@ -282,17 +349,20 @@ $(() => {
282349
pingable[`${threadId}-${postId}`] = await resp.json();
283350
}
284351

285-
const items = Object.entries(pingable[`${threadId}-${postId}`]).filter((e) => {
286-
return e[0].toLowerCase().startsWith(currentWord.substr(1).toLowerCase());
287-
}).map((e) => {
288-
const username = e[0].replace(/</g, '&#x3C;').replace(/>/g, '&#x3E;');
289-
const id = e[1];
290-
return itemTemplate.clone().html(`${username} <span class="has-color-tertiary-600">#${id}</span>`)
291-
.attr('data-user-id', id);
292-
});
352+
const items = Object.entries(pingable[`${threadId}-${postId}`])
353+
.filter((e) => {
354+
return e[0].toLowerCase().startsWith(currentWord.substr(1).toLowerCase());
355+
})
356+
.map((e) => {
357+
const username = e[0].replace(/</g, '&#x3C;').replace(/>/g, '&#x3E;');
358+
const id = e[1];
359+
return itemTemplate
360+
.clone()
361+
.html(`${username} <span class="has-color-tertiary-600">#${id}</span>`)
362+
.attr('data-user-id', id);
363+
});
293364
QPixel.Popup.getPopup(items, $tgt[0], callback);
294-
}
295-
else {
365+
} else {
296366
QPixel.Popup.destroyAll();
297367
}
298368
}
@@ -306,8 +376,7 @@ $(() => {
306376
if ($thread.is(':hidden')) {
307377
$thread.show();
308378
$thread.find('.js-comment-field').trigger('focus');
309-
}
310-
else {
379+
} else {
311380
$thread.hide();
312381
}
313382
});
@@ -322,8 +391,7 @@ $(() => {
322391
if ($reply.is(':hidden')) {
323392
$reply.show();
324393
$reply.find('.js-comment-field').trigger('focus');
325-
}
326-
else {
394+
} else {
327395
$reply.hide();
328396
}
329397
});
@@ -358,9 +426,7 @@ $(() => {
358426

359427
const shouldFollow = action === 'follow';
360428

361-
const data = shouldFollow ?
362-
await QPixel.followComments(postId) :
363-
await QPixel.unfollowComments(postId);
429+
const data = shouldFollow ? await QPixel.followComments(postId) : await QPixel.unfollowComments(postId);
364430

365431
QPixel.handleJSONResponse(data, () => {
366432
target.dataset.action = shouldFollow ? 'unfollow' : 'follow';

app/assets/javascripts/qpixel_api.js

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ window.QPixel = {
131131
}
132132

133133
const myselfPromise = QPixel.fetch('/users/me', {
134-
headers: { 'Accept': 'application/json' }
134+
headers: {
135+
'Accept': 'application/json',
136+
'Cache-Control': 'no-cache',
137+
}
135138
});
136139

137140
QPixel._pendingUserResponse = myselfPromise;
@@ -451,6 +454,38 @@ window.QPixel = {
451454
return QPixel.parseJSONResponse(resp, 'Failed to vote');
452455
},
453456

457+
archiveThread: async (id) => {
458+
const resp = await QPixel.fetchJSON(`/comments/thread/${id}/archive`, {}, {
459+
headers: { 'Accept': 'application/json' },
460+
});
461+
462+
return QPixel.parseJSONResponse(resp, 'Failed to archive thread');
463+
},
464+
465+
deleteThread: async (id) => {
466+
const resp = await QPixel.fetchJSON(`/comments/thread/${id}/delete`, {}, {
467+
headers: { 'Accept': 'application/json' },
468+
});
469+
470+
return QPixel.parseJSONResponse(resp, 'Failed to delete thread');
471+
},
472+
473+
followThread: async (id) => {
474+
const resp = await QPixel.fetchJSON(`/comments/thread/${id}/follow`, {}, {
475+
headers: { 'Accept': 'application/json' },
476+
});
477+
478+
return QPixel.parseJSONResponse(resp, 'Failed to follow thread');
479+
},
480+
481+
lockThread: async (id, duration) => {
482+
const resp = await QPixel.fetchJSON(`/comments/thread/${id}/lock`, {
483+
duration,
484+
});
485+
486+
return QPixel.parseJSONResponse(resp, 'Failed to lock thread');
487+
},
488+
454489
deleteComment: async (id) => {
455490
const resp = await QPixel.fetchJSON(`/comments/${id}/delete`, {}, {
456491
headers: { 'Accept': 'application/json' },
@@ -495,14 +530,6 @@ window.QPixel = {
495530
return QPixel.parseJSONResponse(resp, 'Failed to unfollow post comments');
496531
},
497532

498-
lockThread: async (id) => {
499-
const resp = await QPixel.fetchJSON(`/comments/thread/${id}/restrict`, {
500-
type: 'lock'
501-
});
502-
503-
return QPixel.parseJSONResponse(resp, 'Failed to lock thread');
504-
},
505-
506533
renameTag: async (categoryId, tagId, name) => {
507534
const resp = await QPixel.fetchJSON(`/categories/${categoryId}/tags/${tagId}/rename`, { name }, {
508535
headers: { 'Accept': 'application/json' }

app/assets/stylesheets/errors.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
.error-report-summary {
2424
display: flex;
2525
align-items: center;
26+
gap: 0.15em;
2627

2728
.details {
2829
flex: 1;
2930
}
30-
}
31+
}

0 commit comments

Comments
 (0)