fix: circular import causing navigate undefined; unified importer input
- Remove top-level ui.js import from soundcloud.js (broke views.js→soundcloud.js→ui.js→views.js cycle) - Use window.executeSpotifyDownload() instead to avoid the circular dep - Merge Spotify + SoundCloud into one unified URL input that auto-detects the service and colors the badge/button accordingly - resolveImporterLink() routes to the correct handler based on URL Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
ee65183555
commit
0e8eb311af
4 files changed
+84
-57
No files matched your search
+18
-28
@@ -381,23 +381,29 @@
|
|||||||
<div class="page-title">Song <em>Importer</em></div>
|
<div class="page-title">Song <em>Importer</em></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SPOTIFY SECTION -->
|
<!-- UNIFIED INPUT — auto-detects Spotify vs SoundCloud -->
|
||||||
<div class="importer-section-label" style="color:#1DB954">
|
<div class="search-wrap" style="padding:0 0 8px">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="#1DB954"><circle cx="12" cy="12" r="10"/><path d="M8 11.973c2.5-1.473 5.5-.973 7.5.527M9.5 14.473c1.5-.973 3.5-.973 5 .027M7 9c3-1.5 6.5-1 9 1" stroke="#000" stroke-width="2" fill="none"/></svg>
|
|
||||||
Spotify
|
|
||||||
</div>
|
|
||||||
<div class="search-wrap" style="padding:0 0 12px">
|
|
||||||
<div style="display:flex;gap:8px">
|
<div style="display:flex;gap:8px">
|
||||||
<div class="search-box" style="background:var(--surface);border-color:var(--border2);flex:1">
|
<div class="search-box" style="background:var(--surface);border-color:var(--border2);flex:1">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 11.973c2.5-1.473 5.5-.973 7.5.527M9.5 14.473c1.5-.973 3.5-.973 5 .027M7 9c3-1.5 6.5-1 9 1"/></svg>
|
<div id="importer-badge" style="font-size:10px;font-weight:700;padding:2px 7px;border-radius:10px;background:var(--surface2);color:var(--text3);white-space:nowrap;flex-shrink:0">URL</div>
|
||||||
<input id="spotify-link-input" type="url" placeholder="Spotify Link (Track, Album oder Playlist)…" style="font-size:14px" oninput="clearSpotifyPreview()">
|
<input id="importer-link-input" type="url" placeholder="Spotify oder SoundCloud Link einfügen…" style="font-size:14px" oninput="onImporterInput(this.value)">
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-outline" style="padding:0 16px;font-size:13px;font-weight:700;white-space:nowrap;flex-shrink:0;background:#1DB954;color:#000;border-color:transparent" onclick="resolveSpotifyLink()">Importieren</button>
|
<button id="importer-btn" class="btn-outline" style="padding:0 16px;font-size:13px;font-weight:700;white-space:nowrap;flex-shrink:0" onclick="resolveImporterLink()">Importieren</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="spotify-importer-hint" style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px;font-size:12px;color:var(--text2);line-height:1.8;margin-bottom:4px">
|
<div id="importer-hint" style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px;font-size:12px;color:var(--text2);line-height:1.8;margin-bottom:4px">
|
||||||
Track, Album oder Playlist-Link einfügen — Metadaten werden direkt von Spotify geladen und Songs via yt-dlp in die Library heruntergeladen.
|
<div style="display:flex;gap:16px;flex-wrap:wrap">
|
||||||
|
<div style="display:flex;align-items:center;gap:6px"><span style="color:#1DB954;font-weight:700">Spotify</span> — Track, Album oder Playlist</div>
|
||||||
|
<div style="display:flex;align-items:center;gap:6px"><span style="color:#ff5500;font-weight:700">SoundCloud</span> — Track oder Playlist</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:6px;color:var(--text3);font-size:11px">Link einfügen → Metadaten werden geladen → via yt-dlp in die Navidrome Library herunterladen.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- hidden inputs kept for compat with spotify.js / soundcloud.js -->
|
||||||
|
<input id="spotify-link-input" type="hidden">
|
||||||
|
<input id="sc-link-input" type="hidden">
|
||||||
|
|
||||||
|
<!-- SPOTIFY RESULTS -->
|
||||||
<div id="spotify-preview" style="display:none;margin-bottom:8px">
|
<div id="spotify-preview" style="display:none;margin-bottom:8px">
|
||||||
<div id="spotify-preview-inner"></div>
|
<div id="spotify-preview-inner"></div>
|
||||||
<div id="spotify-enrich-status" style="display:none"></div>
|
<div id="spotify-enrich-status" style="display:none"></div>
|
||||||
@@ -409,23 +415,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SOUNDCLOUD SECTION -->
|
<!-- SOUNDCLOUD RESULTS -->
|
||||||
<div class="importer-section-label" style="color:#ff5500;margin-top:20px">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#ff5500" stroke-width="2"><path d="M2 16.5c0 1.1.9 2 2 2h14c2.2 0 4-1.8 4-4s-1.8-4-4-4c-.3 0-.6 0-.9.1C16.8 8.4 14.6 7 12 7c-3.3 0-6 2.7-6 6H4c-1.1 0-2 .9-2 1.5z"/></svg>
|
|
||||||
SoundCloud
|
|
||||||
</div>
|
|
||||||
<div class="search-wrap" style="padding:0 0 12px">
|
|
||||||
<div style="display:flex;gap:8px">
|
|
||||||
<div class="search-box" style="background:var(--surface);border-color:var(--border2);flex:1">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 16.5c0 1.1.9 2 2 2h14c2.2 0 4-1.8 4-4s-1.8-4-4-4c-.3 0-.6 0-.9.1C16.8 8.4 14.6 7 12 7c-3.3 0-6 2.7-6 6H4c-1.1 0-2 .9-2 1.5z"/></svg>
|
|
||||||
<input id="sc-link-input" type="url" placeholder="SoundCloud Link (Track oder Playlist)…" style="font-size:14px" oninput="clearSoundCloudPreview()">
|
|
||||||
</div>
|
|
||||||
<button class="btn-outline" style="padding:0 16px;font-size:13px;font-weight:700;white-space:nowrap;flex-shrink:0;background:#ff5500;color:#fff;border-color:transparent" onclick="resolveSoundCloudLink()">Importieren</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="sc-importer-hint" style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px;font-size:12px;color:var(--text2);line-height:1.8;margin-bottom:4px">
|
|
||||||
Track oder Playlist-Link einfügen — Metadaten (Titel, Artist, Genre, Artwork) werden direkt von SoundCloud geladen und der Audio-Stream heruntergeladen.
|
|
||||||
</div>
|
|
||||||
<div id="sc-preview" style="display:none;margin-bottom:8px">
|
<div id="sc-preview" style="display:none;margin-bottom:8px">
|
||||||
<div id="sc-preview-inner"></div>
|
<div id="sc-preview-inner"></div>
|
||||||
<div id="sc-tracks-section" style="display:none">
|
<div id="sc-tracks-section" style="display:none">
|
||||||
|
|||||||
+3
-1
@@ -18,7 +18,8 @@ import {
|
|||||||
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist,
|
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist,
|
||||||
plReorderEnter, plReorderSave, plReorderCancel, toggleAllGenres,
|
plReorderEnter, plReorderSave, plReorderCancel, toggleAllGenres,
|
||||||
} from './views.js';
|
} from './views.js';
|
||||||
import { loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll } from './spotify.js';
|
import { loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
|
||||||
|
onImporterInput, resolveImporterLink } from './spotify.js';
|
||||||
import { loadSoundCloudImporter, resolveSoundCloudLink, clearSoundCloudPreview, scDlAll } from './soundcloud.js';
|
import { loadSoundCloudImporter, resolveSoundCloudLink, clearSoundCloudPreview, scDlAll } from './soundcloud.js';
|
||||||
import {
|
import {
|
||||||
absInitFromStorage, absOpenSettings, absConnectSettings, absSetLibrary, absDisconnect, absVerifyApiKey,
|
absInitFromStorage, absOpenSettings, absConnectSettings, absSetLibrary, absDisconnect, absVerifyApiKey,
|
||||||
@@ -308,6 +309,7 @@ Object.assign(window, {
|
|||||||
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist,
|
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist,
|
||||||
plReorderEnter, plReorderSave, plReorderCancel, toggleAllGenres,
|
plReorderEnter, plReorderSave, plReorderCancel, toggleAllGenres,
|
||||||
loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
|
loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
|
||||||
|
onImporterInput, resolveImporterLink,
|
||||||
loadSoundCloudImporter, resolveSoundCloudLink, clearSoundCloudPreview, scDlAll, scDlSingle, scDlTrack,
|
loadSoundCloudImporter, resolveSoundCloudLink, clearSoundCloudPreview, scDlAll, scDlSingle, scDlTrack,
|
||||||
executeSpotifyDownload,
|
executeSpotifyDownload,
|
||||||
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt,
|
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt,
|
||||||
|
|||||||
+7
-16
@@ -1,34 +1,25 @@
|
|||||||
import { S } from './state.js';
|
import { S } from './state.js';
|
||||||
import { esc, toast } from './utils.js';
|
import { esc, toast } from './utils.js';
|
||||||
// Circular imports — safe (only used inside function bodies)
|
|
||||||
import { executeSpotifyDownload } from './ui.js';
|
|
||||||
|
|
||||||
export function loadSoundCloudImporter() {
|
export function loadSoundCloudImporter() {
|
||||||
const input = document.getElementById('sc-link-input');
|
// Hint is managed by the unified importer input in spotify.js
|
||||||
if (!input?.value) _showHint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearSoundCloudPreview() {
|
export function clearSoundCloudPreview() {
|
||||||
if (!document.getElementById('sc-link-input')?.value) _showHint();
|
// no-op — handled by onImporterInput
|
||||||
}
|
|
||||||
|
|
||||||
function _showHint() {
|
|
||||||
document.getElementById('sc-importer-hint').style.display = '';
|
|
||||||
document.getElementById('sc-preview').style.display = 'none';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveSoundCloudLink() {
|
export async function resolveSoundCloudLink() {
|
||||||
const url = document.getElementById('sc-link-input')?.value.trim();
|
const url = document.getElementById('sc-link-input')?.value.trim()
|
||||||
|
|| document.getElementById('importer-link-input')?.value.trim();
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
if (!url.includes('soundcloud.com')) { toast('⚠ Kein SoundCloud-Link'); return; }
|
if (!url.includes('soundcloud.com')) { toast('⚠ Kein SoundCloud-Link'); return; }
|
||||||
if (!S.cfg.backendUrl) { toast('⚠ Backend URL in Einstellungen konfigurieren'); return; }
|
if (!S.cfg.backendUrl) { toast('⚠ Backend URL in Einstellungen konfigurieren'); return; }
|
||||||
|
|
||||||
const hint = document.getElementById('sc-importer-hint');
|
|
||||||
const preview = document.getElementById('sc-preview');
|
const preview = document.getElementById('sc-preview');
|
||||||
const inner = document.getElementById('sc-preview-inner');
|
const inner = document.getElementById('sc-preview-inner');
|
||||||
const section = document.getElementById('sc-tracks-section');
|
const section = document.getElementById('sc-tracks-section');
|
||||||
|
|
||||||
hint.style.display = 'none';
|
|
||||||
preview.style.display = '';
|
preview.style.display = '';
|
||||||
section.style.display = 'none';
|
section.style.display = 'none';
|
||||||
inner.innerHTML = '<div class="spinner"></div>';
|
inner.innerHTML = '<div class="spinner"></div>';
|
||||||
@@ -59,7 +50,7 @@ export async function resolveSoundCloudLink() {
|
|||||||
export function scDlAll() {
|
export function scDlAll() {
|
||||||
const data = window._scResolved;
|
const data = window._scResolved;
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
executeSpotifyDownload({
|
window.executeSpotifyDownload({
|
||||||
type: 'playlist',
|
type: 'playlist',
|
||||||
title: data.name,
|
title: data.name,
|
||||||
artist: data.artist,
|
artist: data.artist,
|
||||||
@@ -72,7 +63,7 @@ export function scDlSingle() {
|
|||||||
const data = window._scResolved;
|
const data = window._scResolved;
|
||||||
if (!data?.tracks?.[0]) return;
|
if (!data?.tracks?.[0]) return;
|
||||||
const t = data.tracks[0];
|
const t = data.tracks[0];
|
||||||
executeSpotifyDownload({
|
window.executeSpotifyDownload({
|
||||||
type: 'track',
|
type: 'track',
|
||||||
title: t.trackName,
|
title: t.trackName,
|
||||||
artist: t.artistName,
|
artist: t.artistName,
|
||||||
@@ -85,7 +76,7 @@ export function scDlTrack(idx) {
|
|||||||
const data = window._scResolved;
|
const data = window._scResolved;
|
||||||
const t = data?.tracks?.[idx];
|
const t = data?.tracks?.[idx];
|
||||||
if (!t) return;
|
if (!t) return;
|
||||||
executeSpotifyDownload({
|
window.executeSpotifyDownload({
|
||||||
type: 'track',
|
type: 'track',
|
||||||
title: t.trackName,
|
title: t.trackName,
|
||||||
artist: t.artistName,
|
artist: t.artistName,
|
||||||
|
|||||||
+56
-12
@@ -5,27 +5,71 @@ import { itunesSearch } from './api.js';
|
|||||||
import { makeGlobalTrackItem } from './components.js';
|
import { makeGlobalTrackItem } from './components.js';
|
||||||
import { openDlModal } from './ui.js';
|
import { openDlModal } from './ui.js';
|
||||||
|
|
||||||
|
// ─── UNIFIED IMPORTER INPUT ───
|
||||||
|
export function onImporterInput(val) {
|
||||||
|
const badge = document.getElementById('importer-badge');
|
||||||
|
const btn = document.getElementById('importer-btn');
|
||||||
|
if (!badge || !btn) return;
|
||||||
|
if (val.includes('spotify.com')) {
|
||||||
|
badge.textContent = 'Spotify';
|
||||||
|
badge.style.background = 'rgba(29,185,84,.15)';
|
||||||
|
badge.style.color = '#1DB954';
|
||||||
|
btn.style.background = '#1DB954';
|
||||||
|
btn.style.color = '#000';
|
||||||
|
btn.style.borderColor = 'transparent';
|
||||||
|
} else if (val.includes('soundcloud.com')) {
|
||||||
|
badge.textContent = 'SoundCloud';
|
||||||
|
badge.style.background = 'rgba(255,85,0,.15)';
|
||||||
|
badge.style.color = '#ff5500';
|
||||||
|
btn.style.background = '#ff5500';
|
||||||
|
btn.style.color = '#fff';
|
||||||
|
btn.style.borderColor = 'transparent';
|
||||||
|
} else {
|
||||||
|
badge.textContent = 'URL';
|
||||||
|
badge.style.background = '';
|
||||||
|
badge.style.color = '';
|
||||||
|
btn.style.background = '';
|
||||||
|
btn.style.color = '';
|
||||||
|
btn.style.borderColor = '';
|
||||||
|
}
|
||||||
|
// Hide results when input is cleared
|
||||||
|
if (!val) {
|
||||||
|
document.getElementById('spotify-preview').style.display = 'none';
|
||||||
|
document.getElementById('sc-preview').style.display = 'none';
|
||||||
|
document.getElementById('importer-hint').style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveImporterLink() {
|
||||||
|
const url = document.getElementById('importer-link-input')?.value.trim();
|
||||||
|
if (!url) return;
|
||||||
|
// Mirror to the hidden inputs so existing resolve functions pick it up
|
||||||
|
document.getElementById('spotify-link-input').value = url;
|
||||||
|
document.getElementById('sc-link-input').value = url;
|
||||||
|
document.getElementById('importer-hint').style.display = 'none';
|
||||||
|
if (url.includes('soundcloud.com')) {
|
||||||
|
window.resolveSoundCloudLink();
|
||||||
|
} else {
|
||||||
|
resolveSpotifyLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── SPOTIFY IMPORTER PAGE ───
|
// ─── SPOTIFY IMPORTER PAGE ───
|
||||||
export function loadSpotifyImporter() {
|
export function loadSpotifyImporter() {
|
||||||
if (!document.getElementById('spotify-link-input')?.value) {
|
const input = document.getElementById('importer-link-input');
|
||||||
const hint = document.getElementById('spotify-importer-hint');
|
if (!input?.value) {
|
||||||
const preview = document.getElementById('spotify-preview');
|
document.getElementById('importer-hint').style.display = '';
|
||||||
if (hint) hint.style.display = '';
|
document.getElementById('spotify-preview').style.display = 'none';
|
||||||
if (preview) preview.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearSpotifyPreview() {
|
export function clearSpotifyPreview() {
|
||||||
if (!document.getElementById('spotify-link-input')?.value) {
|
// no-op — handled by onImporterInput
|
||||||
const hint = document.getElementById('spotify-importer-hint');
|
|
||||||
const preview = document.getElementById('spotify-preview');
|
|
||||||
if (hint) hint.style.display = '';
|
|
||||||
if (preview) preview.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveSpotifyLink() {
|
export async function resolveSpotifyLink() {
|
||||||
const url = document.getElementById('spotify-link-input')?.value.trim();
|
const url = document.getElementById('spotify-link-input')?.value.trim()
|
||||||
|
|| document.getElementById('importer-link-input')?.value.trim();
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
if (!S.cfg.backendUrl) { toast('⚠ Backend URL in Einstellungen konfigurieren'); return; }
|
if (!S.cfg.backendUrl) { toast('⚠ Backend URL in Einstellungen konfigurieren'); return; }
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user