Bandcamp embed (#9445)
This commit is contained in:
@@ -437,6 +437,13 @@ describe('parseEmbedPlayerFromUrl', () => {
|
|||||||
|
|
||||||
'https://www.flickr.com/groups/898944@N23/',
|
'https://www.flickr.com/groups/898944@N23/',
|
||||||
'https://www.flickr.com/groups',
|
'https://www.flickr.com/groups',
|
||||||
|
|
||||||
|
'https://maxblansjaar.bandcamp.com/album/false-comforts',
|
||||||
|
'https://grmnygrmny.bandcamp.com/track/fluid',
|
||||||
|
'https://sufjanstevens.bandcamp.com/',
|
||||||
|
'https://sufjanstevens.bandcamp.com',
|
||||||
|
'https://bandcamp.com/',
|
||||||
|
'https://bandcamp.com',
|
||||||
]
|
]
|
||||||
|
|
||||||
const outputs = [
|
const outputs = [
|
||||||
@@ -815,6 +822,23 @@ describe('parseEmbedPlayerFromUrl', () => {
|
|||||||
|
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
|
|
||||||
|
{
|
||||||
|
type: 'bandcamp_album',
|
||||||
|
source: 'bandcamp',
|
||||||
|
playerUri:
|
||||||
|
'https://bandcamp.com/EmbeddedPlayer/url=https%3A%2F%2Fmaxblansjaar.bandcamp.com%2Falbum%2Ffalse-comforts/size=large/bgcol=ffffff/linkcol=0687f5/minimal=true/transparent=true/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bandcamp_track',
|
||||||
|
source: 'bandcamp',
|
||||||
|
playerUri:
|
||||||
|
'https://bandcamp.com/EmbeddedPlayer/url=https%3A%2F%2Fgrmnygrmny.bandcamp.com%2Ftrack%2Ffluid/size=large/bgcol=ffffff/linkcol=0687f5/minimal=true/transparent=true/',
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
]
|
]
|
||||||
|
|
||||||
it('correctly grabs the correct id from uri', () => {
|
it('correctly grabs the correct id from uri', () => {
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {
|
|||||||
externalEmbedLabels,
|
externalEmbedLabels,
|
||||||
} from '#/lib/strings/embed-player'
|
} from '#/lib/strings/embed-player'
|
||||||
import {useSetExternalEmbedPref} from '#/state/preferences'
|
import {useSetExternalEmbedPref} from '#/state/preferences'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, web} from '#/alf'
|
||||||
|
import {Admonition} from '#/components/Admonition'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -24,9 +25,7 @@ export function EmbedConsentDialog({
|
|||||||
onAccept: () => void
|
onAccept: () => void
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
|
||||||
const setExternalEmbedPref = useSetExternalEmbedPref()
|
const setExternalEmbedPref = useSetExternalEmbedPref()
|
||||||
const {gtMobile} = useBreakpoints()
|
|
||||||
|
|
||||||
const onShowAllPress = useCallback(() => {
|
const onShowAllPress = useCallback(() => {
|
||||||
for (const key of embedPlayerSources) {
|
for (const key of embedPlayerSources) {
|
||||||
@@ -52,50 +51,46 @@ export function EmbedConsentDialog({
|
|||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<Dialog.ScrollableInner
|
<Dialog.ScrollableInner
|
||||||
label={_(msg`External Media`)}
|
label={_(msg`External Media`)}
|
||||||
style={[gtMobile ? {width: 'auto', maxWidth: 400} : a.w_full]}>
|
style={web({maxWidth: 400})}>
|
||||||
<View style={a.gap_sm}>
|
<View style={a.gap_sm}>
|
||||||
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
<Text style={[a.text_2xl, a.font_bold]}>
|
||||||
<Trans>External Media</Trans>
|
<Trans>External Media</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={[a.mt_sm, a.mb_2xl, a.gap_lg]}>
|
<View style={[a.mt_sm, a.mb_2xl, a.gap_lg]}>
|
||||||
<Text>
|
<Text style={[a.text_md, a.leading_snug]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
This content is hosted by {externalEmbedLabels[source]}. Do you
|
This content is hosted by {externalEmbedLabels[source]}. Do you
|
||||||
want to enable external media?
|
want to enable external media?
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text style={t.atoms.text_contrast_medium}>
|
<Admonition type="info">
|
||||||
<Trans>
|
<Trans>
|
||||||
External media may allow websites to collect information about
|
External media may allow websites to collect information about
|
||||||
you and your device. No information is sent or requested until
|
you and your device. No information is sent or requested until
|
||||||
you press the "play" button.
|
you press the "play" button.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Admonition>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={a.gap_md}>
|
<View style={a.gap_md}>
|
||||||
<Button
|
<Button
|
||||||
style={gtMobile && a.flex_1}
|
|
||||||
label={_(msg`Enable external media`)}
|
label={_(msg`Enable external media`)}
|
||||||
onPress={onShowAllPress}
|
onPress={onShowAllPress}
|
||||||
onAccessibilityEscape={control.close}
|
onAccessibilityEscape={control.close}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="large"
|
size="large">
|
||||||
variant="solid">
|
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Enable external media</Trans>
|
<Trans>Enable external media</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
style={gtMobile && a.flex_1}
|
|
||||||
label={_(msg`Enable this source only`)}
|
label={_(msg`Enable this source only`)}
|
||||||
onPress={onShowPress}
|
onPress={onShowPress}
|
||||||
onAccessibilityEscape={control.close}
|
onAccessibilityEscape={control.close}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="large"
|
size="large">
|
||||||
variant="solid">
|
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Enable {externalEmbedLabels[source]} only</Trans>
|
<Trans>Enable {externalEmbedLabels[source]} only</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const embedPlayerSources = [
|
|||||||
'giphy',
|
'giphy',
|
||||||
'tenor',
|
'tenor',
|
||||||
'flickr',
|
'flickr',
|
||||||
|
'bandcamp',
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type EmbedPlayerSource = (typeof embedPlayerSources)[number]
|
export type EmbedPlayerSource = (typeof embedPlayerSources)[number]
|
||||||
@@ -44,6 +45,8 @@ export type EmbedPlayerType =
|
|||||||
| 'giphy_gif'
|
| 'giphy_gif'
|
||||||
| 'tenor_gif'
|
| 'tenor_gif'
|
||||||
| 'flickr_album'
|
| 'flickr_album'
|
||||||
|
| 'bandcamp_album'
|
||||||
|
| 'bandcamp_track'
|
||||||
|
|
||||||
export const externalEmbedLabels: Record<EmbedPlayerSource, string> = {
|
export const externalEmbedLabels: Record<EmbedPlayerSource, string> = {
|
||||||
youtube: 'YouTube',
|
youtube: 'YouTube',
|
||||||
@@ -56,6 +59,7 @@ export const externalEmbedLabels: Record<EmbedPlayerSource, string> = {
|
|||||||
appleMusic: 'Apple Music',
|
appleMusic: 'Apple Music',
|
||||||
soundcloud: 'SoundCloud',
|
soundcloud: 'SoundCloud',
|
||||||
flickr: 'Flickr',
|
flickr: 'Flickr',
|
||||||
|
bandcamp: 'Bandcamp',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmbedPlayerParams {
|
export interface EmbedPlayerParams {
|
||||||
@@ -459,6 +463,32 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bandcampRegex = /^[a-z\d][a-z\d-]{2,}[a-z\d]\.bandcamp\.com$/i
|
||||||
|
|
||||||
|
if (bandcampRegex.test(urlp.hostname)) {
|
||||||
|
const pathComponents = urlp.pathname.split('/')
|
||||||
|
switch (pathComponents[1]) {
|
||||||
|
case 'album':
|
||||||
|
return {
|
||||||
|
type: 'bandcamp_album',
|
||||||
|
source: 'bandcamp',
|
||||||
|
playerUri: `https://bandcamp.com/EmbeddedPlayer/url=${encodeURIComponent(
|
||||||
|
urlp.href,
|
||||||
|
)}/size=large/bgcol=ffffff/linkcol=0687f5/minimal=true/transparent=true/`,
|
||||||
|
}
|
||||||
|
case 'track':
|
||||||
|
return {
|
||||||
|
type: 'bandcamp_track',
|
||||||
|
source: 'bandcamp',
|
||||||
|
playerUri: `https://bandcamp.com/EmbeddedPlayer/url=${encodeURIComponent(
|
||||||
|
urlp.href,
|
||||||
|
)}/size=large/bgcol=ffffff/linkcol=0687f5/minimal=true/transparent=true/`,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPlayerAspect({
|
export function getPlayerAspect({
|
||||||
@@ -498,6 +528,9 @@ export function getPlayerAspect({
|
|||||||
return {height: 165}
|
return {height: 165}
|
||||||
case 'apple_music_song':
|
case 'apple_music_song':
|
||||||
return {height: 150}
|
return {height: 150}
|
||||||
|
case 'bandcamp_album':
|
||||||
|
case 'bandcamp_track':
|
||||||
|
return {aspectRatio: 1}
|
||||||
default:
|
default:
|
||||||
return {aspectRatio: 16 / 9}
|
return {aspectRatio: 16 / 9}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ const schema = z.object({
|
|||||||
appleMusic: z.enum(externalEmbedOptions).optional(),
|
appleMusic: z.enum(externalEmbedOptions).optional(),
|
||||||
soundcloud: z.enum(externalEmbedOptions).optional(),
|
soundcloud: z.enum(externalEmbedOptions).optional(),
|
||||||
flickr: z.enum(externalEmbedOptions).optional(),
|
flickr: z.enum(externalEmbedOptions).optional(),
|
||||||
|
bandcamp: z.enum(externalEmbedOptions).optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
invites: z.object({
|
invites: z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user