feat(artists): artists socials

This commit is contained in:
Samidy 2026-02-22 01:04:02 +03:00
parent 9e55593d79
commit 77d99245c8
5 changed files with 127 additions and 1 deletions

View file

@ -2588,6 +2588,7 @@
<div class="detail-header-info">
<h1 class="title" id="artist-detail-name"></h1>
<div class="meta" id="artist-detail-meta"></div>
<div id="artist-detail-socials" class="artist-socials"></div>
<div id="artist-detail-bio" class="artist-bio"></div>
<div class="detail-header-actions">
<button id="play-artist-radio-btn" class="btn-primary" title="Artist Radio">

View file

@ -685,6 +685,46 @@ export class LosslessAPI {
return result;
}
async getArtistSocials(artistName) {
const cacheKey = `artist_socials_${artistName}`;
const cached = await this.cache.get('artist', cacheKey);
if (cached) return cached;
try {
const searchUrl = `https://musicbrainz.org/ws/2/artist/?query=artist:${encodeURIComponent(artistName)}&fmt=json`;
const searchRes = await fetch(searchUrl, {
headers: { 'User-Agent': 'Monochrome/2.0.0 ( https://github.com/monochrome-music/monochrome )' },
});
const searchData = await searchRes.json();
if (!searchData.artists || searchData.artists.length === 0) return [];
const artist = searchData.artists[0];
const mbid = artist.id;
const detailsUrl = `https://musicbrainz.org/ws/2/artist/${mbid}?inc=url-rels&fmt=json`;
const detailsRes = await fetch(detailsUrl, {
headers: { 'User-Agent': 'Monochrome/2.0.0 ( https://github.com/monochrome-music/monochrome )' },
});
const detailsData = await detailsRes.json();
const links = [];
if (detailsData.relations) {
for (const rel of detailsData.relations) {
if (['social network', 'streaming', 'official homepage', 'youtube', 'soundcloud', 'bandcamp'].includes(rel.type)) {
links.push({ type: rel.type, url: rel.url.resource });
}
}
}
await this.cache.set('artist', cacheKey, links);
return links;
} catch (e) {
console.warn('Failed to fetch artist socials:', e);
return [];
}
}
async getArtist(artistId, options = {}) {
const cacheKey = options.lightweight ? `artist_${artistId}_light` : `artist_${artistId}`;
if (!options.skipCache) {

View file

@ -88,6 +88,10 @@ export class MusicAPI {
return null;
}
async getArtistSocials(artistName) {
return this.tidalAPI.getArtistSocials(artistName);
}
async getPlaylist(id, _provider = null) {
// Playlists are always Tidal for now
return this.tidalAPI.getPlaylist(id);

View file

@ -52,6 +52,15 @@ import { trackSearch, trackChangeSort } from './analytics.js';
fontSettings.applyFont();
fontSettings.applyFontSize();
const SVG_GLOBE = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>';
const SVG_INSTAGRAM = '<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke=""><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M12 18C15.3137 18 18 15.3137 18 12C18 8.68629 15.3137 6 12 6C8.68629 6 6 8.68629 6 12C6 15.3137 8.68629 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z" fill="#9E9E9E"></path> <path d="M18 5C17.4477 5 17 5.44772 17 6C17 6.55228 17.4477 7 18 7C18.5523 7 19 6.55228 19 6C19 5.44772 18.5523 5 18 5Z" fill="#9E9E9E"></path> <path fill-rule="evenodd" clip-rule="evenodd" d="M1.65396 4.27606C1 5.55953 1 7.23969 1 10.6V13.4C1 16.7603 1 18.4405 1.65396 19.7239C2.2292 20.8529 3.14708 21.7708 4.27606 22.346C5.55953 23 7.23969 23 10.6 23H13.4C16.7603 23 18.4405 23 19.7239 22.346C20.8529 21.7708 21.7708 20.8529 22.346 19.7239C23 18.4405 23 16.7603 23 13.4V10.6C23 7.23969 23 5.55953 22.346 4.27606C21.7708 3.14708 20.8529 2.2292 19.7239 1.65396C18.4405 1 16.7603 1 13.4 1H10.6C7.23969 1 5.55953 1 4.27606 1.65396C3.14708 2.2292 2.2292 3.14708 1.65396 4.27606ZM13.4 3H10.6C8.88684 3 7.72225 3.00156 6.82208 3.0751C5.94524 3.14674 5.49684 3.27659 5.18404 3.43597C4.43139 3.81947 3.81947 4.43139 3.43597 5.18404C3.27659 5.49684 3.14674 5.94524 3.0751 6.82208C3.00156 7.72225 3 8.88684 3 10.6V13.4C3 15.1132 3.00156 16.2777 3.0751 17.1779C3.14674 18.0548 3.27659 18.5032 3.43597 18.816C3.81947 19.5686 4.43139 20.1805 5.18404 20.564C5.49684 20.7234 5.94524 20.8533 6.82208 20.9249C7.72225 20.9984 8.88684 21 10.6 21H13.4C15.1132 21 16.2777 20.9984 17.1779 20.9249C18.0548 20.8533 18.5032 20.7234 18.816 20.564C19.5686 20.1805 20.1805 19.5686 20.564 18.816C20.7234 18.5032 20.8533 18.0548 20.9249 17.1779C20.9984 16.2777 21 15.1132 21 13.4V10.6C21 8.88684 20.9984 7.72225 20.9249 6.82208C20.8533 5.94524 20.7234 5.49684 20.564 5.18404C20.1805 4.43139 19.5686 3.81947 18.816 3.43597C18.5032 3.27659 18.0548 3.14674 17.1779 3.0751C16.2777 3.00156 15.1132 3 13.4 3Z" fill="#9E9E9E"></path> </g></svg>';
const SVG_FACEBOOK = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg>';
const SVG_YOUTUBE = '<svg viewBox="0 -3 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>youtube [#9E9E9E168]</title> <desc>Created with Sketch.</desc> <defs> </defs> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Dribbble-Light-Preview" transform="translate(-300.000000, -7442.000000)" fill="#9E9E9E"> <g id="icons" transform="translate(56.000000, 160.000000)"> <path d="M251.988432,7291.58588 L251.988432,7285.97425 C253.980638,7286.91168 255.523602,7287.8172 257.348463,7288.79353 C255.843351,7289.62824 253.980638,7290.56468 251.988432,7291.58588 M263.090998,7283.18289 C262.747343,7282.73013 262.161634,7282.37809 261.538073,7282.26141 C259.705243,7281.91336 248.270974,7281.91237 246.439141,7282.26141 C245.939097,7282.35515 245.493839,7282.58153 245.111335,7282.93357 C243.49964,7284.42947 244.004664,7292.45151 244.393145,7293.75096 C244.556505,7294.31342 244.767679,7294.71931 245.033639,7294.98558 C245.376298,7295.33761 245.845463,7295.57995 246.384355,7295.68865 C247.893451,7296.0008 255.668037,7296.17532 261.506198,7295.73552 C262.044094,7295.64178 262.520231,7295.39147 262.895762,7295.02447 C264.385932,7293.53455 264.28433,7285.06174 263.090998,7283.18289" id="youtube-[#9E9E9E168]"> </path> </g> </g> </g> </g></svg>';
const SVG_TWITTER = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"/></svg>';
const SVG_LINK = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>';
const SVG_SOUNDCLOUD = '<svg fill="#9E9E9E" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve" stroke="#9E9E9E"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g id="5151e0c8492e5103c096af88a50061c5"> <path display="inline" d="M25.173,355.106c1.076,0,1.946-0.849,2.117-2.067l5.717-44.884l-5.717-45.889 c-0.171-1.22-1.041-2.061-2.117-2.061c-1.091,0-1.982,0.862-2.125,2.061c0,0.007-5.026,45.889-5.026,45.889l5.026,44.876 C23.191,354.229,24.083,355.106,25.173,355.106z M8.328,336.058c0,0,0,0,0,0.007l0,0V336.058z M6.274,338.047 c1.041,0,1.875-0.813,2.053-1.982l4.42-27.909l-4.42-28.395c-0.171-1.169-1.012-1.981-2.053-1.981 c-1.062,0-1.896,0.819-2.046,1.996L0.5,308.155l3.729,27.896C4.378,337.227,5.212,338.047,6.274,338.047z M47.808,253.712 c-0.157-1.454-1.233-2.51-2.56-2.51c-1.354,0-2.424,1.063-2.566,2.51l-4.762,54.45l4.762,52.455 c0.143,1.461,1.212,2.509,2.566,2.509c1.326,0,2.402-1.048,2.552-2.495l5.425-52.469L47.808,253.712z M65.487,365.236 c1.568,0,2.852-1.269,3.001-2.944l0,0l5.119-54.123l-5.119-55.947c-0.149-1.675-1.433-2.944-3.001-2.944 c-1.583,0-2.873,1.27-3.001,2.951l-4.513,55.94l4.513,54.123C62.614,363.968,63.904,365.236,65.487,365.236z M85.89,366.127 c1.825,0,3.301-1.461,3.436-3.386l-0.007,0.007l4.827-54.571l-4.827-51.913c-0.128-1.918-1.604-3.372-3.429-3.372 c-1.839,0-3.315,1.454-3.429,3.387l-4.256,51.898l4.256,54.564C82.575,364.666,84.051,366.127,85.89,366.127z M114.848,308.198 l-4.527-84.436c-0.114-2.152-1.811-3.828-3.864-3.828s-3.764,1.676-3.864,3.828l-4,84.436l4,54.564 c0.1,2.124,1.811,3.813,3.864,3.813s3.75-1.689,3.864-3.828v0.015L114.848,308.198z M127.195,366.677 c2.303,0,4.185-1.868,4.299-4.264v0.036l4.228-54.244l-4.228-103.74c-0.114-2.395-1.996-4.27-4.299-4.27 c-2.317,0-4.213,1.875-4.313,4.27c0,0.008-3.735,103.74-3.735,103.74l3.743,54.229 C122.982,364.809,124.878,366.677,127.195,366.677z M148.09,191.112c-2.574,0-4.655,2.067-4.748,4.705 c0,0.008-3.472,112.402-3.472,112.402l3.479,53.666c0.085,2.616,2.167,4.677,4.741,4.677c2.552,0,4.641-2.061,4.741-4.698v0.036 l3.928-53.681l-3.928-112.409C152.73,193.173,150.642,191.112,148.09,191.112z M169.156,366.669c2.809,0,5.083-2.26,5.175-5.14 v0.035l3.622-53.338l-3.622-116.188c-0.093-2.887-2.366-5.146-5.175-5.146c-2.823,0-5.097,2.26-5.183,5.146l-3.223,116.188 l3.223,53.331C164.059,364.409,166.333,366.669,169.156,366.669z M190.378,366.619c3.065,0,5.532-2.452,5.611-5.589v0.043 l3.336-52.84l-3.336-113.229c-0.079-3.129-2.546-5.582-5.611-5.582c-3.072,0-5.546,2.46-5.617,5.582l-2.958,113.229l2.965,52.825 C184.832,364.167,187.306,366.619,190.378,366.619z M220.848,308.248l-3.03-109.108c-0.071-3.372-2.73-6.017-6.045-6.017 c-3.329,0-5.988,2.645-6.053,6.024l-2.702,109.093l2.702,52.49c0.064,3.344,2.724,5.988,6.053,5.988 c3.314,0,5.974-2.645,6.045-6.023v0.043L220.848,308.248z M233.33,366.826c3.515,0,6.423-2.901,6.48-6.466v0.043l2.737-52.148 l-2.737-129.824c-0.058-3.558-2.966-6.459-6.48-6.459c-3.521,0-6.431,2.901-6.487,6.459l-2.445,129.781 c0,0.079,2.445,52.184,2.445,52.184C226.899,363.925,229.809,366.826,233.33,366.826z M254.788,159.767 c-3.771,0-6.872,3.108-6.93,6.908l-2.83,141.595l2.83,51.385c0.058,3.75,3.158,6.851,6.93,6.851c3.764,0,6.865-3.101,6.922-6.9 v0.057l3.08-51.392l-3.08-141.602C261.653,162.875,258.552,159.767,254.788,159.767z M274.428,366.861 c0.157,0.015,173.098,0.101,174.224,0.101c34.718,0,62.849-28.146,62.849-62.863s-28.131-62.849-62.849-62.849 c-8.618,0-16.824,1.74-24.31,4.877c-4.997-56.646-52.512-101.089-110.448-101.089c-14.179,0-28.002,2.795-40.207,7.521 c-4.74,1.832-6.01,3.729-6.052,7.386c0,0.007,0,199.488,0,199.488C267.685,363.283,270.671,366.491,274.428,366.861z"> </path> </g> </g></svg>';
const SVG_APPLE = '<svg viewBox="-1.5 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>apple [#9E9E9E173]</title> <desc>Created with Sketch.</desc> <defs> </defs> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Dribbble-Light-Preview" transform="translate(-102.000000, -7439.000000)" fill="#9E9E9E"> <g id="icons" transform="translate(56.000000, 160.000000)"> <path d="M57.5708873,7282.19296 C58.2999598,7281.34797 58.7914012,7280.17098 58.6569121,7279 C57.6062792,7279.04 56.3352055,7279.67099 55.5818643,7280.51498 C54.905374,7281.26397 54.3148354,7282.46095 54.4735932,7283.60894 C55.6455696,7283.69593 56.8418148,7283.03894 57.5708873,7282.19296 M60.1989864,7289.62485 C60.2283111,7292.65181 62.9696641,7293.65879 63,7293.67179 C62.9777537,7293.74279 62.562152,7295.10677 61.5560117,7296.51675 C60.6853718,7297.73474 59.7823735,7298.94772 58.3596204,7298.97372 C56.9621472,7298.99872 56.5121648,7298.17973 54.9134635,7298.17973 C53.3157735,7298.17973 52.8162425,7298.94772 51.4935978,7298.99872 C50.1203933,7299.04772 49.0738052,7297.68074 48.197098,7296.46676 C46.4032359,7293.98379 45.0330649,7289.44985 46.8734421,7286.3899 C47.7875635,7284.87092 49.4206455,7283.90793 51.1942837,7283.88393 C52.5422083,7283.85893 53.8153044,7284.75292 54.6394294,7284.75292 C55.4635543,7284.75292 57.0106846,7283.67793 58.6366882,7283.83593 C59.3172232,7283.86293 61.2283842,7284.09893 62.4549652,7285.8199 C62.355868,7285.8789 60.1747177,7287.09489 60.1989864,7289.62485" id="apple-[#9E9E9E173]"> </path> </g> </g> </g> </g></svg>';
function sortTracks(tracks, sortType) {
if (sortType === 'custom') return [...tracks];
const sorted = [...tracks];
@ -2975,6 +2984,7 @@ export class UIRenderer {
const imageEl = document.getElementById('artist-detail-image');
const nameEl = document.getElementById('artist-detail-name');
const metaEl = document.getElementById('artist-detail-meta');
const socialsEl = document.getElementById('artist-detail-socials');
const bioEl = document.getElementById('artist-detail-bio');
const tracksContainer = document.getElementById('artist-detail-tracks');
const albumsContainer = document.getElementById('artist-detail-albums');
@ -2989,6 +2999,7 @@ export class UIRenderer {
imageEl.style.backgroundColor = 'var(--muted)';
nameEl.innerHTML = '<div class="skeleton" style="height: 48px; width: 300px; max-width: 90%;"></div>';
metaEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 150px;"></div>';
if (socialsEl) socialsEl.innerHTML = '';
if (bioEl) {
bioEl.style.display = 'none';
bioEl.textContent = '';
@ -3228,6 +3239,12 @@ export class UIRenderer {
</div>
`;
this.api.getArtistSocials(artist.name).then((links) => {
if (socialsEl && links.length > 0) {
socialsEl.innerHTML = links.map((link) => this.createSocialLinkHTML(link)).join('');
}
});
this.renderListWithTracks(tracksContainer, artist.tracks, true);
// Update header like button
@ -3238,7 +3255,6 @@ export class UIRenderer {
artistLikeBtn.classList.toggle('active', isLiked);
}
albumsContainer.innerHTML = artist.albums.map((album) => this.createAlbumCardHTML(album)).join('');
// Render Albums
albumsContainer.innerHTML = artist.albums.length
? artist.albums.map((album) => this.createAlbumCardHTML(album)).join('')
@ -3373,6 +3389,41 @@ export class UIRenderer {
}
}
createSocialLinkHTML(link) {
const url = link.url;
if (url.includes('tidal.com')) return '';
if (url.includes('qobuz.com')) return '';
let icon = SVG_GLOBE;
let title = 'Website';
if (url.includes('twitter.com') || url.includes('x.com')) {
icon = SVG_TWITTER;
title = 'Twitter';
} else if (url.includes('instagram.com')) {
icon = SVG_INSTAGRAM;
title = 'Instagram';
} else if (url.includes('facebook.com')) {
icon = SVG_FACEBOOK;
title = 'Facebook';
} else if (url.includes('youtube.com')) {
icon = SVG_YOUTUBE;
title = 'YouTube';
} else if (url.includes('spotify.com') || url.includes('open.spotify.com')) {
icon = SVG_LINK;
title = 'Spotify';
} else if (url.includes('soundcloud.com')) {
icon = SVG_SOUNDCLOUD;
title = 'SoundCloud';
} else if (url.includes('apple.com')) {
icon = SVG_APPLE;
title = 'Apple Music';
}
return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="social-link" title="${title}">${icon}</a>`;
}
async renderRecentPage() {
this.showPage('recent');
const container = document.getElementById('recent-tracks-container');

View file

@ -2376,6 +2376,36 @@ input[type='search']::-webkit-search-cancel-button {
color: var(--foreground);
}
.artist-socials {
display: flex;
gap: 0.75rem;
margin-top: 0.75rem;
flex-wrap: wrap;
}
.social-link {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: var(--radius-full);
background-color: var(--secondary);
color: var(--muted-foreground);
transition: all var(--transition-fast);
}
.social-link:hover {
background-color: var(--highlight);
color: var(--background);
transform: translateY(-2px);
}
.social-link svg {
width: 18px;
height: 18px;
}
.bio-link {
color: var(--highlight) !important;
text-decoration: underline !important;