your personal website on atproto - mirror blento.app

Merge pull request #199 from zzstoatzz/plyrfm-collection-embeds

add plyr.fm playlist and album embeds

authored by

Florian and committed by
GitHub
6decbcc3 a71c943c

+109 -16
+2 -1
src/lib/cards/index.ts
··· 46 46 import { LastFMTopTracksCardDefinition } from './media/LastFMCard/LastFMTopTracksCard'; 47 47 import { LastFMTopAlbumsCardDefinition } from './media/LastFMCard/LastFMTopAlbumsCard'; 48 48 import { LastFMProfileCardDefinition } from './media/LastFMCard/LastFMProfileCard'; 49 - import { PlyrFMCardDefinition } from './media/PlyrFMCard'; 49 + import { PlyrFMCardDefinition, PlyrFMCollectionCardDefinition } from './media/PlyrFMCard'; 50 50 import { MarginCardDefinition } from './social/MarginCard'; 51 51 import { SembleCollectionCardDefinition } from './social/SembleCollectionCard'; 52 52 // import { Model3DCardDefinition } from './visual/Model3DCard'; ··· 101 101 LastFMTopAlbumsCardDefinition, 102 102 LastFMProfileCardDefinition, 103 103 PlyrFMCardDefinition, 104 + PlyrFMCollectionCardDefinition, 104 105 MarginCardDefinition, 105 106 SembleCollectionCardDefinition 106 107 ] as const;
+10 -4
src/lib/cards/media/PlyrFMCard/CreatePlyrFMCardModal.svelte
··· 2 2 import { Alert, Button, Input, Subheading } from '@foxui/core'; 3 3 import Modal from '$lib/components/modal/Modal.svelte'; 4 4 import type { CreationModalComponentProps } from '../../types'; 5 - import { toPlyrFMEmbedUrl } from './index'; 5 + import { toPlyrFMEmbedUrl, toPlyrFMCollectionEmbedUrl } from './index'; 6 6 7 7 let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props(); 8 8 ··· 14 14 const embedUrl = toPlyrFMEmbedUrl(item.cardData.href); 15 15 16 16 if (!embedUrl) { 17 - errorMessage = 'Please enter a valid plyr.fm track URL'; 17 + errorMessage = 'Please enter a valid plyr.fm URL (track, playlist, or album)'; 18 18 return false; 19 19 } 20 20 21 21 item.cardData.href = embedUrl; 22 22 23 + // resize to collection dimensions if it's a playlist or album 24 + if (toPlyrFMCollectionEmbedUrl(item.cardData.href)) { 25 + item.h = 5; 26 + item.mobileH = 10; 27 + } 28 + 23 29 return true; 24 30 } 25 31 </script> 26 32 27 33 <Modal open={true} closeButton={false}> 28 - <Subheading>Enter a Plyr.fm track URL</Subheading> 34 + <Subheading>Enter a Plyr.fm URL</Subheading> 29 35 <Input 30 36 bind:value={item.cardData.href} 31 - placeholder="https://plyr.fm/track/..." 37 + placeholder="https://plyr.fm/track/... or /playlist/... or /u/.../album/..." 32 38 onkeydown={(e) => { 33 39 if (e.key === 'Enter' && checkUrl()) oncreate(); 34 40 }}
+1 -1
src/lib/cards/media/PlyrFMCard/PlyrFMCard.svelte
··· 12 12 frameborder="0" 13 13 allow="autoplay; encrypted-media" 14 14 loading="lazy" 15 - title="Plyr.fm track" 15 + title="Plyr.fm" 16 16 ></iframe> 17 17 </div> 18 18 {:else}
+96 -10
src/lib/cards/media/PlyrFMCard/index.ts
··· 2 2 import CreatePlyrFMCardModal from './CreatePlyrFMCardModal.svelte'; 3 3 import PlyrFMCard from './PlyrFMCard.svelte'; 4 4 5 - const cardType = 'plyr-fm'; 5 + const musicIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="m9 9 10.5-3m0 6.553v3.75a2.25 2.25 0 0 1-1.632 2.163l-1.32.377a1.803 1.803 0 1 1-.99-3.467l2.31-.66a2.25 2.25 0 0 0 1.632-2.163Zm0 0V4.125A2.25 2.25 0 0 0 17.378 1.9l-7.056 2.018A2.25 2.25 0 0 0 8.69 6.08v7.353m0 0v2.929a2.25 2.25 0 0 1-1.244 2.013L6.07 19.21a1.803 1.803 0 1 1-1.758-3.14l1.88-1.003A2.25 2.25 0 0 0 7.378 13.1v-.065Z" /></svg>`; 6 + 7 + // ── Track card (existing) ──────────────────────────────────────────── 8 + 9 + const trackType = 'plyr-fm'; 6 10 7 11 export const PlyrFMCardDefinition = { 8 - type: cardType, 12 + type: trackType, 9 13 contentComponent: PlyrFMCard, 10 14 creationModalComponent: CreatePlyrFMCardModal, 11 15 createNew: (item) => { 12 - item.cardType = cardType; 16 + item.cardType = trackType; 13 17 item.cardData = {}; 14 18 item.w = 4; 15 19 item.mobileW = 8; ··· 18 22 }, 19 23 20 24 onUrlHandler: (url, item) => { 21 - const embedUrl = toPlyrFMEmbedUrl(url); 25 + const embedUrl = toPlyrFMTrackEmbedUrl(url); 22 26 if (!embedUrl) return null; 23 27 24 28 item.cardData.href = embedUrl; ··· 38 42 minW: 2, 39 43 minH: 2, 40 44 41 - keywords: ['music', 'song', 'plyr', 'plyrfm', 'audio'], 45 + keywords: ['music', 'song', 'plyr', 'plyrfm', 'audio', 'track'], 42 46 groups: ['Media'], 43 - icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="m9 9 10.5-3m0 6.553v3.75a2.25 2.25 0 0 1-1.632 2.163l-1.32.377a1.803 1.803 0 1 1-.99-3.467l2.31-.66a2.25 2.25 0 0 0 1.632-2.163Zm0 0V4.125A2.25 2.25 0 0 0 17.378 1.9l-7.056 2.018A2.25 2.25 0 0 0 8.69 6.08v7.353m0 0v2.929a2.25 2.25 0 0 1-1.244 2.013L6.07 19.21a1.803 1.803 0 1 1-1.758-3.14l1.88-1.003A2.25 2.25 0 0 0 7.378 13.1v-.065Z" /></svg>` 44 - } as CardDefinition & { type: typeof cardType }; 47 + icon: musicIcon 48 + } as CardDefinition & { type: typeof trackType }; 49 + 50 + // ── Collection card (playlists + albums) ───────────────────────────── 51 + 52 + const collectionType = 'plyr-fm-collection'; 53 + 54 + export const PlyrFMCollectionCardDefinition = { 55 + type: collectionType, 56 + contentComponent: PlyrFMCard, 57 + creationModalComponent: CreatePlyrFMCardModal, 58 + createNew: (item) => { 59 + item.cardType = collectionType; 60 + item.cardData = {}; 61 + item.w = 4; 62 + item.mobileW = 8; 63 + item.h = 5; 64 + item.mobileH = 10; 65 + }, 45 66 46 - // Match plyr.fm track URLs (both embed and regular) 47 - // https://plyr.fm/embed/track/56 67 + onUrlHandler: (url, item) => { 68 + const embedUrl = toPlyrFMCollectionEmbedUrl(url); 69 + if (!embedUrl) return null; 70 + 71 + item.cardData.href = embedUrl; 72 + 73 + item.w = 4; 74 + item.mobileW = 8; 75 + item.h = 5; 76 + item.mobileH = 10; 77 + 78 + return item; 79 + }, 80 + 81 + // higher priority so collection URLs are matched before the track handler 82 + urlHandlerPriority: 3, 83 + 84 + name: 'Plyr.fm Playlist / Album', 85 + canResize: true, 86 + minW: 2, 87 + minH: 3, 88 + 89 + keywords: ['music', 'playlist', 'album', 'plyr', 'plyrfm', 'collection'], 90 + groups: ['Media'], 91 + icon: musicIcon 92 + } as CardDefinition & { type: typeof collectionType }; 93 + 94 + // ── URL matching ───────────────────────────────────────────────────── 95 + 96 + // Match plyr.fm track URLs 48 97 // https://plyr.fm/track/595 49 - export function toPlyrFMEmbedUrl(url: string | undefined): string | null { 98 + // https://plyr.fm/embed/track/56 99 + export function toPlyrFMTrackEmbedUrl(url: string | undefined): string | null { 50 100 if (!url) return null; 51 101 52 102 const match = url.match(/plyr\.fm\/(embed\/)?track\/(\d+)/); ··· 54 104 55 105 return `https://plyr.fm/embed/track/${match[2]}`; 56 106 } 107 + 108 + // Match plyr.fm playlist and album URLs 109 + // 110 + // Playlists: 111 + // https://plyr.fm/playlist/abc-def-123 112 + // https://plyr.fm/embed/playlist/abc-def-123 113 + // 114 + // Albums: 115 + // https://plyr.fm/u/handle/album/slug 116 + // https://plyr.fm/embed/album/handle/slug 117 + export function toPlyrFMCollectionEmbedUrl(url: string | undefined): string | null { 118 + if (!url) return null; 119 + 120 + // Playlist: /playlist/{uuid} or /embed/playlist/{uuid} 121 + const playlistMatch = url.match(/plyr\.fm\/(embed\/)?playlist\/([a-f0-9-]+)/i); 122 + if (playlistMatch) { 123 + return `https://plyr.fm/embed/playlist/${playlistMatch[2]}`; 124 + } 125 + 126 + // Album: /u/{handle}/album/{slug} or /embed/album/{handle}/{slug} 127 + const albumEmbedMatch = url.match(/plyr\.fm\/embed\/album\/([^/?#]+)\/([^/?#]+)/); 128 + if (albumEmbedMatch) { 129 + return `https://plyr.fm/embed/album/${albumEmbedMatch[1]}/${albumEmbedMatch[2]}`; 130 + } 131 + const albumPageMatch = url.match(/plyr\.fm\/u\/([^/?#]+)\/album\/([^/?#]+)/); 132 + if (albumPageMatch) { 133 + return `https://plyr.fm/embed/album/${albumPageMatch[1]}/${albumPageMatch[2]}`; 134 + } 135 + 136 + return null; 137 + } 138 + 139 + // Accepts any plyr.fm URL (track, playlist, or album) 140 + export function toPlyrFMEmbedUrl(url: string | undefined): string | null { 141 + return toPlyrFMTrackEmbedUrl(url) ?? toPlyrFMCollectionEmbedUrl(url); 142 + }