philomena/assets/js/interactions.js

236 lines
6.4 KiB
JavaScript
Raw Normal View History

2019-10-05 02:09:52 +02:00
/**
* Interactions.
*/
import { fetchJson } from './utils/requests';
import { $ } from './utils/dom';
2019-10-05 02:09:52 +02:00
const endpoints = {
vote(imageId) { return `/images/${imageId}/vote`; },
fave(imageId) { return `/images/${imageId}/fave`; },
hide(imageId) { return `/images/${imageId}/hide`; },
2019-10-05 02:09:52 +02:00
};
const spoilerDownvoteMsg =
'Neigh! - Remove spoilered tags from your filters to downvote from thumbnails';
/* Quick helper function to less verbosely iterate a QSA */
function onImage(id, selector, cb) {
[].forEach.call(
document.querySelectorAll(`${selector}[data-image-id="${id}"]`), cb);
}
/* Since JS modifications to webpages, except form inputs, are not stored
* in the browser navigation history, we store a cache of the changes in a
* form to allow interactions to persist on navigation. */
function getCache() {
const cacheEl = $('.js-interaction-cache');
return Object.values(JSON.parse(cacheEl.value));
}
function modifyCache(callback) {
const cacheEl = $('.js-interaction-cache');
cacheEl.value = JSON.stringify(callback(JSON.parse(cacheEl.value)));
}
function cacheStatus(imageId, interactionType, value) {
modifyCache(cache => {
cache[`${imageId}${interactionType}`] = { imageId, interactionType, value };
return cache;
});
}
function uncacheStatus(imageId, interactionType) {
modifyCache(cache => {
delete cache[`${imageId}${interactionType}`];
return cache;
});
}
2019-10-05 02:09:52 +02:00
function setScore(imageId, data) {
onImage(imageId, '.score',
el => el.textContent = data.score);
onImage(imageId, '.favorites',
2019-11-17 03:20:33 +01:00
el => el.textContent = data.faves);
2019-10-05 02:09:52 +02:00
onImage(imageId, '.upvotes',
el => el.textContent = data.upvotes);
onImage(imageId, '.downvotes',
el => el.textContent = data.downvotes);
}
/* These change the visual appearance of interaction links.
* Their classes also effect their behavior due to event delegation. */
function showUpvoted(imageId) {
cacheStatus(imageId, 'voted', 'up');
2019-10-05 02:09:52 +02:00
onImage(imageId, '.interaction--upvote',
el => el.classList.add('active'));
}
function showDownvoted(imageId) {
cacheStatus(imageId, 'voted', 'down');
2019-10-05 02:09:52 +02:00
onImage(imageId, '.interaction--downvote',
el => el.classList.add('active'));
}
function showFaved(imageId) {
cacheStatus(imageId, 'faved', '');
2019-10-05 02:09:52 +02:00
onImage(imageId, '.interaction--fave',
el => el.classList.add('active'));
}
function showHidden(imageId) {
cacheStatus(imageId, 'hidden', '');
2019-10-05 02:09:52 +02:00
onImage(imageId, '.interaction--hide',
el => el.classList.add('active'));
}
function resetVoted(imageId) {
uncacheStatus(imageId, 'voted');
2019-10-05 02:09:52 +02:00
onImage(imageId, '.interaction--upvote',
el => el.classList.remove('active'));
onImage(imageId, '.interaction--downvote',
el => el.classList.remove('active'));
}
function resetFaved(imageId) {
uncacheStatus(imageId, 'faved');
2019-10-05 02:09:52 +02:00
onImage(imageId, '.interaction--fave',
el => el.classList.remove('active'));
}
function resetHidden(imageId) {
uncacheStatus(imageId, 'hidden');
2019-10-05 02:09:52 +02:00
onImage(imageId, '.interaction--hide',
el => el.classList.remove('active'));
}
2019-11-17 03:20:33 +01:00
function interact(type, imageId, method, data = {}) {
return fetchJson(method, endpoints[type](imageId), data)
2019-10-05 02:09:52 +02:00
.then(res => res.json())
.then(res => setScore(imageId, res));
}
function displayInteractionSet(interactions) {
interactions.forEach(i => {
switch (i.interaction_type) {
case 'faved':
showFaved(i.image_id);
break;
case 'hidden':
showHidden(i.image_id);
break;
default:
if (i.value === 'up') showUpvoted(i.image_id);
if (i.value === 'down') showDownvoted(i.image_id);
}
});
}
function loadInteractions() {
/* Set up the actual interactions */
displayInteractionSet(window.booru.interactions);
displayInteractionSet(getCache());
2019-10-05 02:09:52 +02:00
/* Next part is only for image index pages
* TODO: find a better way to do this */
2020-09-12 03:30:04 +02:00
if (!document.getElementById('imagelist-container')) return;
2019-10-05 02:09:52 +02:00
/* Users will blind downvote without this */
window.booru.imagesWithDownvotingDisabled.forEach(i => {
onImage(i, '.interaction--downvote', a => {
// TODO Use a 'js-' class to target these instead
const icon = a.querySelector('i') || a.querySelector('.oc-icon-small');
icon.setAttribute('title', spoilerDownvoteMsg);
a.classList.add('disabled');
a.addEventListener('click', event => {
event.stopPropagation();
event.preventDefault();
}, true);
});
});
}
const targets = {
/* Active-state targets first */
'.interaction--upvote.active'(imageId) {
2019-11-17 03:20:33 +01:00
interact('vote', imageId, 'DELETE')
2019-10-05 02:09:52 +02:00
.then(() => resetVoted(imageId));
},
'.interaction--downvote.active'(imageId) {
2019-11-17 03:20:33 +01:00
interact('vote', imageId, 'DELETE')
2019-10-05 02:09:52 +02:00
.then(() => resetVoted(imageId));
},
'.interaction--fave.active'(imageId) {
2019-11-17 03:20:33 +01:00
interact('fave', imageId, 'DELETE')
2019-10-05 02:09:52 +02:00
.then(() => resetFaved(imageId));
},
'.interaction--hide.active'(imageId) {
2019-11-17 03:20:33 +01:00
interact('hide', imageId, 'DELETE')
2019-10-05 02:09:52 +02:00
.then(() => resetHidden(imageId));
},
/* Inactive targets */
'.interaction--upvote:not(.active)'(imageId) {
2019-11-17 03:20:33 +01:00
interact('vote', imageId, 'POST', { up: true })
2019-10-05 02:09:52 +02:00
.then(() => { resetVoted(imageId); showUpvoted(imageId); });
},
'.interaction--downvote:not(.active)'(imageId) {
2019-11-17 03:20:33 +01:00
interact('vote', imageId, 'POST', { up: false })
2019-10-05 02:09:52 +02:00
.then(() => { resetVoted(imageId); showDownvoted(imageId); });
},
'.interaction--fave:not(.active)'(imageId) {
2019-11-17 03:20:33 +01:00
interact('fave', imageId, 'POST')
2019-10-05 02:09:52 +02:00
.then(() => { resetVoted(imageId); showFaved(imageId); showUpvoted(imageId); });
},
'.interaction--hide:not(.active)'(imageId) {
2019-11-17 03:20:33 +01:00
interact('hide', imageId, 'POST')
2019-10-05 02:09:52 +02:00
.then(() => { showHidden(imageId); });
},
};
function bindInteractions() {
document.addEventListener('click', event => {
if (event.button === 0) { // Is it a left-click?
for (const target in targets) {
/* Event delegation doesn't quite grab what we want here. */
2019-10-05 02:09:52 +02:00
const link = event.target && event.target.closest(target);
if (link) {
event.preventDefault();
targets[target](link.dataset.imageId);
}
}
}
});
}
function loggedOutInteractions() {
[].forEach.call(document.querySelectorAll('.interaction--fave,.interaction--upvote,.interaction--downvote'),
2020-08-02 01:04:48 +02:00
a => a.setAttribute('href', '/sessions/new'));
2019-10-05 02:09:52 +02:00
}
function setupInteractions() {
if (window.booru.userIsSignedIn) {
bindInteractions();
loadInteractions();
}
else {
loggedOutInteractions();
}
}
export { setupInteractions, displayInteractionSet };