Merge pull request #326 from lsmda/feature/add-strict-album-match-toggle-on-playlist-creation

feat(playlists): add strict album match toggle on CSV tab import
This commit is contained in:
edideaur 2026-03-15 13:47:58 +02:00 committed by GitHub
commit 7b70e55895
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 25 additions and 10 deletions

View file

@ -528,7 +528,6 @@
color: var(--foreground);
cursor: pointer;
padding: 0.25rem 0.5rem;
opacity: 0.7;
"
>
CSV
@ -669,6 +668,22 @@
Make sure its headers are in English.
</p>
</div>
<div
class="setting-item"
style="margin-top: 1rem; margin-bottom: 1rem; background: transparent"
>
<div class="info">
<span style="font-size: 0.9rem; font-weight: 600">Strict Album Matching</span>
<span style="font-size: 0.8rem; max-width: 14rem">
Album name should strictly match CSV metadata. Disable for better discovery.
</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="strict-album-match-toggle" />
<span class="slider"></span>
</label>
</div>
</div>
<div id="jspf-import-panel" class="import-panel" style="display: none">

View file

@ -1265,6 +1265,7 @@ document.addEventListener('DOMContentLoaded', async () => {
let name = document.getElementById('playlist-name-input').value.trim();
let description = document.getElementById('playlist-description-input').value.trim();
const isPublic = document.getElementById('playlist-public-toggle')?.checked;
const isStrictAlbumMatch = document.getElementById('strict-album-match-toggle')?.checked;
if (name) {
const modal = document.getElementById('playlist-modal');
@ -1317,7 +1318,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const xmlFileInput = document.getElementById('xml-file-input');
const m3uFileInput = document.getElementById('m3u-file-input');
const importOptions = { strictArtistMatch: true, albumMatch: true };
const importOptions = { strictArtistMatch: true, strictAlbumMatch: isStrictAlbumMatch };
let tracks = [];
let importSource = 'manual';

View file

@ -5,21 +5,21 @@ function isFuzzyMatch(str1, str2) {
return s1.includes(s2) || s2.includes(s1);
}
function findBestMatch(items, targetArtist, targetAlbum, options) {
function findBestMatch(items, targetArtist, targetAlbum, importOptions) {
if (!items || items.length === 0) return null;
if (!options?.strictArtistMatch && !options?.albumMatch) return items[0];
if (!importOptions?.strictArtistMatch && !importOptions?.strictAlbumMatch) return items[0];
return (
items.find((item) => {
let artistOk = true;
let albumOk = true;
if (options.strictArtistMatch && targetArtist) {
if (importOptions.strictArtistMatch && targetArtist) {
const itemArtist = item.artist?.name || item.artists?.[0]?.name;
if (!isFuzzyMatch(itemArtist, targetArtist)) artistOk = false;
}
if (options.albumMatch && targetAlbum) {
if (importOptions.strictAlbumMatch && targetAlbum) {
const itemAlbum = item.album?.title;
if (itemAlbum && !isFuzzyMatch(itemAlbum, targetAlbum)) albumOk = false;
}
@ -294,8 +294,7 @@ export async function parseDynamicCSV(csvText, api, onProgress, options = {}) {
const albumName = mappedHeaders.album !== undefined ? values[mappedHeaders.album] : '';
const isrc = mappedHeaders.isrc !== undefined ? values[mappedHeaders.isrc] : '';
const playlistName = mappedHeaders.playlistName !== undefined ? values[mappedHeaders.playlistName] : '';
const typeValue =
mappedHeaders.type !== undefined ? values[mappedHeaders.type]?.toLowerCase().trim() : '';
const typeValue = mappedHeaders.type !== undefined ? values[mappedHeaders.type]?.toLowerCase().trim() : '';
const isFavorite = typeValue.includes('favorite');
if (onProgress) {
@ -497,7 +496,7 @@ export async function importToLibrary(csvResult, db, onProgress, options = {}) {
return results;
}
export async function parseCSV(csvText, api, onProgress, options = {}) {
export async function parseCSV(csvText, api, onProgress, importOptions = {}) {
const lines = csvText.trim().split('\n');
if (lines.length < 2) return { tracks: [], missingTracks: [] };
@ -583,7 +582,7 @@ export async function parseCSV(csvText, api, onProgress, options = {}) {
const searchResult = await api.searchTracks(searchQuery);
if (searchResult.items && searchResult.items.length > 0) {
const match = findBestMatch(searchResult.items, artistNames, albumName, options);
const match = findBestMatch(searchResult.items, artistNames, albumName, importOptions);
if (match) tracks.push(match);
else missingTracks.push({ title: trackTitle, artist: artistNames, album: albumName });
} else {