diff --git a/index.html b/index.html index a564724..0cc4e9d 100644 --- a/index.html +++ b/index.html @@ -3108,6 +3108,19 @@ +
+
+ Intercept Back to Close Modals + When pressing back, close open modals/panels first without navigating. + Press back again to actually go back. +
+ +
diff --git a/js/app.js b/js/app.js index 5507f3b..5af1126 100644 --- a/js/app.js +++ b/js/app.js @@ -2117,6 +2117,14 @@ document.addEventListener('DOMContentLoaded', async () => { return; } + // Intercept back navigation to close modals first if setting is enabled + if (event && modalSettings.shouldInterceptBackToClose() && modalSettings.hasOpenModalsOrPanels()) { + sidePanelManager.close(); + modalSettings.closeAllModals(); + history.pushState(history.state || { app: true }, '', window.location.pathname); + return; + } + // Close side panel (queue/lyrics) and modals on navigation if setting is enabled if (modalSettings.shouldCloseOnNavigation()) { sidePanelManager.close(); @@ -2293,6 +2301,12 @@ function showUpdateNotification(updateCallback) { }); } +function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + function showMissingTracksNotification(missingTracks) { const modal = document.getElementById('missing-tracks-modal'); const listUl = document.getElementById('missing-tracks-list-ul'); @@ -2301,7 +2315,7 @@ function showMissingTracksNotification(missingTracks) { .map((track) => { const text = typeof track === 'string' ? track : `${track.artist ? track.artist + ' - ' : ''}${track.title}`; - return `
  • ${text}
  • `; + return `
  • ${escapeHtml(text)}
  • `; }) .join(''); diff --git a/js/player.js b/js/player.js index 4839f4e..a094d17 100644 --- a/js/player.js +++ b/js/player.js @@ -8,6 +8,7 @@ import { getTrackArtistsHTML, getTrackYearDisplay, createQualityBadgeHTML, + escapeHtml, } from './utils.js'; import { queueManager, @@ -166,7 +167,7 @@ export class Player { if (coverEl) coverEl.src = this.api.getCoverUrl(track.album?.cover); if (titleEl) { const qualityBadge = createQualityBadgeHTML(track); - titleEl.innerHTML = `${trackTitle} ${qualityBadge}`; + titleEl.innerHTML = `${escapeHtml(trackTitle)} ${qualityBadge}`; } if (albumEl) { const albumTitle = track.album?.title || ''; @@ -356,7 +357,8 @@ export class Player { const yearDisplay = getTrackYearDisplay(track); document.querySelector('.now-playing-bar .cover').src = this.api.getCoverUrl(track.album?.cover); - document.querySelector('.now-playing-bar .title').innerHTML = `${trackTitle} ${createQualityBadgeHTML(track)}`; + document.querySelector('.now-playing-bar .title').innerHTML = + `${escapeHtml(trackTitle)} ${createQualityBadgeHTML(track)}`; const albumEl = document.querySelector('.now-playing-bar .album'); if (albumEl) { const albumTitle = track.album?.title || ''; diff --git a/js/settings.js b/js/settings.js index 88caab0..a4a03b5 100644 --- a/js/settings.js +++ b/js/settings.js @@ -1919,6 +1919,15 @@ export function initializeSettings(scrobbler, player, api, ui) { }); } + // Intercept Back to Close Modals Toggle + const interceptBackToCloseToggle = document.getElementById('intercept-back-to-close-modals-toggle'); + if (interceptBackToCloseToggle) { + interceptBackToCloseToggle.checked = modalSettings.shouldInterceptBackToClose(); + interceptBackToCloseToggle.addEventListener('change', (e) => { + modalSettings.setInterceptBackToClose(e.target.checked); + }); + } + // Compact Artist Toggle const compactArtistToggle = document.getElementById('compact-artist-toggle'); if (compactArtistToggle) { diff --git a/js/storage.js b/js/storage.js index 20b28ad..97fc89f 100644 --- a/js/storage.js +++ b/js/storage.js @@ -2210,10 +2210,10 @@ export const musicProviderSettings = { export const modalSettings = { STORAGE_KEY: 'close-modals-on-navigation', + INTERCEPT_BACK_KEY: 'intercept-back-to-close-modals', shouldCloseOnNavigation() { try { - // Default to false to preserve existing behavior const saved = localStorage.getItem(this.STORAGE_KEY); if (saved === null) { return false; @@ -2228,6 +2228,54 @@ export const modalSettings = { localStorage.setItem(this.STORAGE_KEY, enabled ? 'true' : 'false'); }, + shouldInterceptBackToClose() { + try { + const saved = localStorage.getItem(this.INTERCEPT_BACK_KEY); + if (saved === null) { + return false; + } + return saved === 'true'; + } catch { + return false; + } + }, + + setInterceptBackToClose(enabled) { + localStorage.setItem(this.INTERCEPT_BACK_KEY, enabled ? 'true' : 'false'); + }, + + hasOpenModalsOrPanels() { + const sidePanel = document.getElementById('side-panel'); + if (sidePanel && sidePanel.classList.contains('active')) { + return true; + } + if (document.querySelector('.modal.active')) { + return true; + } + if (document.querySelector('.modal-overlay')) { + return true; + } + const modalIds = [ + 'playlist-modal', + 'folder-modal', + 'playlist-select-modal', + 'shortcuts-modal', + 'missing-tracks-modal', + 'sleep-timer-modal', + 'discography-download-modal', + 'custom-db-modal', + 'tracker-modal', + 'epilepsy-warning-modal', + ]; + for (const id of modalIds) { + const modal = document.getElementById(id); + if (modal && modal.classList.contains('active')) { + return true; + } + } + return false; + }, + closeAllModals() { // Close all modal overlays document.querySelectorAll('.modal-overlay').forEach((modal) => { diff --git a/js/utils.js b/js/utils.js index 10be675..d95c626 100644 --- a/js/utils.js +++ b/js/utils.js @@ -285,14 +285,17 @@ export const getTrackArtistsHTML = (track = {}, { fallback = 'Unknown Artist' } if (track?.artists?.length) { return track.artists .map((artist) => { + const escapedName = escapeHtml(artist.name || 'Unknown Artist'); + const escapedId = escapeHtml(artist.id || ''); // Check if this is a tracker/unreleased track const isTracker = track.isTracker || (track.id && String(track.id).startsWith('tracker-')); if (isTracker && track.trackerInfo?.sheetId) { + const escapedSheetId = escapeHtml(track.trackerInfo.sheetId); // For tracker tracks, link to the tracker artist page - return `${artist.name}`; + return `${escapedName}`; } // For normal tracks, use the artist ID - return `${artist.name}`; + return `${escapedName}`; }) .join(', '); }