Skip to content

Commit e6694c9

Browse files
authored
Merge pull request #1814 from codidact/0valt/voting-improvements
JSON response handling improvements
2 parents e398cfc + 3453a96 commit e6694c9

File tree

9 files changed

+157
-100
lines changed

9 files changed

+157
-100
lines changed

app/assets/javascripts/filters.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ $(() => {
122122
async function saveFilter() {
123123
if (!$form[0].reportValidity()) { return; }
124124

125-
const filter = /** @type {QPixelFlag} */({});
125+
const filter = /** @type {QPixelFilter} */({});
126126

127127
for (const el of $formFilters) {
128128
filter[el.dataset.name] = $(el).val();

app/assets/javascripts/notifications.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ $(() => {
2121
return template;
2222
};
2323

24+
/**
25+
* @param {number} change
26+
*/
2427
const changeInboxCount = (change) => {
2528
const counter = $('.inbox-count');
2629
let count;

app/assets/javascripts/qpixel_api.js

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ window.QPixel = {
110110
},
111111

112112
/**
113-
* @type {QPixelFlag[]|null}
113+
* @type {QPixelFilter[]|null}
114114
*/
115115
_filters: null,
116116

@@ -206,7 +206,8 @@ window.QPixel = {
206206
headers: { 'Accept': 'application/json' }
207207
});
208208

209-
const data = await resp.json();
209+
/** @type {QPixelResponseJSON<{ preferences: UserPreferences }>} */
210+
const data = await QPixel.parseJSONResponse(resp, 'Failed to save preference');
210211

211212
QPixel.handleJSONResponse(data, (data) => {
212213
QPixel._updatePreferencesLocally(data.preferences);
@@ -239,27 +240,19 @@ window.QPixel = {
239240
return data.name;
240241
},
241242

242-
setFilterAsDefault: async (categoryId, name) => {
243-
await QPixel.fetchJSON(`/categories/${categoryId}/filters/default`, { name }, {
244-
headers: { 'Accept': 'application/json' }
245-
});
246-
},
247-
248243
setFilter: async (name, filter, category, isDefault) => {
249244
const resp = await QPixel.fetchJSON('/users/me/filters',
250245
Object.assign(filter, {name, category, is_default: isDefault}), {
251246
headers: { 'Accept': 'application/json' }
252247
});
248+
249+
/** @type {QPixelResponseJSON<{ filters: QPixelFilter[] }>} */
250+
const data = await QPixel.parseJSONResponse(resp, 'Failed to save filter');
253251

254-
const data = await resp.json();
255-
if (data.status !== 'success') {
256-
console.error(`Filter persist failed (${name})`);
257-
console.error(resp);
258-
}
259-
else {
252+
QPixel.handleJSONResponse(data, (data) => {
260253
this._filters = data.filters;
261254
QPixel.Storage?.set('user_filters', this._filters);
262-
}
255+
});
263256
},
264257

265258
deleteFilter: async (name, system = false) => {
@@ -268,16 +261,13 @@ window.QPixel = {
268261
method: 'DELETE'
269262
});
270263

271-
const data = await resp.json();
264+
/** @type {QPixelResponseJSON<{ filters: QPixelFilter[] }>} */
265+
const data = await QPixel.parseJSONResponse(resp, 'Failed to delete filter');
272266

273-
if (data.status !== 'success') {
274-
console.error(`Filter deletion failed (${name})`);
275-
console.error(resp);
276-
}
277-
else {
267+
QPixel.handleJSONResponse(data, (data) => {
278268
this._filters = data.filters;
279269
QPixel.Storage?.set('user_filters', this._filters);
280-
}
270+
});
281271
},
282272

283273
_preferencesLocalStorageKey: () => {
@@ -410,11 +400,30 @@ window.QPixel = {
410400
return content;
411401
},
412402

403+
parseJSONResponse: async (response, errorMessage) => {
404+
try {
405+
const data = await response.json();
406+
407+
return data;
408+
}
409+
catch (error) {
410+
if (response.ok) {
411+
console.error(error);
412+
}
413+
414+
return {
415+
status: 'failed',
416+
message: errorMessage
417+
};
418+
}
419+
},
420+
413421
handleJSONResponse: (data, onSuccess, onFinally) => {
422+
const is_modified = data.status === 'modified';
414423
const is_success = data.status === 'success';
415424

416-
if (is_success) {
417-
onSuccess(data);
425+
if (is_modified || is_success) {
426+
onSuccess(/** @type {Parameters<typeof onSuccess>[0]} */(data));
418427
}
419428
else {
420429
QPixel.createNotification('danger', data.message);
@@ -430,9 +439,16 @@ window.QPixel = {
430439
headers: { 'Accept': 'application/json' }
431440
});
432441

433-
const data = await resp.json();
442+
return QPixel.parseJSONResponse(resp, 'Failed to flag');
443+
},
434444

435-
return data;
445+
vote: async (postId, voteType) => {
446+
const resp = await QPixel.fetchJSON('/votes/new', {
447+
post_id: postId,
448+
vote_type: voteType
449+
});
450+
451+
return QPixel.parseJSONResponse(resp, 'Failed to vote');
436452
},
437453

438454
deleteComment: async (id) => {
@@ -441,19 +457,15 @@ window.QPixel = {
441457
method: 'DELETE'
442458
});
443459

444-
const data = await resp.json();
445-
446-
return data;
460+
return QPixel.parseJSONResponse(resp, 'Failed to delete comment');
447461
},
448462

449463
followComments: async (postId) => {
450464
const resp = await QPixel.fetchJSON(`/comments/post/${postId}/follow`, {}, {
451465
headers: { 'Accept': 'application/json' }
452466
});
453467

454-
const data = await resp.json();
455-
456-
return data;
468+
return QPixel.parseJSONResponse(resp, 'Failed to follow post comments');
457469
},
458470

459471
deleteDraft: async () => {
@@ -463,9 +475,7 @@ window.QPixel = {
463475
headers: { 'Accept': 'application/json' }
464476
});
465477

466-
const data = await resp.json();
467-
468-
return data;
478+
return QPixel.parseJSONResponse(resp, 'Failed to delete post draft');
469479
},
470480

471481
undeleteComment: async (id) => {
@@ -474,39 +484,37 @@ window.QPixel = {
474484
method: 'PATCH'
475485
});
476486

477-
const data = await resp.json();
478-
479-
return data;
487+
return QPixel.parseJSONResponse(resp, 'Failed to undelete comment');
480488
},
481489

482490
unfollowComments: async (postId) => {
483491
const resp = await QPixel.fetchJSON(`/comments/post/${postId}/unfollow`, {}, {
484492
headers: { 'Accept': 'application/json' }
485493
});
486494

487-
const data = await resp.json();
488-
489-
return data;
495+
return QPixel.parseJSONResponse(resp, 'Failed to unfollow post comments');
490496
},
491497

492498
lockThread: async (id) => {
493499
const resp = await QPixel.fetchJSON(`/comments/thread/${id}/restrict`, {
494500
type: 'lock'
495501
});
496502

497-
const data = await resp.json();
498-
499-
return data;
503+
return QPixel.parseJSONResponse(resp, 'Failed to lock thread');
500504
},
501505

502506
renameTag: async (categoryId, tagId, name) => {
503507
const resp = await QPixel.fetchJSON(`/categories/${categoryId}/tags/${tagId}/rename`, { name }, {
504508
headers: { 'Accept': 'application/json' }
505509
});
506510

507-
const data = await resp.json();
511+
return QPixel.parseJSONResponse(resp, 'Failed to rename tag');
512+
},
508513

509-
return data;
514+
retractVote: async (id) => {
515+
const resp = await QPixel.fetchJSON(`/votes/${id}`, {}, { method: 'DELETE' });
516+
517+
return QPixel.parseJSONResponse(resp, 'Failed to retract vote');
510518
},
511519

512520
saveDraft: async (draft) => {
@@ -515,8 +523,6 @@ window.QPixel = {
515523
path: location.pathname
516524
});
517525

518-
const data = await resp.json();
519-
520-
return data;
526+
return QPixel.parseJSONResponse(resp, 'Failed to save draft');
521527
},
522528
};

app/assets/javascripts/votes.js

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
$(() => {
1+
document.addEventListener('DOMContentLoaded', () => {
22
$(document).on('click', '.vote-button', async (evt) => {
33
const $tgt = $(evt.target).is('button') ? $(evt.target) : $(evt.target).parents('button');
44
const $post = $tgt.parents('.post');
@@ -7,38 +7,27 @@ $(() => {
77

88
const $up = $container.find('.js-upvote-count');
99
const $down = $container.find('.js-downvote-count');
10+
const postId = $post.data('post-id');
1011
const voteType = $tgt.data('vote-type');
1112
const voted = $tgt.hasClass('is-active');
1213

1314
if (voted) {
1415
const voteId = $tgt.attr('data-vote-id');
1516

16-
const resp = await QPixel.fetchJSON(`/votes/${voteId}`, {}, { method: 'DELETE' });
17+
const data = await QPixel.retractVote(voteId);
1718

18-
const data = await resp.json();
19-
20-
if (data.status === 'OK') {
19+
QPixel.handleJSONResponse(data, (data) => {
2120
$up.text(`+${data.upvotes}`);
2221
$down.html(`&minus;${data.downvotes}`);
2322
$container.attr("title", `Score: ${data.score}`);
2423
$tgt.removeClass('is-active')
2524
.removeAttr('data-vote-id');
26-
}
27-
else {
28-
console.error('Vote delete failed');
29-
console.log(resp);
30-
QPixel.createNotification('danger', `<strong>Failed:</strong> ${data.message} (${resp.status})`);
31-
}
25+
});
3226
}
3327
else {
34-
const resp = await QPixel.fetchJSON('/votes/new', {
35-
post_id: $post.data('post-id'),
36-
vote_type: voteType
37-
});
28+
const data = await QPixel.vote(postId, voteType);
3829

39-
const data = await resp.json();
40-
41-
if (data.status === 'modified' || data.status === 'OK') {
30+
QPixel.handleJSONResponse(data, (data) => {
4231
$up.text(`+${data.upvotes}`);
4332
$down.html(`&minus;${data.downvotes}`);
4433
$container.attr("title", `Score: ${data.score}`);
@@ -50,12 +39,7 @@ $(() => {
5039
$oppositeVote.removeClass('is-active')
5140
.removeAttr('data-vote-id');
5241
}
53-
}
54-
else {
55-
console.error('Vote create failed');
56-
console.log(resp);
57-
QPixel.createNotification('danger', `<strong>Failed:</strong> ${data.message} (${resp.status})`);
58-
}
42+
});
5943
}
6044
});
6145
});

app/controllers/votes_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def create
4242
AbilityQueue.add(post.user, "Vote Change on ##{post.id}")
4343

4444
modified = !destroyed.empty?
45-
state = { status: (modified ? 'modified' : 'OK'),
45+
state = { status: (modified ? 'modified' : 'success'),
4646
vote_id: vote.id,
4747
upvotes: post.upvote_count,
4848
downvotes: post.downvote_count,
@@ -62,7 +62,7 @@ def destroy
6262

6363
if vote.destroy
6464
AbilityQueue.add(post.user, "Vote Change on ##{post.id}")
65-
render json: { status: 'OK',
65+
render json: { status: 'success',
6666
upvotes: post.upvote_count,
6767
downvotes: post.downvote_count,
6868
score: post.score }

0 commit comments

Comments
 (0)