You've already forked wp-fedistream
feat: Initial release v0.1.0
WP FediStream - Stream music over ActivityPub Features: - Custom post types: Artist, Album, Track, Playlist - Custom taxonomies: Genre, Mood, License - User roles: Artist, Label - Admin dashboard with statistics - Frontend templates and shortcodes - Audio player with queue management - ActivityPub integration with actor support - WooCommerce product types for albums/tracks - User library with favorites and history - Notification system (in-app and email) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
353
assets/js/notifications.js
Normal file
353
assets/js/notifications.js
Normal file
@@ -0,0 +1,353 @@
|
||||
/**
|
||||
* FediStream Notifications JavaScript
|
||||
*
|
||||
* @package WP_FediStream
|
||||
*/
|
||||
|
||||
( function( $ ) {
|
||||
'use strict';
|
||||
|
||||
const Notifications = {
|
||||
unreadCount: 0,
|
||||
isOpen: false,
|
||||
pollTimer: null,
|
||||
|
||||
init: function() {
|
||||
this.createDropdown();
|
||||
this.bindEvents();
|
||||
this.loadNotifications();
|
||||
this.startPolling();
|
||||
},
|
||||
|
||||
createDropdown: function() {
|
||||
const dropdown = `
|
||||
<div class="fedistream-notifications-dropdown" style="display: none;">
|
||||
<div class="notifications-header">
|
||||
<h4>${fedistreamNotifications.i18n.viewAll || 'Notifications'}</h4>
|
||||
<button class="mark-all-read" title="${fedistreamNotifications.i18n.markAllRead}">
|
||||
<span class="dashicons dashicons-yes-alt"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="notifications-list">
|
||||
<div class="loading">${fedistreamNotifications.i18n.loading || 'Loading...'}</div>
|
||||
</div>
|
||||
<div class="notifications-footer">
|
||||
<a href="#" class="view-all">${fedistreamNotifications.i18n.viewAll}</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$( '.fedistream-notifications-menu' ).append( dropdown );
|
||||
},
|
||||
|
||||
bindEvents: function() {
|
||||
const self = this;
|
||||
|
||||
// Toggle dropdown.
|
||||
$( document ).on( 'click', '#wp-admin-bar-fedistream-notifications > a', function( e ) {
|
||||
e.preventDefault();
|
||||
self.toggleDropdown();
|
||||
} );
|
||||
|
||||
// Close dropdown when clicking outside.
|
||||
$( document ).on( 'click', function( e ) {
|
||||
if ( ! $( e.target ).closest( '.fedistream-notifications-menu' ).length ) {
|
||||
self.closeDropdown();
|
||||
}
|
||||
} );
|
||||
|
||||
// Mark all as read.
|
||||
$( document ).on( 'click', '.fedistream-notifications-dropdown .mark-all-read', function( e ) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
self.markAllRead();
|
||||
} );
|
||||
|
||||
// Mark single as read.
|
||||
$( document ).on( 'click', '.notification-item', function() {
|
||||
const id = $( this ).data( 'id' );
|
||||
const isRead = $( this ).hasClass( 'is-read' );
|
||||
|
||||
if ( ! isRead ) {
|
||||
self.markRead( id, $( this ) );
|
||||
}
|
||||
} );
|
||||
|
||||
// Delete notification.
|
||||
$( document ).on( 'click', '.notification-item .delete-btn', function( e ) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const item = $( this ).closest( '.notification-item' );
|
||||
const id = item.data( 'id' );
|
||||
self.deleteNotification( id, item );
|
||||
} );
|
||||
},
|
||||
|
||||
toggleDropdown: function() {
|
||||
if ( this.isOpen ) {
|
||||
this.closeDropdown();
|
||||
} else {
|
||||
this.openDropdown();
|
||||
}
|
||||
},
|
||||
|
||||
openDropdown: function() {
|
||||
$( '.fedistream-notifications-dropdown' ).show();
|
||||
this.isOpen = true;
|
||||
this.loadNotifications();
|
||||
},
|
||||
|
||||
closeDropdown: function() {
|
||||
$( '.fedistream-notifications-dropdown' ).hide();
|
||||
this.isOpen = false;
|
||||
},
|
||||
|
||||
loadNotifications: function() {
|
||||
const self = this;
|
||||
|
||||
$.ajax( {
|
||||
url: fedistreamNotifications.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fedistream_get_notifications',
|
||||
nonce: fedistreamNotifications.nonce,
|
||||
limit: 10
|
||||
},
|
||||
success: function( response ) {
|
||||
if ( response.success ) {
|
||||
self.renderNotifications( response.data.notifications );
|
||||
self.updateUnreadCount( response.data.unread_count );
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$( '.notifications-list' ).html(
|
||||
'<p class="error">' + fedistreamNotifications.i18n.error + '</p>'
|
||||
);
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
renderNotifications: function( notifications ) {
|
||||
const container = $( '.notifications-list' );
|
||||
container.empty();
|
||||
|
||||
if ( ! notifications || notifications.length === 0 ) {
|
||||
container.html(
|
||||
'<p class="empty">' + fedistreamNotifications.i18n.noNotifications + '</p>'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.forEach( function( notification ) {
|
||||
container.append( this.createNotificationItem( notification ) );
|
||||
}, this );
|
||||
},
|
||||
|
||||
createNotificationItem: function( notification ) {
|
||||
const isRead = notification.is_read ? 'is-read' : '';
|
||||
const icon = this.getNotificationIcon( notification.type );
|
||||
const time = this.formatTime( notification.created_at );
|
||||
const link = this.getNotificationLink( notification );
|
||||
|
||||
return `
|
||||
<div class="notification-item ${isRead}" data-id="${notification.id}">
|
||||
<div class="notification-icon">
|
||||
<span class="dashicons dashicons-${icon}"></span>
|
||||
</div>
|
||||
<div class="notification-content">
|
||||
<div class="notification-title">${this.escapeHtml( notification.title )}</div>
|
||||
<div class="notification-message">${this.escapeHtml( notification.message )}</div>
|
||||
<div class="notification-time">${time}</div>
|
||||
</div>
|
||||
<div class="notification-actions">
|
||||
<button class="delete-btn" title="Delete">
|
||||
<span class="dashicons dashicons-no-alt"></span>
|
||||
</button>
|
||||
</div>
|
||||
${link ? `<a href="${link}" class="notification-link"></a>` : ''}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
getNotificationIcon: function( type ) {
|
||||
const icons = {
|
||||
'new_release': 'album',
|
||||
'new_follower': 'admin-users',
|
||||
'fediverse_like': 'heart',
|
||||
'fediverse_boost': 'megaphone',
|
||||
'playlist_added': 'playlist-audio',
|
||||
'purchase': 'cart',
|
||||
'system': 'info'
|
||||
};
|
||||
return icons[ type ] || 'bell';
|
||||
},
|
||||
|
||||
getNotificationLink: function( notification ) {
|
||||
const data = notification.data || {};
|
||||
|
||||
if ( data.album_url ) {
|
||||
return data.album_url;
|
||||
}
|
||||
if ( data.track_url ) {
|
||||
return data.track_url;
|
||||
}
|
||||
if ( data.artist_url ) {
|
||||
return data.artist_url;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
markRead: function( notificationId, element ) {
|
||||
const self = this;
|
||||
|
||||
$.ajax( {
|
||||
url: fedistreamNotifications.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fedistream_mark_notification_read',
|
||||
nonce: fedistreamNotifications.nonce,
|
||||
notification_id: notificationId
|
||||
},
|
||||
success: function( response ) {
|
||||
if ( response.success ) {
|
||||
element.addClass( 'is-read' );
|
||||
self.updateUnreadCount( response.data.unread_count );
|
||||
}
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
markAllRead: function() {
|
||||
const self = this;
|
||||
|
||||
$.ajax( {
|
||||
url: fedistreamNotifications.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fedistream_mark_all_notifications_read',
|
||||
nonce: fedistreamNotifications.nonce
|
||||
},
|
||||
success: function( response ) {
|
||||
if ( response.success ) {
|
||||
$( '.notification-item' ).addClass( 'is-read' );
|
||||
self.updateUnreadCount( 0 );
|
||||
}
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
deleteNotification: function( notificationId, element ) {
|
||||
const self = this;
|
||||
|
||||
$.ajax( {
|
||||
url: fedistreamNotifications.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fedistream_delete_notification',
|
||||
nonce: fedistreamNotifications.nonce,
|
||||
notification_id: notificationId
|
||||
},
|
||||
success: function( response ) {
|
||||
if ( response.success ) {
|
||||
element.fadeOut( 200, function() {
|
||||
$( this ).remove();
|
||||
|
||||
// Check if list is empty.
|
||||
if ( $( '.notification-item' ).length === 0 ) {
|
||||
$( '.notifications-list' ).html(
|
||||
'<p class="empty">' + fedistreamNotifications.i18n.noNotifications + '</p>'
|
||||
);
|
||||
}
|
||||
} );
|
||||
self.updateUnreadCount( response.data.unread_count );
|
||||
}
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
updateUnreadCount: function( count ) {
|
||||
this.unreadCount = count;
|
||||
|
||||
const badge = $( '.fedistream-notification-count' );
|
||||
|
||||
if ( count > 0 ) {
|
||||
if ( badge.length ) {
|
||||
badge.text( count );
|
||||
} else {
|
||||
$( '#wp-admin-bar-fedistream-notifications .ab-icon' ).after(
|
||||
'<span class="fedistream-notification-count">' + count + '</span>'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
badge.remove();
|
||||
}
|
||||
},
|
||||
|
||||
startPolling: function() {
|
||||
const self = this;
|
||||
const interval = fedistreamNotifications.pollInterval || 60000;
|
||||
|
||||
this.pollTimer = setInterval( function() {
|
||||
if ( ! self.isOpen ) {
|
||||
self.checkForNewNotifications();
|
||||
}
|
||||
}, interval );
|
||||
},
|
||||
|
||||
checkForNewNotifications: function() {
|
||||
const self = this;
|
||||
|
||||
$.ajax( {
|
||||
url: fedistreamNotifications.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fedistream_get_notifications',
|
||||
nonce: fedistreamNotifications.nonce,
|
||||
unread_only: true,
|
||||
limit: 1
|
||||
},
|
||||
success: function( response ) {
|
||||
if ( response.success ) {
|
||||
self.updateUnreadCount( response.data.unread_count );
|
||||
}
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
formatTime: function( dateString ) {
|
||||
const date = new Date( dateString );
|
||||
const now = new Date();
|
||||
const diff = Math.floor( ( now - date ) / 1000 );
|
||||
|
||||
if ( diff < 60 ) {
|
||||
return fedistreamNotifications.i18n.justNow || 'Just now';
|
||||
} else if ( diff < 3600 ) {
|
||||
const mins = Math.floor( diff / 60 );
|
||||
return mins + 'm ago';
|
||||
} else if ( diff < 86400 ) {
|
||||
const hours = Math.floor( diff / 3600 );
|
||||
return hours + 'h ago';
|
||||
} else if ( diff < 604800 ) {
|
||||
const days = Math.floor( diff / 86400 );
|
||||
return days + 'd ago';
|
||||
} else {
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
},
|
||||
|
||||
escapeHtml: function( text ) {
|
||||
const div = document.createElement( 'div' );
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize on document ready.
|
||||
$( document ).ready( function() {
|
||||
if ( $( '.fedistream-notifications-menu' ).length ) {
|
||||
Notifications.init();
|
||||
}
|
||||
} );
|
||||
|
||||
} )( jQuery );
|
||||
Reference in New Issue
Block a user