From c1d70c8a6057c03bf639822b74d6c16fdfc2774e Mon Sep 17 00:00:00 2001 From: unbedenklich <106080544+unbedenklich@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:10:03 +0100 Subject: [PATCH] spotify card --- src/lib/cards/BaseCard/BaseEditingCard.svelte | 3 +- .../SpotifyCard/CreateSpotifyCardModal.svelte | 51 ++++++++++++++ src/lib/cards/SpotifyCard/SpotifyCard.svelte | 23 +++++++ src/lib/cards/SpotifyCard/index.ts | 66 +++++++++++++++++++ src/lib/cards/index.ts | 4 +- 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/lib/cards/SpotifyCard/CreateSpotifyCardModal.svelte create mode 100644 src/lib/cards/SpotifyCard/SpotifyCard.svelte create mode 100644 src/lib/cards/SpotifyCard/index.ts diff --git a/src/lib/cards/BaseCard/BaseEditingCard.svelte b/src/lib/cards/BaseCard/BaseEditingCard.svelte index 852289c..93d80de 100644 --- a/src/lib/cards/BaseCard/BaseEditingCard.svelte +++ b/src/lib/cards/BaseCard/BaseEditingCard.svelte @@ -9,6 +9,7 @@ import { COLUMNS } from '$lib'; import { getCanEdit, getIsMobile } from '$lib/website/context'; import PlainTextEditor from '$lib/components/PlainTextEditor.svelte'; + import { fixAllCollisions, fixCollisions } from '$lib/helper'; let colorsChoices = [ { class: 'text-base-500', label: 'base' }, @@ -55,7 +56,7 @@ let colorPopoverOpen = $state(false); - const cardDef = $derived(CardDefinitionsByType[item.cardType]); + const cardDef = $derived(CardDefinitionsByType[item.cardType] ?? {}); const minW = $derived(cardDef.minW ?? (isMobile() ? 2 : 2)); const minH = $derived(cardDef.minH ?? (isMobile() ? 2 : 2)); diff --git a/src/lib/cards/SpotifyCard/CreateSpotifyCardModal.svelte b/src/lib/cards/SpotifyCard/CreateSpotifyCardModal.svelte new file mode 100644 index 0000000..5fe88c2 --- /dev/null +++ b/src/lib/cards/SpotifyCard/CreateSpotifyCardModal.svelte @@ -0,0 +1,51 @@ + + + + Enter a Spotify album or playlist URL + { + if (e.key === 'Enter' && checkUrl()) oncreate(); + }} + /> + + {#if errorMessage} + {errorMessage} + {/if} + +
+ + +
+
diff --git a/src/lib/cards/SpotifyCard/SpotifyCard.svelte b/src/lib/cards/SpotifyCard/SpotifyCard.svelte new file mode 100644 index 0000000..47580ca --- /dev/null +++ b/src/lib/cards/SpotifyCard/SpotifyCard.svelte @@ -0,0 +1,23 @@ + + +{#if item.cardData?.spotifyType && item.cardData?.spotifyId} +
+ +
+{:else} +
+ Missing Spotify data +
+{/if} diff --git a/src/lib/cards/SpotifyCard/index.ts b/src/lib/cards/SpotifyCard/index.ts new file mode 100644 index 0000000..9db2828 --- /dev/null +++ b/src/lib/cards/SpotifyCard/index.ts @@ -0,0 +1,66 @@ +import type { CardDefinition } from '../types'; +import CreateSpotifyCardModal from './CreateSpotifyCardModal.svelte'; +import SpotifyCard from './SpotifyCard.svelte'; + +const cardType = 'spotify-list-embed'; + +export const SpotifyCardDefinition = { + type: cardType, + contentComponent: SpotifyCard, + creationModalComponent: CreateSpotifyCardModal, + sidebarButtonText: 'Spotify Embed', + + createNew: (item) => { + item.cardType = cardType; + item.cardData = {}; + item.w = 4; + item.mobileW = 8; + item.h = 5; + item.mobileH = 10; + }, + + onUrlHandler: (url, item) => { + const match = matchSpotifyUrl(url); + if (!match) return null; + + item.cardData.spotifyType = match.type; + item.cardData.spotifyId = match.id; + item.cardData.href = url; + + item.w = 4; + item.mobileW = 8; + item.h = 5; + item.mobileH = 10; + + return item; + }, + + urlHandlerPriority: 2, + + name: 'Spotify Embed', + canResize: true, + minW: 4, + minH: 5 +} as CardDefinition & { type: typeof cardType }; + +// Match Spotify album and playlist URLs +// Examples: +// https://open.spotify.com/album/1DFixLWuPkv3KT3TnV35m3 +// https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M +function matchSpotifyUrl( + url: string | undefined +): { type: 'album' | 'playlist'; id: string } | null { + if (!url) return null; + + const pattern = /open\.spotify\.com\/(album|playlist)\/([a-zA-Z0-9]+)/; + const match = url.match(pattern); + + if (match) { + return { + type: match[1] as 'album' | 'playlist', + id: match[2] + }; + } + + return null; +} diff --git a/src/lib/cards/index.ts b/src/lib/cards/index.ts index b86875f..9302990 100644 --- a/src/lib/cards/index.ts +++ b/src/lib/cards/index.ts @@ -30,6 +30,7 @@ import { EventCardDefinition } from './EventCard'; import { VCardCardDefinition } from './VCardCard'; import { DrawCardDefinition } from './DrawCard'; import { TimerCardDefinition } from './TimerCard'; +import { SpotifyCardDefinition } from './SpotifyCard'; export const AllCardDefinitions = [ ImageCardDefinition, @@ -62,7 +63,8 @@ export const AllCardDefinitions = [ EventCardDefinition, VCardCardDefinition, DrawCardDefinition, - TimerCardDefinition + TimerCardDefinition, + SpotifyCardDefinition ] as const; export const CardDefinitionsByType = AllCardDefinitions.reduce( -- 2.51.2