class NewsManager {
|
constructor(){
|
this.queue = window.jvbQueue;
|
this.loading = window.jvbLoading;
|
this.cache = window.jvbCache;
|
this.a11y = window.jvbA11y;
|
this.error = window.jvbError;
|
this.activeTab = 'all';
|
|
this.tabs = new window.jvbTabs(document.querySelector('.replace'), {
|
'news': () => {
|
this.activeTab = 'all';
|
this.resetFilters();
|
this.loadItems(true).then(()=>{});
|
},
|
'mine': () => {
|
console.log('switching to mine tab');
|
this.activeTab = 'own';
|
this.resetFilters();
|
this.filters.artist = window.auth.getUser();
|
this.loadItems(true).then(()=>{});
|
},
|
'watching': () => {
|
this.activeTab = 'watching';
|
this.resetFilters();
|
this.filters.watched = true;
|
this.loadItems(true).then(()=>{});
|
}
|
});
|
|
|
this.isLoading = false;
|
this.alreadyHandling = false;
|
|
this.template = new Map();
|
|
this.endpoints = {
|
news: 'news',
|
vote: 'news/vote'
|
};
|
this.resetFilters();
|
this.state = {
|
hasMore: true,
|
pages: 1,
|
items: 0
|
};
|
this.initElements();
|
this.initEvents();
|
|
this.loadItems();
|
}
|
|
resetFilters(){
|
this.filters = {
|
page: 1,
|
order: 'DESC',
|
orderby: 'date',
|
shop: null,
|
type: null,
|
artist: null,
|
watched: false,
|
}
|
}
|
|
initElements(){
|
this.container = document.querySelector('.replace');
|
this.grid = this.container.querySelector('.item-grid');
|
this.addButton = this.container.querySelector('.add-item-btn');
|
this.addModal = new window.jvbModal(
|
this.container.querySelector('.create-modal'),
|
{
|
render: this.renderModal.bind(this),
|
open: this.addButton,
|
content: 'news',
|
openMessage: 'Opened modal to create a news post.',
|
onSave: this.saveModal.bind(this),
|
});
|
this.filterForm = this.container.querySelector('form');
|
this.dateRangeFilter = new window.jvbModal(
|
this.container.querySelector('dialog.date-range'),
|
{
|
open: false,
|
});
|
this.clearFilters = this.container.querySelector('.clear-filters');
|
|
this.replyModal = new window.jvbModal(
|
this.container.querySelector('.create-response'),
|
{
|
open: false,
|
content: 'response',
|
openMessage: 'Opened Response modal',
|
onSave: this.saveCreatedResponse.bind(this)
|
});
|
}
|
|
initEvents(){
|
this.filterForm.addEventListener('change', (e) => {
|
let value = e.target.value;
|
if(e.target.closest('.date-range')){
|
return;
|
}
|
if(value === 'custom'){
|
this.handleCustomDateRange();
|
}else{
|
let name = e.target.name;
|
if(name){
|
this.filters[name] = value;
|
}else{
|
this.resetFilters();
|
}
|
|
this.loadItems(true);
|
}
|
});
|
|
|
document.addEventListener('click', (e) => {
|
if(e.target === this.clearFilters){
|
this.filterForm.reset();
|
this.resetFilters();
|
this.loadItems(true);
|
}
|
|
if(e.target.closest('button.reply')){
|
//TODO:
|
//1) get content of what we are responding to, and add it to .original
|
//2) get the ID of the item we are responding to
|
|
let button = e.target.closest('button');
|
let itemID = button.closest('.item').dataset.id;
|
|
let original = '';
|
if(button.dataset.type === 'news'){
|
original = button.closest('.item').querySelector('.item-info').innerHTML;
|
}else{
|
original = button.closest('.response').querySelector('.content').innerHTML;
|
this.replyModal.modal.dataset.parent_id = button.id.replace('reply-to', '');
|
}
|
|
this.replyModal.modal.dataset.id = itemID;
|
this.replyModal.modal.dataset.type = button.dataset.type;
|
this.replyModal.modal.querySelector('.original').innerHTML = '<h5>Replying to:</h5>'+original;
|
|
|
this.replyModal.handleOpen();
|
}
|
});
|
}
|
|
renderModal(){
|
|
}
|
|
handleCustomDateRange(){
|
this.dateRangeFilter.handleOpen();
|
|
let dateStart = this.dateRangeFilter.modal.querySelector('input.date-start');
|
let dateEnd = this.dateRangeFilter.modal.querySelector('input.date-end');
|
let custom = this.dateRangeFilter.modal.querySelector('select');
|
let inputs = this.dateRangeFilter.modal.querySelectorAll('input, select').forEach(input => {
|
input.addEventListener('change', (e) => {
|
|
//We need both beginning and end filters in order to parse the request
|
if((input === dateStart && dateEnd.value !== '') || (input === dateEnd && dateStart.value !== '')){
|
this.filters.dateFrom = dateStart.value;
|
this.filters.dateTo = dateEnd.value;
|
this.dateRangeFilter.handleClose();
|
this.loadItems(true);
|
}else if (input === custom){
|
//Or one of the preset months
|
this.filters.customDate = custom.value;
|
this.dateRangeFilter.handleClose();
|
this.loadItems(true);
|
}
|
});
|
});
|
|
// this.alreadyHandling = false;
|
}
|
|
async saveModal(form){
|
const formData = new FormData(this.addModal.modal.querySelector('form'));
|
|
formData.append('user', window.auth.getUser());
|
this.queue.addToQueue({
|
type: 'new_news',
|
data: formData,
|
});
|
}
|
|
/**
|
* Load favourites from the server
|
* @returns {Promise<Object>} Response data
|
*/
|
async loadItems(reset = true) {
|
|
if(this.isLoading) return;
|
|
try {
|
this.isLoading = true;
|
this.loading.show();
|
|
if(reset){
|
this.filters.page = 1;
|
removeChildren(this.grid);
|
this.grid.classList.remove('empty');
|
}
|
const params = this.buildFilters();
|
|
const data = await this.cache.fetchWithCache(
|
`${jvbSettings.api}${this.endpoints.news}?${params.toString()}`,
|
{
|
method: 'GET',
|
headers: {
|
'X-WP-Nonce': window.auth.getNonce(),
|
'X-Action-Nonce': window.auth.getNonce('dash'),
|
}
|
},{
|
context: 'news',
|
forceRefresh: true, //TODO: set false
|
}
|
);
|
|
|
// Process and render the favourites
|
this.renderItems(data.items || [], this.filters.page > 1);
|
|
// Update pagination info
|
if (data.pagination) {
|
this.state = {
|
hasMore: data.has_more,
|
items: data.items,
|
pages: data.pages
|
};
|
}
|
|
|
return data;
|
} catch (error) {
|
this.handleError(error, 'loading news');
|
throw error;
|
} finally {
|
this.isLoading = false;
|
this.loading.hide();
|
}
|
}
|
|
buildFilters(){
|
//Clone to avoid modifying original
|
const filters = JSON.parse(JSON.stringify(this.filters));
|
|
let temp = {};
|
for(var[name, value] of Object.entries(filters)){
|
if(value !== false && value !== null){
|
temp[name] = value;
|
}
|
}
|
|
return new URLSearchParams(temp);
|
}
|
|
renderItems(items, append = false){
|
if(!append){
|
removeChildren(this.grid);
|
}
|
|
if(items.length === 0){
|
this.a11y.announceItems(0, append);
|
this.showEmptyState();
|
return;
|
}
|
|
// Use DocumentFragment for better performance
|
const fragment = document.createDocumentFragment();
|
|
// Process items in batches for better performance
|
const batchSize = 10;
|
const processBatch = (startIndex) => {
|
const endIndex = Math.min(startIndex + batchSize, items.length);
|
|
// Process this batch
|
for (let i = startIndex; i < endIndex; i++) {
|
const item = items[i];
|
const element = this.createItemElement(item);
|
fragment.appendChild(element);
|
}
|
|
// If we have more items, process next batch in next frame
|
if (endIndex < items.length) {
|
requestAnimationFrame(() => {
|
processBatch(endIndex);
|
});
|
} else {
|
// All batches processed, append fragment
|
this.grid.appendChild(fragment);
|
this.a11y.makeNavigable(this.grid.querySelectorAll('.item:not([data-keyboard-nav])'));
|
this.a11y.announceItems(items.length, append, this.state.hasMore);
|
}
|
};
|
|
// Start processing the first batch
|
if (items.length > 0) {
|
processBatch(0);
|
} else {
|
this.a11y.announceItems(0, append);
|
}
|
}
|
|
createItemElement(item){
|
const itemEl = window.getTemplate(`template-${this.activeTab}`);
|
|
itemEl.id = `news-${item.id}`;
|
itemEl.dataset.id = item.id;
|
|
const [title] = itemEl.getElementsByTagName('h3');
|
const [published] =itemEl.getElementsByClassName('published');
|
const [artist] = itemEl.getElementsByClassName('artist');
|
const [shop] = itemEl.getElementsByClassName('shop');
|
const [tldr] = itemEl.getElementsByClassName('tldr');
|
const [content] = itemEl.getElementsByClassName('item-info');
|
const [img] = itemEl.getElementsByClassName('image');
|
|
[title.textContent, published.textContent, artist.href, artist.textContent,tldr.textContent, content.innerHTML] = [item.title, formatTimeAgo(item.date), item.artist.url, item.artist.name, item.tldr, item['post_content']];
|
|
if(item.shop){
|
[shop.href, shop.innerHTML] = [item.shop.url, jvbSettings.icons.shop + item.shop.name];
|
|
}else{
|
shop.hidden = true;
|
}
|
const [favourite] = itemEl.getElementsByClassName('favourite-button');
|
if(this.activeTab !== 'own'){
|
|
[favourite.dataset.id, favourite.dataset.artist] = [item.id, item.artist.id];
|
if(window.userFavourites.news?.includes(parseInt(item.id))){
|
removeChildren(favourite);
|
favourite.append(getIcon('star-fi'));
|
}else{
|
removeChildren(favourite);
|
favourite.append(getIcon('star'));
|
}
|
}else{
|
favourite.hidden = true;
|
const [select] = itemEl.getElementsByClassName('select-checkbox');
|
const [selectLabel] = itemEl.getElementsByTagName('label');
|
[select.id, select.value, selectLabel.for] = [`item-${item.id}`, item.id, `item-${item.id}`];
|
}
|
|
|
let status = '';
|
if(window.userVotes?.news?.has(item.id)){
|
status = window.userVotes.news.get(item.id);
|
}
|
|
console.log(item);
|
itemEl.querySelector('.summary').appendChild(formatVote(item, status));
|
let commentButton = window.getTemplate('commentsButton');
|
commentButton.href = (`#responses-to-${item.id}`);
|
commentButton.querySelector('.count').textContent = item.comments.items.length;
|
|
|
let responses = window.getTemplate('responses');
|
responses.id = `responses-to-${item.id}`;
|
let summary =responses.querySelector('summary');
|
summary.textContent += ' { '+item.comments.items.length + ' }';
|
let reply = window.getTemplate('replyButton');
|
reply.id = 'reply-to-'+item.id;
|
reply.dataset.type = 'news';
|
reply.dataset.action = 'reply';
|
itemEl.appendChild(reply);
|
|
|
if(item.comments.items.length > 0){
|
item.comments.items.forEach(comment => {
|
responses.appendChild(this.formatComment(comment));
|
});
|
}
|
itemEl.appendChild(responses);
|
|
itemEl.querySelector('.vote').prepend(commentButton,itemEl.querySelector('.vote button'));
|
|
const imageHTML = (item.image) ? item.image.replace(/src="([^"]+)"/, 'data-src="$1"') : '';
|
|
return itemEl;
|
}
|
|
formatComment(comment, parent = null){
|
let response = window.getTemplate('response');
|
response.id = 'response-'+comment.id;
|
|
let summary = response.querySelector('summary');
|
summary.querySelector('.content').innerHTML = comment.response;
|
summary.querySelector('.created').textContent = formatTimeAgo(comment.created_at);
|
|
|
|
//Add voting buttons
|
let status = checkVoteStatus('response',comment.id);
|
comment.content = 'response';
|
response.querySelector('.footer').appendChild(formatVote(comment, status));
|
|
|
console.log(comment);
|
let reply = window.getTemplate('replyButton');
|
reply.id = 'reply-to-'+comment.id;
|
if(parent){
|
reply.dataset.parent_id = parent;
|
}
|
reply.dataset.action = 'reply';
|
reply.dataset.type = comment.content;
|
summary.querySelector('.vote').prepend(reply, summary.querySelector('.vote').firstElementChild);
|
|
let artist = summary.querySelector('.artist');
|
let shop = summary.querySelector('.shop');
|
console.log(comment);
|
if(comment.artist){
|
if(!comment.artist.shop){
|
shop.remove();
|
}
|
[artist.href, artist.textContent, shop.href, shop.textContent] = [comment.artist.url, comment.artist.name, comment.artist.shop.url, comment.artist.shop.name];
|
}else{
|
artist.remove();
|
shop.remove();
|
}
|
|
|
//Add any replies
|
if(comment.children.items.length>0){
|
let responses = window.getTemplate('responses');
|
responses.id = 'replies-to-'+comment.id;
|
responses.querySelector('summary').textContent = 'See Responses {'+comment.children.items.length+'}';
|
comment.children.items.forEach(item=>{
|
responses.appendChild(this.formatComment(item, comment.id));
|
});
|
response.appendChild(responses);
|
}
|
return response;
|
}
|
|
|
renderResponseCreate(){
|
|
}
|
saveCreatedResponse(){
|
console.log('Saving create response');
|
console.log(this.replyModal.modal.id)
|
const modal = this.replyModal.modal;
|
|
let data = {
|
user: window.auth.getUser(),
|
item_id: modal.dataset.id,
|
response: modal.querySelector('.ql-editor').innerHTML,
|
content: modal.dataset.type,
|
action: 'create',
|
};
|
if(modal.dataset.parent_id){
|
data.parent_id = modal.dataset.parent_id;
|
}
|
|
console.log(data);
|
|
this.queue.addToQueue({
|
type: 'new_response',
|
data: data,
|
});
|
}
|
|
showEmptyState(){
|
const empty = document.createElement('div');
|
empty.className = 'no-news';
|
empty.innerHTML = `
|
<h3>Nothing here</h3>
|
<p>No updates here.</p>
|
<p>Add some gap fillers from the main favourites tab.</p>
|
`;
|
this.grid.appendChild(empty);
|
this.grid.classList.add('empty');
|
this.a11y.announce('No favourites to show!');
|
}
|
|
hideEmptyState(){
|
let empty = this.grid.querySelector('.no-news');
|
if(empty) {
|
empty.remove();
|
}
|
}
|
|
/**
|
* Handle errors
|
* @param {Error} error - Error object
|
* @param {string} action - Action being performed when error occurred
|
*/
|
handleError(error, action) {
|
console.error(`News error (${action}):`, error);
|
|
// Log with error handler if available
|
if (window.jvbError) {
|
window.jvbError.log(error, {
|
component: 'NewsManager',
|
action: action
|
});
|
}
|
|
// Announce to screen readers
|
if (window.jvbA11y) {
|
window.jvbA11y.announce(`Error ${action}. ${error.message || 'Please try again.'}`);
|
}
|
}
|
|
}
|
window.newsManager = NewsManager;
|