/**
|
* FrontendInteractions - Unified class for frontend user interactions
|
* Handles: Favourites, Votes, and related user actions
|
*/
|
class UserInteractions {
|
constructor() {
|
if (!window.auth.getUser()) {
|
return; // Don't initialize if not logged in
|
}
|
|
// Initialize favourites store
|
this.favouritesStore = window.jvbStore.register(
|
'favourites',
|
{
|
storeName: 'favourites',
|
endpoint: 'favourites',
|
indexes: [
|
{name: 'content', keyPath: 'content'},
|
{name: 'listId', keyPath: 'listId'},
|
],
|
TTL: 6 * 60 * 1000,
|
showLoading: false,
|
filters: {
|
user: window.auth.getUser(),
|
content: 'all',
|
order: 'desc',
|
orderby: 'date',
|
page: 1,
|
all: true,
|
}
|
}
|
);
|
|
// Initialize favourites lists store
|
this.listsStore = window.jvbStore.register(
|
'favourites_lists',
|
{
|
storeName: 'lists',
|
keyPath: 'listId',
|
endpoint: 'favourites/lists',
|
TTL: 6 * 60 * 1000,
|
}
|
);
|
|
// Initialize votes store
|
this.votesStore = window.jvbStore.register(
|
'votes',
|
{
|
storeName: 'votes',
|
endpoint: 'votes',
|
useIndexedDB: true,
|
TTL: 6 * 60 * 1000,
|
showLoading: false
|
}
|
);
|
|
this.setupEventListeners();
|
this.favouritesStore.fetch();
|
}
|
|
setupEventListeners() {
|
// Subscribe to favourites updates
|
this.favouritesStore.subscribe((event, data) => {
|
switch (event) {
|
case 'data-fetched':
|
case 'data-cached':
|
case 'items-updated':
|
case 'item-stored':
|
// Could handle UI updates here
|
break;
|
}
|
});
|
}
|
|
/**
|
* Toggle favourite status
|
* @param {HTMLElement} button - Button element with data attributes
|
*/
|
toggleFavourite(button) {
|
if (!window.auth.getUser()) {
|
window.location.href = jvbSettings.redirect + '&action=register&type=favourites';
|
return;
|
}
|
|
// Toggle UI immediately
|
button.classList.toggle('favourited');
|
const action = button.classList.contains('favourited') ? 'add' : 'remove';
|
const message = button.classList.contains('favourited')
|
? `Added ${button.dataset.type} to favourites.`
|
: `Removed ${button.dataset.type} from favourites.`;
|
|
window.jvbA11y.announce(message);
|
|
// Update button icon
|
button.innerHTML = jvbSettings.icons[button.classList.contains('favourited') ? 'heart-filled' : 'heart'];
|
|
// Save to store
|
this.favouritesStore.setItem(button.dataset.id, {
|
target_id: button.dataset.id,
|
action: action,
|
type: button.dataset.type,
|
artist: button.dataset.artist,
|
});
|
}
|
|
/**
|
* Handle vote action
|
* @param {HTMLElement} button - Vote button element
|
*/
|
handleVote(button) {
|
if (!window.auth.getUser()) {
|
window.location.href = jvbSettings.redirect + '&action=register&type=vote';
|
return;
|
}
|
|
// Queue the vote operation
|
window.jvbQueue.handleVote(button);
|
|
const parent = button.closest('.vote');
|
const alreadyVoted = parent.querySelector('.voted');
|
|
// Handle previous vote if exists
|
if (alreadyVoted) {
|
const count = alreadyVoted.querySelector('.count');
|
if (alreadyVoted.classList.contains('up')) {
|
count.textContent = parseInt(count.textContent) - 1;
|
} else {
|
count.textContent = parseInt(count.textContent) + 1;
|
}
|
alreadyVoted.classList.remove('voted');
|
}
|
|
// Update current vote
|
button.classList.add('voted');
|
const count = button.querySelector('.count');
|
if (button.classList.contains('up')) {
|
count.textContent = parseInt(count.textContent) + 1;
|
} else {
|
count.textContent = parseInt(count.textContent) - 1;
|
}
|
}
|
|
/**
|
* Check if an item is favourited
|
* @param {string} content - Content type
|
* @param {string|number} id - Item ID
|
* @returns {boolean}
|
*/
|
isFavourited(content, id) {
|
if (!window.auth.getUser()) {
|
return false;
|
}
|
if (typeof window.userFavourites === 'undefined') {
|
return false;
|
}
|
if (typeof window.userFavourites[content] === 'undefined') {
|
return false;
|
}
|
return window.userFavourites[content]?.has(id);
|
}
|
|
/**
|
* Check if user has voted on an item
|
* @param {string} content - Content type
|
* @param {string|number} id - Item ID
|
* @returns {string} - 'up', 'down', or ''
|
*/
|
checkVoteStatus(content, id) {
|
if (!window.auth.getUser()) {
|
return '';
|
}
|
let status = '';
|
if (window.userVotes && window.userVotes[content]?.has(id)) {
|
status = window.userVotes[content].get(id);
|
}
|
return status;
|
}
|
}
|
|
// Lazy initialization using requestIdleCallback for better performance
|
function initFrontendInteractions() {
|
if (window.auth.getUser()) {
|
window.jvbInteractions = new FrontendInteractions();
|
}
|
}
|
|
// Initialize after DOM is ready but without blocking render
|
if ('requestIdleCallback' in window) {
|
requestIdleCallback(async function() {
|
window.auth.subscribe((event) => {
|
if (event === 'auth-loaded') {
|
if (document.readyState === 'loading') {
|
document.addEventListener('DOMContentLoaded', initFrontendInteractions);
|
} else {
|
initFrontendInteractions();
|
}
|
}
|
});
|
});
|
} else {
|
// Fallback for browsers without requestIdleCallback
|
if (document.readyState === 'loading') {
|
document.addEventListener('DOMContentLoaded', initFrontendInteractions);
|
} else {
|
setTimeout(initFrontendInteractions, 1);
|
}
|
}
|
|
/**
|
* Global helper functions for backwards compatibility
|
*/
|
window.toggleFavourite = function(button) {
|
if (!window.jvbInteractions) {
|
console.warn('FrontendInteractions not initialized');
|
return;
|
}
|
window.jvbInteractions.toggleFavourite(button);
|
}
|
|
window.handleVote = function(button) {
|
if (!window.jvbInteractions) {
|
console.warn('FrontendInteractions not initialized');
|
return;
|
}
|
window.jvbInteractions.handleVote(button);
|
}
|
|
window.isFavourited = function(content, id) {
|
if (!window.jvbInteractions) {
|
return false;
|
}
|
return window.jvbInteractions.isFavourited(content, id);
|
}
|
|
window.checkVoteStatus = function(content, id) {
|
if (!window.jvbInteractions) {
|
return '';
|
}
|
return window.jvbInteractions.checkVoteStatus(content, id);
|
}
|
|
|
/**
|
* Formats vote from template
|
* @param item
|
* @param status
|
* @returns {Node|ActiveX.IXMLDOMNode|boolean}
|
*/
|
window.formatVote = function(item, status) {
|
let vote = window.getTemplate('voteButton');
|
|
vote.dataset.itemId = item.id;
|
vote.dataset.content = item.content;
|
let up =vote.querySelector('button.up');
|
let down =vote.querySelector('button.down');
|
|
if(status === 'up'){
|
up.classList.add('voted');
|
}
|
if(status === 'down'){
|
down.classList.add('voted');
|
}
|
if(item.upvotes > 0){
|
up.querySelector('.count').textContent = item.upvotes;
|
}
|
if(item.downvotes > 0){
|
down.querySelector('.count').textContent = '-'+item.downvotes;
|
}
|
|
return vote;
|
}
|
|
|
/**
|
* Tests if user has voted for this item
|
* @param content
|
* @param id
|
* @returns {string}
|
*/
|
window.checkVoteStatus = function(content, id){
|
if(!window.auth.getUser()){
|
return '';
|
}
|
let status = '';
|
if(window.userVotes && window.userVotes[content]?.has(id)){
|
status = window.userVotes[content].get(id);
|
}
|
|
return status;
|
}
|