From 02bfc7ba914528b529bc8a759449a7d40d6ce4f7 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:57:05 -0600 Subject: [PATCH 001/525] fix(ui): suppress WebKit default context menu globally --- src/routes/+page.svelte | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a96020802..c913ecaff 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2898,6 +2898,11 @@ // Keyboard navigation document.addEventListener('keydown', handleKeydown); + // Suppress WebKit default context menu globally + // Custom menus (TrackRow, sidebar, etc.) call e.stopPropagation() so they are unaffected + const handleGlobalContextMenu = (e: MouseEvent) => e.preventDefault(); + document.addEventListener('contextmenu', handleGlobalContextMenu); + // Register keybinding actions registerAction('playback.toggle', togglePlay); registerAction('playback.next', handleSkipForward); @@ -3392,6 +3397,7 @@ saveSessionBeforeClose(); cleanupBootstrap(); document.removeEventListener('keydown', handleKeydown); + document.removeEventListener('contextmenu', handleGlobalContextMenu); unregisterAll(); // Cleanup keybinding actions window.removeEventListener('beforeunload', handleBeforeUnload); document.removeEventListener('visibilitychange', handleVisibilityChange); From 66d25b773bc494f4ba9b65f44fb2db98ab924d01 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:57:46 -0600 Subject: [PATCH 002/525] fix(ui): keybindings modal z-index and 2-column layout --- src/lib/components/KeyboardShortcutsModal.svelte | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/components/KeyboardShortcutsModal.svelte b/src/lib/components/KeyboardShortcutsModal.svelte index 7d652ca03..6d556291e 100644 --- a/src/lib/components/KeyboardShortcutsModal.svelte +++ b/src/lib/components/KeyboardShortcutsModal.svelte @@ -148,7 +148,7 @@ display: flex; align-items: center; justify-content: center; - z-index: 1000; + z-index: 3000; backdrop-filter: blur(4px); animation: fadeIn 150ms ease-out; } @@ -162,7 +162,7 @@ background: var(--bg-secondary); border-radius: 12px; width: 90%; - max-width: 500px; + max-width: 660px; max-height: 80vh; display: flex; flex-direction: column; @@ -225,10 +225,14 @@ flex: 1; overflow-y: auto; padding: 20px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0 24px; } .category-section { margin-bottom: 24px; + break-inside: avoid; } .category-section:last-child { @@ -315,6 +319,7 @@ .modal-content { padding: 16px; + grid-template-columns: 1fr; } .shortcut-row { From 37cca5dccf325055be36b03c40bae4bf51150b69 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:58:41 -0600 Subject: [PATCH 003/525] fix(ui): render sanitized HTML in playlist descriptions --- .../views/PlaylistDetailView.svelte | 12 +++++- src/lib/utils/sanitize.ts | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/lib/utils/sanitize.ts diff --git a/src/lib/components/views/PlaylistDetailView.svelte b/src/lib/components/views/PlaylistDetailView.svelte index ac3434aee..fd0364c42 100644 --- a/src/lib/components/views/PlaylistDetailView.svelte +++ b/src/lib/components/views/PlaylistDetailView.svelte @@ -22,6 +22,7 @@ import { isTrackUnavailable, clearTrackUnavailable, subscribe as subscribeUnavailable } from '$lib/stores/unavailableTracksStore'; import { isBlacklisted as isArtistBlacklisted } from '$lib/stores/artistBlacklistStore'; import { showToast } from '$lib/stores/toastStore'; + import { sanitizeHtml } from '$lib/utils/sanitize'; import { get } from 'svelte/store'; import { t } from '$lib/i18n'; import { onMount, tick } from 'svelte'; @@ -2020,7 +2021,7 @@ Playlist

{playlist.name}

{#if playlist.description} -

{playlist.description}

+

{@html sanitizeHtml(playlist.description)}

{/if}
{playlist.owner.name} @@ -2603,6 +2604,15 @@ line-height: 1.4; } + .playlist-description :global(a) { + color: var(--accent-primary); + text-decoration: none; + } + + .playlist-description :global(a:hover) { + text-decoration: underline; + } + .playlist-info { display: flex; align-items: center; diff --git a/src/lib/utils/sanitize.ts b/src/lib/utils/sanitize.ts new file mode 100644 index 000000000..b73e4b351 --- /dev/null +++ b/src/lib/utils/sanitize.ts @@ -0,0 +1,37 @@ +/** + * Lightweight HTML sanitizer for rendering API content (e.g. Qobuz playlist descriptions). + * Allows safe tags and strips dangerous elements/attributes. + */ + +const ALLOWED_TAGS = new Set([ + 'b', 'i', 'em', 'strong', 'br', 'a', 'p', 'span', 'ul', 'ol', 'li', +]); + +/** Strip event handler attributes and javascript: URLs */ +function stripDangerousAttrs(tag: string): string { + // Remove on* event attributes + tag = tag.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, ''); + // Remove javascript: URLs from href/src + tag = tag.replace(/(href|src)\s*=\s*(?:"javascript:[^"]*"|'javascript:[^']*')/gi, '$1=""'); + return tag; +} + +export function sanitizeHtml(html: string): string { + if (!html) return ''; + + // Remove - + {#snippet children()} -