Fix double audio playback and add UID handling for personal stream

- Fixed double playback issue on stream page by properly scoping event delegation in streams-ui.js
- Added init-personal-stream.js to handle UID for personal stream playback
- Improved error handling and logging for audio playback
- Added proper event propagation control to prevent duplicate event handling
This commit is contained in:
oib
2025-07-18 16:51:39 +02:00
parent 17616ac5b8
commit 402e920bc6
24 changed files with 4074 additions and 1090 deletions

View File

@ -1,136 +1,55 @@
// inject-nav.js - Handles dynamic injection and management of navigation elements
import { showOnly } from './router.js';
// Menu state
let isMenuOpen = false;
// Export the injectNavigation function
export function injectNavigation(isAuthenticated = false) {
console.log('injectNavigation called with isAuthenticated:', isAuthenticated);
const navContainer = document.getElementById('main-navigation');
if (!navContainer) {
console.error('Navigation container not found. Looking for #main-navigation');
console.log('Available elements with id:', document.querySelectorAll('[id]'));
return;
}
// Clear existing content
navContainer.innerHTML = '';
// Function to set up guest navigation links
function setupGuestNav() {
const guestDashboard = document.getElementById('guest-dashboard');
if (!guestDashboard) return;
console.log('Creating navigation...');
try {
// Create the navigation wrapper
const navWrapper = document.createElement('nav');
navWrapper.className = 'nav-wrapper';
// Create the navigation content
const nav = isAuthenticated ? createUserNav() : createGuestNav();
console.log('Navigation HTML created:', nav.outerHTML);
// Append navigation to wrapper
navWrapper.appendChild(nav);
// Append to container
navContainer.appendChild(navWrapper);
console.log('Navigation appended to container');
// Initialize menu toggle after navigation is injected
setupMenuToggle();
// Set up menu links
setupMenuLinks();
// Add click handler for the logo to navigate home
const logo = document.querySelector('.logo');
if (logo) {
logo.addEventListener('click', (e) => {
e.preventDefault();
showOnly('welcome');
closeMenu();
});
}
} catch (error) {
console.error('Error creating navigation:', error);
return;
}
// Set up menu toggle for mobile
setupMenuToggle();
// Set up menu links
setupMenuLinks();
// Close menu when clicking on a nav link on mobile
const navLinks = navContainer.querySelectorAll('.nav-link');
navLinks.forEach(link => {
link.addEventListener('click', () => {
if (window.innerWidth < 768) { // Mobile breakpoint
closeMenu();
}
});
});
// Add click handler for the logo to navigate home
const logo = document.querySelector('.logo');
if (logo) {
logo.addEventListener('click', (e) => {
const links = guestDashboard.querySelectorAll('a');
links.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
showOnly('welcome');
});
}
}
// Function to create the guest navigation
function createGuestNav() {
const nav = document.createElement('div');
nav.className = 'dashboard-nav';
nav.setAttribute('role', 'navigation');
nav.setAttribute('aria-label', 'Main navigation');
const navList = document.createElement('ul');
navList.className = 'nav-list';
const links = [
{ id: 'nav-login', target: 'login', text: 'Login / Register' },
{ id: 'nav-streams', target: 'streams', text: 'Streams' },
{ id: 'nav-welcome', target: 'welcome', text: 'Welcome' }
];
// Create and append links
links.forEach((link) => {
const li = document.createElement('li');
li.className = 'nav-item';
const a = document.createElement('a');
a.id = link.id;
a.href = `#${link.target}`;
a.className = 'nav-link';
a.setAttribute('data-target', link.target);
a.textContent = link.text;
// Add click handler for navigation
a.addEventListener('click', (e) => {
e.preventDefault();
const target = e.currentTarget.getAttribute('data-target');
const target = link.getAttribute('href')?.substring(1); // Remove '#'
if (target) {
window.location.hash = target;
if (window.router && typeof window.router.showOnly === 'function') {
window.router.showOnly(target);
}
// Close menu on mobile after clicking a link
if (window.innerWidth < 768) {
closeMenu();
}
}
});
li.appendChild(a);
navList.appendChild(li);
});
}
// Function to set up user navigation links
function setupUserNav() {
const userDashboard = document.getElementById('user-dashboard');
if (!userDashboard) return;
nav.appendChild(navList);
return nav;
const links = userDashboard.querySelectorAll('a');
links.forEach(link => {
// Handle logout specially
if (link.getAttribute('href') === '#logout') {
link.addEventListener('click', (e) => {
e.preventDefault();
if (window.handleLogout) {
window.handleLogout();
}
});
} else {
// Handle regular navigation
link.addEventListener('click', (e) => {
e.preventDefault();
const target = link.getAttribute('href')?.substring(1); // Remove '#'
if (target) {
window.location.hash = target;
if (window.router && typeof window.router.showOnly === 'function') {
window.router.showOnly(target);
}
}
});
}
});
}
function createUserNav() {
@ -150,7 +69,7 @@ function createUserNav() {
];
// Create and append links
links.forEach((link, index) => {
links.forEach((link) => {
const li = document.createElement('li');
li.className = 'nav-item';
@ -158,35 +77,24 @@ function createUserNav() {
a.id = link.id;
a.href = '#';
a.className = 'nav-link';
// Special handling for logout
if (link.target === 'logout') {
a.href = '#';
a.addEventListener('click', async (e) => {
e.preventDefault();
closeMenu();
// Use the handleLogout function from dashboard.js if available
if (typeof handleLogout === 'function') {
await handleLogout();
} else {
// Fallback in case handleLogout is not available
localStorage.removeItem('user');
localStorage.removeItem('uid');
localStorage.removeItem('uid_time');
localStorage.removeItem('confirmed_uid');
document.cookie = 'uid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
window.location.href = '/';
}
window.location.href = '#';
// Force reload to reset the app state
window.location.reload();
});
} else {
a.setAttribute('data-target', link.target);
}
a.setAttribute('data-target', link.target);
a.textContent = link.text;
a.addEventListener('click', (e) => {
e.preventDefault();
const target = e.currentTarget.getAttribute('data-target');
if (target === 'logout') {
if (window.handleLogout) {
window.handleLogout();
}
} else if (target) {
window.location.hash = target;
if (window.router && typeof window.router.showOnly === 'function') {
window.router.showOnly(target);
}
}
});
li.appendChild(a);
navList.appendChild(li);
});
@ -195,243 +103,82 @@ function createUserNav() {
return nav;
}
// Set up menu toggle functionality
function setupMenuToggle() {
const menuToggle = document.querySelector('.menu-toggle');
const navWrapper = document.querySelector('.nav-wrapper');
if (!menuToggle || !navWrapper) return;
menuToggle.addEventListener('click', toggleMenu);
// Close menu when clicking outside
document.addEventListener('click', (e) => {
if (isMenuOpen && !navWrapper.contains(e.target) && !menuToggle.contains(e.target)) {
closeMenu();
}
});
// Close menu on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isMenuOpen) {
closeMenu();
}
});
// Close menu when resizing to desktop
let resizeTimer;
window.addEventListener('resize', () => {
if (window.innerWidth >= 768) {
closeMenu();
}
});
}
// Toggle mobile menu
function toggleMenu(event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
const navWrapper = document.querySelector('.nav-wrapper');
const menuToggle = document.querySelector('.menu-toggle');
if (!navWrapper || !menuToggle) return;
isMenuOpen = !isMenuOpen;
if (isMenuOpen) {
// Open menu
navWrapper.classList.add('active');
menuToggle.setAttribute('aria-expanded', 'true');
menuToggle.innerHTML = '✕';
document.body.style.overflow = 'hidden';
// Focus the first link in the menu for better keyboard navigation
const firstLink = navWrapper.querySelector('a');
if (firstLink) firstLink.focus();
// Add click outside handler
document._handleClickOutside = (e) => {
if (!navWrapper.contains(e.target) && e.target !== menuToggle) {
closeMenu();
}
};
document.addEventListener('click', document._handleClickOutside);
// Add escape key handler
document._handleEscape = (e) => {
if (e.key === 'Escape') {
closeMenu();
}
};
document.addEventListener('keydown', document._handleEscape);
} else {
closeMenu();
}
}
// Close menu function
function closeMenu() {
const navWrapper = document.querySelector('.nav-wrapper');
const menuToggle = document.querySelector('.menu-toggle');
if (!navWrapper || !menuToggle) return;
isMenuOpen = false;
navWrapper.classList.remove('active');
menuToggle.setAttribute('aria-expanded', 'false');
menuToggle.innerHTML = '☰';
document.body.style.overflow = '';
// Remove event listeners
if (document._handleClickOutside) {
document.removeEventListener('click', document._handleClickOutside);
delete document._handleClickOutside;
}
if (document._handleEscape) {
document.removeEventListener('keydown', document._handleEscape);
delete document._handleEscape;
}
}
// Initialize menu toggle on page load
function initializeMenuToggle() {
console.log('Initializing menu toggle...');
const menuToggle = document.getElementById('menu-toggle');
if (!menuToggle) {
console.error('Main menu toggle button not found!');
return;
}
console.log('Menu toggle button found:', menuToggle);
// Remove any existing click listeners
const newToggle = menuToggle.cloneNode(true);
if (menuToggle.parentNode) {
menuToggle.parentNode.replaceChild(newToggle, menuToggle);
console.log('Replaced menu toggle button');
} else {
console.error('Menu toggle has no parent node!');
return;
}
// Add click handler to the new toggle
newToggle.addEventListener('click', function(event) {
console.log('Menu toggle clicked!', event);
event.preventDefault();
event.stopPropagation();
toggleMenu(event);
return false;
});
// Also handle the header menu toggle if it exists
const headerMenuToggle = document.getElementById('header-menu-toggle');
if (headerMenuToggle) {
console.log('Header menu toggle found, syncing with main menu');
headerMenuToggle.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
newToggle.click(); // Trigger the main menu toggle
return false;
});
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM fully loaded and parsed');
// Initialize navigation based on authentication state
// This will be set by the main app after checking auth status
if (window.initializeNavigation) {
window.initializeNavigation();
}
// Initialize menu toggle
initializeMenuToggle();
// Also try to initialize after a short delay in case the DOM changes
setTimeout(initializeMenuToggle, 500);
});
// Navigation injection function
export function injectNavigation(isAuthenticated = false) {
console.log('Injecting navigation, isAuthenticated:', isAuthenticated);
const container = document.getElementById('main-navigation');
const navWrapper = document.querySelector('.nav-wrapper');
// Get the appropriate dashboard element based on auth state
const guestDashboard = document.getElementById('guest-dashboard');
const userDashboard = document.getElementById('user-dashboard');
if (!container || !navWrapper) {
console.error('Navigation elements not found. Looking for #main-navigation and .nav-wrapper');
return null;
if (isAuthenticated) {
// Show user dashboard, hide guest dashboard
if (guestDashboard) guestDashboard.style.display = 'none';
if (userDashboard) userDashboard.style.display = 'block';
document.body.classList.add('authenticated');
document.body.classList.remove('guest-mode');
} else {
// Show guest dashboard, hide user dashboard
if (guestDashboard) guestDashboard.style.display = 'block';
if (userDashboard) userDashboard.style.display = 'none';
document.body.classList.add('guest-mode');
document.body.classList.remove('authenticated');
}
try {
// Store scroll position
const scrollPosition = window.scrollY;
// Clear existing navigation
container.innerHTML = '';
// Create the appropriate navigation based on authentication status
const nav = isAuthenticated ? createUserNav() : createGuestNav();
// Append the navigation to the container
container.appendChild(nav);
// Set up menu toggle functionality
setupMenuToggle();
// Set up navigation links
setupMenuLinks();
// Show the appropriate page based on URL
if (window.location.hash === '#streams' || window.location.pathname === '/streams') {
showOnly('stream-page');
if (typeof window.maybeLoadStreamsOnShow === 'function') {
window.maybeLoadStreamsOnShow();
}
} else if (!window.location.hash || window.location.hash === '#') {
// Show welcome page by default if no hash
showOnly('welcome-page');
}
// Restore scroll position
window.scrollTo(0, scrollPosition);
return nav;
} catch (error) {
console.error('Error injecting navigation:', error);
return null;
}
// Set up menu links and active state
setupMenuLinks();
updateActiveNav();
return isAuthenticated ? userDashboard : guestDashboard;
}
// Set up menu links with click handlers
function setupMenuLinks() {
// Handle navigation link clicks
document.addEventListener('click', function(e) {
// Check if click is on a nav link or its children
let link = e.target.closest('.nav-link');
if (!link) return;
const target = link.getAttribute('data-target');
if (target) {
e.preventDefault();
console.log('Navigation link clicked:', target);
showOnly(target);
closeMenu();
// Update active state
document.querySelectorAll('.nav-link').forEach(l => {
l.classList.remove('active');
});
// Set up guest and user navigation links
setupGuestNav();
setupUserNav();
// Handle hash changes for SPA navigation
window.addEventListener('hashchange', updateActiveNav);
}
// Update active navigation link
function updateActiveNav() {
const currentHash = window.location.hash.substring(1) || 'welcome';
// Remove active class from all links in both dashboards
document.querySelectorAll('#guest-dashboard a, #user-dashboard a').forEach(link => {
link.classList.remove('active');
// Check if this link's href matches the current hash
const linkTarget = link.getAttribute('href')?.substring(1); // Remove '#'
if (linkTarget === currentHash) {
link.classList.add('active');
}
});
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
// Check authentication state and initialize navigation
const isAuthenticated = document.cookie.includes('sessionid=') ||
localStorage.getItem('isAuthenticated') === 'true';
// Initialize navigation based on authentication state
injectNavigation(isAuthenticated);
// Set up menu links and active navigation
setupMenuLinks();
updateActiveNav();
// Update body classes based on authentication state
if (isAuthenticated) {
document.body.classList.add('authenticated');
document.body.classList.remove('guest-mode');
} else {
document.body.classList.add('guest-mode');
document.body.classList.remove('authenticated');
}
console.log('[NAV] Navigation initialized', { isAuthenticated });
});
// Make the function available globally for debugging
window.injectNavigation = injectNavigation;