mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
fix: RC3 queue link underline, genre album artist split, Artists "Other" bucket (#977)
* fix: RC3 queue link underline, genre album artist split, Artists "Other" bucket Three zunoz reports, one change: - Queue now-playing card: the artist and album links now underline on hover (not just recolour), matching clickable names everywhere else. The album link sits on .queue-current-sub itself; artist links are nested .is-link spans — both selectors covered. - Genre album cards split multi-artist credits into individual links like the rest of the app. Root cause was the local-index genre query hardcoding NULL for the album raw_json column, so OpenSubsonic artists[] never reached the card and it fell back to the flat "A • B" single link. Select a.raw_json instead (parity with the All Albums / advanced-search album queries). - Artists page alphabet index: # is now digits-only, and a new "Other" bucket collects accented Latin (Æ/Ø/Å…) and non-Latin scripts (CJK, Cyrillic, …) that previously fell into the # catch-all. Adds artistBucketKey/compareBuckets helpers (unit-tested), an OTHER bucket sorted last, and the artists.other label across 9 locales. * docs(changelog): queue link hover, genre card artist split, Artists Other bucket (#977)
This commit is contained in:
committed by
GitHub
parent
88df194808
commit
47e16ebfef
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { artistBucketKey, compareBuckets, OTHER_BUCKET, ALPHABET } from './artistsHelpers';
|
||||
|
||||
describe('artistBucketKey', () => {
|
||||
it('buckets A–Z names by their uppercased first letter', () => {
|
||||
expect(artistBucketKey('Adele')).toBe('A');
|
||||
expect(artistBucketKey('zz top')).toBe('Z');
|
||||
expect(artistBucketKey('mGla')).toBe('M');
|
||||
});
|
||||
|
||||
it('puts digit-leading names in #', () => {
|
||||
expect(artistBucketKey('2Pac')).toBe('#');
|
||||
expect(artistBucketKey('50 Cent')).toBe('#');
|
||||
expect(artistBucketKey('999')).toBe('#');
|
||||
});
|
||||
|
||||
it('puts accented Latin and non-Latin scripts in Other (not #)', () => {
|
||||
expect(artistBucketKey('Ärzte')).toBe(OTHER_BUCKET);
|
||||
expect(artistBucketKey('Øde')).toBe(OTHER_BUCKET);
|
||||
expect(artistBucketKey('Å-band')).toBe(OTHER_BUCKET);
|
||||
expect(artistBucketKey('이영지')).toBe(OTHER_BUCKET); // Korean
|
||||
expect(artistBucketKey('くるり')).toBe(OTHER_BUCKET); // Japanese
|
||||
expect(artistBucketKey('Кино')).toBe(OTHER_BUCKET); // Cyrillic
|
||||
expect(artistBucketKey('王菲')).toBe(OTHER_BUCKET); // Chinese
|
||||
});
|
||||
|
||||
it('puts symbol-leading and empty names in Other', () => {
|
||||
expect(artistBucketKey('!!!')).toBe(OTHER_BUCKET);
|
||||
expect(artistBucketKey(' ')).toBe(OTHER_BUCKET);
|
||||
expect(artistBucketKey('')).toBe(OTHER_BUCKET);
|
||||
});
|
||||
|
||||
it('ignores leading whitespace', () => {
|
||||
expect(artistBucketKey(' Beatles')).toBe('B');
|
||||
});
|
||||
});
|
||||
|
||||
describe('compareBuckets', () => {
|
||||
it('orders # first, then A–Z, then Other last', () => {
|
||||
const shuffled = ['OTHER', 'M', '#', 'A', 'Z'];
|
||||
expect([...shuffled].sort(compareBuckets)).toEqual(['#', 'A', 'M', 'Z', 'OTHER']);
|
||||
});
|
||||
|
||||
it('ALPHABET ends with the Other bucket', () => {
|
||||
expect(ALPHABET[ALPHABET.length - 1]).toBe(OTHER_BUCKET);
|
||||
expect(ALPHABET).toContain('#');
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,32 @@
|
||||
import type { SubsonicArtist } from '../../api/subsonicTypes';
|
||||
|
||||
export const ALL_SENTINEL = 'ALL';
|
||||
export const ALPHABET = [ALL_SENTINEL, '#', ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')];
|
||||
/** Catch-all bucket for names that start with neither an A–Z letter nor a digit
|
||||
* (accented Latin like Æ/Ø/Å, and non-Latin scripts: CJK, Cyrillic, …). */
|
||||
export const OTHER_BUCKET = 'OTHER';
|
||||
export const ALPHABET = [ALL_SENTINEL, '#', ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''), OTHER_BUCKET];
|
||||
|
||||
/** Stable ordering index for a bucket key — '#' first, A–Z, then 'Other' last. */
|
||||
const BUCKET_ORDER = new Map(ALPHABET.map((l, i) => [l, i]));
|
||||
|
||||
/**
|
||||
* Bucket an artist name into the alphabet index:
|
||||
* - `#` → starts with a digit (0–9)
|
||||
* - `A`–`Z` → starts with an ASCII letter
|
||||
* - `OTHER` → anything else (accents, CJK, Cyrillic, symbols, empty)
|
||||
*/
|
||||
export function artistBucketKey(name: string): string {
|
||||
const first = name?.trim()?.[0];
|
||||
if (!first) return OTHER_BUCKET;
|
||||
if (/^[0-9]$/.test(first)) return '#';
|
||||
const up = first.toUpperCase();
|
||||
return /^[A-Z]$/.test(up) ? up : OTHER_BUCKET;
|
||||
}
|
||||
|
||||
/** Sort comparator for bucket keys following ALPHABET order (unknown keys last). */
|
||||
export function compareBuckets(a: string, b: string): number {
|
||||
return (BUCKET_ORDER.get(a) ?? 999) - (BUCKET_ORDER.get(b) ?? 999);
|
||||
}
|
||||
|
||||
/** Virtual row height guesses — letter heading vs dense rows vs last row in section (group gap). */
|
||||
export const ARTIST_LIST_LETTER_ROW_EST = 48;
|
||||
|
||||
Reference in New Issue
Block a user