2021-04-06 03:44:59 -04:00
|
|
|
|
/* exported BookWyrm */
|
2021-04-06 03:06:12 -04:00
|
|
|
|
/* globals TabGroup */
|
2021-03-31 11:07:28 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
let BookWyrm = new class {
|
|
|
|
|
constructor() {
|
2021-04-06 03:57:52 -04:00
|
|
|
|
this.initOnDOMLoaded();
|
|
|
|
|
this.initReccuringTasks();
|
2021-04-06 03:44:59 -04:00
|
|
|
|
this.initEventListeners();
|
|
|
|
|
}
|
2021-01-18 19:32:02 -05:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
initEventListeners() {
|
|
|
|
|
// buttons that display or hide content
|
|
|
|
|
document.querySelectorAll('[data-controls]')
|
2021-04-06 05:37:23 -04:00
|
|
|
|
.forEach(button => button.addEventListener('click', this.toggleAction.bind(this)));
|
2021-01-29 13:25:31 -05:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
// javascript interactions (boost/fav)
|
|
|
|
|
document.querySelectorAll('.interaction')
|
2021-04-06 05:37:23 -04:00
|
|
|
|
.forEach(button => button.addEventListener('submit', this.interact.bind(this)));
|
2021-03-19 18:59:28 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
// handle aria settings on menus
|
|
|
|
|
document.querySelectorAll('.pulldown-menu')
|
2021-04-06 05:37:23 -04:00
|
|
|
|
.forEach(button => button.addEventListener('click', this.toggleMenu.bind(this)));
|
2021-01-14 16:02:28 -05:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
// hidden submit button in a form
|
|
|
|
|
document.querySelectorAll('.hidden-form input')
|
2021-04-06 05:37:23 -04:00
|
|
|
|
.forEach(button => button.addEventListener('change', this.revealForm.bind(this)));
|
2021-01-19 17:59:46 -05:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
// browser back behavior
|
|
|
|
|
document.querySelectorAll('[data-back]')
|
2021-04-06 05:37:23 -04:00
|
|
|
|
.forEach(button => button.addEventListener('click', this.back));
|
2021-01-18 19:32:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 03:57:52 -04:00
|
|
|
|
/**
|
|
|
|
|
* Execute code once the DOM is loaded.
|
|
|
|
|
*/
|
|
|
|
|
initOnDOMLoaded() {
|
|
|
|
|
window.addEventListener('DOMContentLoaded', function() {
|
2021-04-06 03:44:59 -04:00
|
|
|
|
document.querySelectorAll('.tab-group')
|
2021-04-06 03:57:52 -04:00
|
|
|
|
.forEach(tabs => new TabGroup(tabs));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Execute recurring tasks.
|
|
|
|
|
*/
|
|
|
|
|
initReccuringTasks() {
|
|
|
|
|
// Polling
|
|
|
|
|
document.querySelectorAll('[data-poll]')
|
|
|
|
|
.forEach(liveArea => this.polling(liveArea));
|
2021-01-18 12:57:44 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
/**
|
|
|
|
|
* Go back in browser history.
|
|
|
|
|
*
|
|
|
|
|
* @param {Event} event
|
|
|
|
|
*
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
|
|
|
|
back(event) {
|
|
|
|
|
event.preventDefault();
|
2021-04-06 03:44:59 -04:00
|
|
|
|
history.back();
|
|
|
|
|
}
|
2021-01-16 22:57:20 -05:00
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
/**
|
|
|
|
|
* Update a counter with recurring requests to the API
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} counter - DOM node
|
|
|
|
|
* @param {int} delay - frequency for polling in ms
|
|
|
|
|
*
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
2021-04-06 09:36:34 -04:00
|
|
|
|
polling(counter, delay) {
|
2021-04-06 10:17:20 -04:00
|
|
|
|
const bookwyrm = this;
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
delay = delay || 10000;
|
|
|
|
|
delay += (Math.random() * 1000);
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
setTimeout(function() {
|
2021-04-06 09:36:34 -04:00
|
|
|
|
fetch('/api/updates/' + counter.dataset.poll)
|
2021-04-06 03:44:59 -04:00
|
|
|
|
.then(response => response.json())
|
2021-04-06 10:17:20 -04:00
|
|
|
|
.then(data => bookwyrm.updateCountElement(counter, data));
|
|
|
|
|
|
|
|
|
|
bookwyrm.polling(counter, delay * 1.25);
|
2021-04-06 09:36:34 -04:00
|
|
|
|
}, delay, counter);
|
2021-01-17 13:10:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
/**
|
|
|
|
|
* Update a counter.
|
|
|
|
|
*
|
|
|
|
|
* @param {object} counter - DOM node
|
|
|
|
|
* @param {object} data - json formatted response from a fetch
|
|
|
|
|
*
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
|
|
|
|
updateCountElement(counter, data) {
|
|
|
|
|
const currentCount = counter.innerText;
|
2021-04-06 03:44:59 -04:00
|
|
|
|
const count = data.count;
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
if (count != currentCount) {
|
2021-04-06 10:17:20 -04:00
|
|
|
|
this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'hidden', count < 1);
|
|
|
|
|
counter.innerText = count;
|
2021-04-06 03:44:59 -04:00
|
|
|
|
}
|
2021-01-16 22:57:20 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
/**
|
|
|
|
|
* Toggle form.
|
|
|
|
|
*
|
|
|
|
|
* @param {Event} event
|
|
|
|
|
*
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
|
|
|
|
revealForm(event) {
|
|
|
|
|
let trigger = event.currentTarget;
|
|
|
|
|
let hidden = trigger.closest('.hidden-form').querySelectorAll('.hidden')[0];
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 05:19:44 -04:00
|
|
|
|
this.addRemoveClass(hidden, 'hidden', !hidden);
|
2021-01-16 22:57:20 -05:00
|
|
|
|
}
|
2021-01-17 11:50:47 -05:00
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
/**
|
|
|
|
|
* Execute actions on targets based on triggers.
|
|
|
|
|
*
|
|
|
|
|
* @param {Event} event
|
|
|
|
|
*
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
2021-04-06 09:36:34 -04:00
|
|
|
|
toggleAction(event) {
|
|
|
|
|
let trigger = event.currentTarget;
|
|
|
|
|
let pressed = trigger.getAttribute('aria-pressed') == 'false';
|
|
|
|
|
let targetId = trigger.dataset.controls;
|
2021-04-06 03:44:59 -04:00
|
|
|
|
|
2021-04-06 09:36:34 -04:00
|
|
|
|
// Un‑press all triggers controlling the same target.
|
2021-04-06 03:44:59 -04:00
|
|
|
|
document.querySelectorAll('[data-controls="' + targetId + '"]')
|
2021-04-06 09:36:34 -04:00
|
|
|
|
.forEach(triggers => triggers.setAttribute(
|
|
|
|
|
'aria-pressed',
|
|
|
|
|
(triggers.getAttribute('aria-pressed') == 'false'))
|
|
|
|
|
);
|
2021-04-06 03:44:59 -04:00
|
|
|
|
|
|
|
|
|
if (targetId) {
|
2021-04-06 04:42:52 -04:00
|
|
|
|
let target = document.getElementById(targetId);
|
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
this.addRemoveClass(target, 'hidden', !pressed);
|
|
|
|
|
this.addRemoveClass(target, 'is-active', pressed);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
// Show/hide container.
|
2021-04-06 04:42:52 -04:00
|
|
|
|
let container = document.getElementById('hide-' + targetId);
|
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
if (container) {
|
|
|
|
|
this.addRemoveClass(container, 'hidden', pressed);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
// Check checkbox, if appropriate.
|
2021-04-06 09:36:34 -04:00
|
|
|
|
let checkbox = trigger.dataset['controls-checkbox'];
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
if (checkbox) {
|
|
|
|
|
document.getElementById(checkbox).checked = !!pressed;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
// Set focus, if appropriate.
|
2021-04-06 09:36:34 -04:00
|
|
|
|
let focus = trigger.dataset['focus-target'];
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
if (focus) {
|
2021-04-06 04:42:52 -04:00
|
|
|
|
let focusEl = document.getElementById(focus);
|
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
focusEl.focus();
|
2021-04-06 04:42:52 -04:00
|
|
|
|
setTimeout(function() { focusEl.selectionStart = focusEl.selectionEnd = 10000; }, 0);
|
2021-04-06 03:44:59 -04:00
|
|
|
|
}
|
2021-01-17 11:50:47 -05:00
|
|
|
|
}
|
2021-01-14 16:02:28 -05:00
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
/**
|
|
|
|
|
* Make a request and update the UI accordingly.
|
|
|
|
|
* This function is used for boosts and favourites.
|
|
|
|
|
*
|
|
|
|
|
* @todo Only update status if the promise is successful.
|
|
|
|
|
*
|
|
|
|
|
* @param {Event} event
|
|
|
|
|
*
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
|
|
|
|
interact(event) {
|
|
|
|
|
event.preventDefault();
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
this.ajaxPost(event.target);
|
2020-03-15 21:12:45 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
// @todo This probably should be done with IDs.
|
2021-04-06 10:17:20 -04:00
|
|
|
|
document.querySelectorAll(`.${event.target.dataset.id}`)
|
|
|
|
|
.forEach(node => this.addRemoveClass(
|
|
|
|
|
node,
|
|
|
|
|
'hidden',
|
|
|
|
|
node.className.indexOf('hidden') == -1
|
|
|
|
|
));
|
2021-01-17 23:19:09 -05:00
|
|
|
|
}
|
2020-11-09 14:58:19 -05:00
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
/**
|
|
|
|
|
* Handle ARIA states on toggled menus.
|
|
|
|
|
*
|
|
|
|
|
* @note This function seems to be redundant and conflicts with toggleAction.
|
|
|
|
|
*
|
|
|
|
|
* @param {Event} event
|
|
|
|
|
*
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
|
|
|
|
toggleMenu(event) {
|
|
|
|
|
let trigger = event.currentTarget;
|
|
|
|
|
let expanded = trigger.getAttribute('aria-expanded') == 'false';
|
|
|
|
|
let targetId = trigger.dataset.controls;
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
trigger.setAttribute('aria-expanded', expanded);
|
2021-04-06 04:42:52 -04:00
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
if (targetId) {
|
2021-04-06 04:42:52 -04:00
|
|
|
|
let target = document.getElementById(targetId);
|
|
|
|
|
|
2021-04-06 03:44:59 -04:00
|
|
|
|
this.addRemoveClass(target, 'is-active', expanded);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-18 11:26:04 -05:00
|
|
|
|
|
2021-04-06 10:17:20 -04:00
|
|
|
|
/**
|
|
|
|
|
* Submit a form using POST.
|
|
|
|
|
*
|
|
|
|
|
* @param {object} form - Form to be submitted
|
|
|
|
|
*
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
2021-04-06 03:44:59 -04:00
|
|
|
|
ajaxPost(form) {
|
|
|
|
|
fetch(form.action, {
|
|
|
|
|
method : "POST",
|
|
|
|
|
body: new FormData(form)
|
|
|
|
|
});
|
2021-01-18 11:26:04 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 05:19:44 -04:00
|
|
|
|
/**
|
|
|
|
|
* Add or remove a class based on a boolean condition.
|
|
|
|
|
*
|
|
|
|
|
* @param {object} node - DOM node to change class on
|
|
|
|
|
* @param {string} classname - Name of the class
|
|
|
|
|
* @param {boolean} add - Add?
|
|
|
|
|
* @return {undefined}
|
|
|
|
|
*/
|
|
|
|
|
addRemoveClass(node, classname, add) {
|
|
|
|
|
if (add) {
|
|
|
|
|
node.classList.add(classname);
|
2021-04-06 03:44:59 -04:00
|
|
|
|
} else {
|
2021-04-06 05:19:44 -04:00
|
|
|
|
node.classList.remove(classname);
|
2021-04-06 03:44:59 -04:00
|
|
|
|
}
|
2021-01-18 11:26:04 -05:00
|
|
|
|
}
|
|
|
|
|
}
|