|
40 | 40 | // We use a WeakMap to store the "pristine" (original) value of an input, |
41 | 41 | // side-stepping any event race conditions with native preview handlers. |
42 | 42 | const pristineValues = new WeakMap(); |
| 43 | + const pristineArtistNames = new WeakMap(); |
43 | 44 |
|
44 | 45 | // ==================================================================================== |
45 | 46 | // --- ✨ USER CONFIGURATION ✨ --- |
|
187 | 188 | })).filter(item => item.name !== ''); |
188 | 189 | } |
189 | 190 |
|
190 | | - function mergeArtistCredits(currentNames, parsedTitleArtists) { |
191 | | - // Flatten all current editor names into individual lowercase names for matching |
192 | | - const currentIndividualNamesLower = []; |
193 | | - currentNames.forEach(n => { |
194 | | - const parsed = parseArtistNamesFromString(n.name); |
195 | | - currentIndividualNamesLower.push(...parsed); |
| 191 | + function mergeArtistCredits(currentNames, parsedTitleArtists, seededArtists) { |
| 192 | + // Flatten all seeded names into individual lowercase names for matching |
| 193 | + const seededIndividualNamesLower = []; |
| 194 | + const artistsToCheck = seededArtists || []; |
| 195 | + artistsToCheck.forEach(name => { |
| 196 | + const parsed = parseArtistNamesFromString(name); |
| 197 | + seededIndividualNamesLower.push(...parsed); |
196 | 198 | }); |
197 | 199 |
|
198 | | - // Check if any parsed title artist is in the editor's individual names |
199 | | - const hasPartialMatch = parsedTitleArtists.some(ta => |
200 | | - currentIndividualNamesLower.includes(ta.name.trim().toLowerCase()) |
| 200 | + // Check if any parsed title artist is in the editor's individual seeded names |
| 201 | + const hasPartialMatch = parsedTitleArtists.some(ta => |
| 202 | + seededIndividualNamesLower.includes(ta.name.trim().toLowerCase()) |
201 | 203 | ); |
202 | 204 |
|
203 | 205 | if (hasPartialMatch) { |
|
335 | 337 |
|
336 | 338 | function removeArtistFromTitle(input, button) { |
337 | 339 | if (!input || !button) return; |
338 | | - let newText = input.value; |
| 340 | + let newText = pristineValues.get(input) || input.value; |
339 | 341 |
|
340 | 342 | // Handle native MB mis-guess in ETI (flattening) |
341 | 343 | // This ensures the separator split can correctly identify the artist part |
|
431 | 433 | // Knockout Model Mode: merge and strip completely |
432 | 434 | const currentAC = acObservable(); |
433 | 435 | if (currentAC?.names) { |
434 | | - const updatedNames = mergeArtistCredits(currentAC.names, parsedTitleArtists); |
| 436 | + const seededArtists = pristineArtistNames.get(input) || getCurrentArtistNames(button); |
| 437 | + const updatedNames = mergeArtistCredits(currentAC.names, parsedTitleArtists, seededArtists); |
435 | 438 | if (updatedNames !== currentAC.names) { |
436 | 439 | log('Updating AC observable with merged artists:', updatedNames); |
437 | 440 | acObservable({ |
|
451 | 454 | } |
452 | 455 | log(`Removed artist part from title: "${input.value}" -> "${finalTitle}"`); |
453 | 456 | setInputValue(input, finalTitle.trim()); |
| 457 | + pristineValues.set(input, input.value); |
454 | 458 | } else { |
455 | 459 | // Fallback DOM Mode: only strip if all parsed artists are already in editor |
456 | 460 | const allArtistsInTitle = parsedTitleArtists.map(n => n.name.toLowerCase()); |
|
463 | 467 | } |
464 | 468 | log(`Removed artist part from title (fallback): "${input.value}" -> "${finalTitle}"`); |
465 | 469 | setInputValue(input, finalTitle.trim()); |
| 470 | + pristineValues.set(input, input.value); |
466 | 471 | } else { |
467 | 472 | // Keep current value intact |
468 | 473 | setInputValue(input, reassembleOriginal()); |
| 474 | + pristineValues.set(input, input.value); |
469 | 475 | } |
470 | 476 | } |
471 | 477 | } |
|
694 | 700 |
|
695 | 701 | button.addEventListener('click', () => { |
696 | 702 | log(`'Guess Feat.' click detected for track. Allowing native script to run first.`); |
| 703 | + const input = trackRow.querySelector('input.track-name'); |
| 704 | + if (input) { |
| 705 | + pristineValues.set(input, input.value); |
| 706 | + pristineArtistNames.set(input, getCurrentArtistNames(button)); |
| 707 | + } |
697 | 708 | setTimeout(() => { |
698 | 709 | deduplicateTrackAC(trackRow); |
699 | | - const input = trackRow.querySelector('input.track-name'); |
700 | 710 | if (input) removeArtistFromTitle(input, button); |
701 | 711 | }, 100); |
702 | 712 | }, true); |
|
710 | 720 |
|
711 | 721 | button.addEventListener('click', () => { |
712 | 722 | log('Medium-wide "Guess Feat." clicked. Allowing native script to run first.'); |
713 | | - setTimeout(() => { |
714 | | - const medium = button.closest('fieldset.advanced-medium'); |
715 | | - if (!medium) return; |
| 723 | + const medium = button.closest('fieldset.advanced-medium'); |
| 724 | + if (!medium) return; |
| 725 | + |
| 726 | + // Capture all pristine values before native script modifies them |
| 727 | + const inputs = Array.from(medium.querySelectorAll('tr.track input.track-name')); |
| 728 | + inputs.forEach(input => { |
| 729 | + pristineValues.set(input, input.value); |
| 730 | + pristineArtistNames.set(input, getCurrentArtistNames(button)); |
| 731 | + }); |
716 | 732 |
|
| 733 | + setTimeout(() => { |
717 | 734 | log('Applying de-duplication and title cleanup to all tracks in this medium.'); |
718 | 735 | medium.querySelectorAll('tr.track').forEach(trackRow => { |
719 | 736 | deduplicateTrackAC(trackRow); |
|
733 | 750 |
|
734 | 751 | button.addEventListener('click', (event) => { |
735 | 752 | const input = findAssociatedInput(button); |
| 753 | + if (input) { |
| 754 | + pristineValues.set(input, input.value); |
| 755 | + pristineArtistNames.set(input, getCurrentArtistNames(button)); |
| 756 | + } |
736 | 757 |
|
737 | 758 | // --- Standalone Form Advanced Correction Interception --- |
738 | 759 | if (input && /[\/.]recording\/create/.test(window.location.pathname)) { |
|
870 | 891 |
|
871 | 892 | setTimeout(() => { |
872 | 893 | pristineValues.set(input, input.value); |
| 894 | + pristineArtistNames.set(input, getCurrentArtistNames(button)); |
873 | 895 | }, 50); |
874 | 896 | return; |
875 | 897 | } |
|
901 | 923 | removeArtistFromTitle(input, button); |
902 | 924 | // Update pristine value state for the guesscase button |
903 | 925 | pristineValues.set(input, input.value); |
| 926 | + pristineArtistNames.set(input, getCurrentArtistNames(button)); |
904 | 927 | log(`Updated pristine value for ${input.name || input.id} after Guess Feat cleanup: "${input.value}"`); |
905 | 928 | } |
906 | 929 | }, 100); |
|
927 | 950 | log(`Set initial pristine value for ${input.name || input.id}: "${input.value}"`); |
928 | 951 | } |
929 | 952 |
|
930 | | - const updatePristineValue = () => { |
| 953 | + const updatePristineValue = (event) => { |
| 954 | + if (event && !event.isTrusted) return; |
931 | 955 | pristineValues.set(input, input.value); |
932 | 956 | log(`Updated pristine value for ${input.name || input.id}: "${input.value}"`); |
933 | 957 | }; |
|
0 commit comments