@playpilot/tpi 6.11.0 → 6.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/dist/link-injections.js +8 -8
- package/package.json +1 -1
- package/src/lib/api/ads.ts +1 -0
- package/src/lib/types/config.d.ts +6 -0
- package/src/lib/types/script.d.ts +2 -0
- package/src/main.ts +3 -1
- package/src/routes/+page.svelte +2 -0
- package/src/routes/components/Explore/ExploreCallToAction.svelte +0 -3
- package/src/routes/components/ListTitle.svelte +11 -2
- package/src/routes/components/Title.svelte +6 -2
- package/src/tests/lib/api/ads.test.js +8 -1
- package/src/tests/routes/+page.test.js +33 -0
- package/src/tests/routes/components/ListTitle.test.js +11 -0
- package/src/tests/routes/components/Title.test.js +24 -0
package/package.json
CHANGED
package/src/lib/api/ads.ts
CHANGED
|
@@ -30,6 +30,7 @@ export async function fetchAds(): Promise<Campaign[]> {
|
|
|
30
30
|
*/
|
|
31
31
|
export function getFirstAdOfType(format: CampaignFormat): Campaign | null {
|
|
32
32
|
if (!hasConsentedTo('ads')) return null
|
|
33
|
+
if (window.PlayPilotLinkInjections.no_affiliate) return null
|
|
33
34
|
|
|
34
35
|
return (window.PlayPilotLinkInjections?.ads || []).find(i => i.campaign_format === format) || null
|
|
35
36
|
}
|
|
@@ -44,6 +44,12 @@ export type ConfigResponse = {
|
|
|
44
44
|
*/
|
|
45
45
|
playlinks_disclaimer_text?: string
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Some partners may want to display titles without affiliate links, which currently comes down to no playlinks.
|
|
49
|
+
* This can also be set via the config on the script itself.
|
|
50
|
+
*/
|
|
51
|
+
no_affiliate?: boolean
|
|
52
|
+
|
|
47
53
|
/**
|
|
48
54
|
* Whether or not we can insert retargeting pixels. Not all third parties allow this and it is disabled by default
|
|
49
55
|
*/
|
|
@@ -32,6 +32,8 @@ export type ScriptConfig = {
|
|
|
32
32
|
evaluated_link_injections?: LinkInjection[]
|
|
33
33
|
// By default the script requires consent from the user through tcfapi. Can be disabled using this setting.
|
|
34
34
|
require_consent?: boolean
|
|
35
|
+
// Some partners may want to display titles without affiliate links, which currently comes down to no playlinks.
|
|
36
|
+
no_affiliate?: boolean
|
|
35
37
|
// Used to check if a user has consented via tcfapi to various consent categories.
|
|
36
38
|
consents?: ConsentOptions
|
|
37
39
|
// The config response from the api, saved to the window object to be used without having to pass the response around.
|
package/src/main.ts
CHANGED
|
@@ -22,6 +22,7 @@ window.PlayPilotLinkInjections = {
|
|
|
22
22
|
evaluated_link_injections: [],
|
|
23
23
|
ads: [],
|
|
24
24
|
require_consent: true,
|
|
25
|
+
no_affiliate: false,
|
|
25
26
|
consents: {
|
|
26
27
|
ads: false,
|
|
27
28
|
pixels: false,
|
|
@@ -32,7 +33,7 @@ window.PlayPilotLinkInjections = {
|
|
|
32
33
|
config: {},
|
|
33
34
|
app: null,
|
|
34
35
|
|
|
35
|
-
initialize(options = { token: '', selector: '', after_article_selector: '', after_article_insert_position: '', language: null, region: null, organization_sid: null, domain_sid: null, editorial_token: '', require_consent: true }): void {
|
|
36
|
+
initialize(options = { token: '', selector: '', after_article_selector: '', after_article_insert_position: '', language: null, region: null, organization_sid: null, domain_sid: null, editorial_token: '', require_consent: true, no_affiliate: false }): void {
|
|
36
37
|
if (!options.token) {
|
|
37
38
|
console.error('An API token is required.')
|
|
38
39
|
return
|
|
@@ -49,6 +50,7 @@ window.PlayPilotLinkInjections = {
|
|
|
49
50
|
this.organization_sid = options.organization_sid
|
|
50
51
|
this.domain_sid = options.domain_sid
|
|
51
52
|
this.require_consent = options.require_consent
|
|
53
|
+
this.no_affiliate = options.no_affiliate
|
|
52
54
|
|
|
53
55
|
if (this.require_consent === false) {
|
|
54
56
|
setConsent({
|
package/src/routes/+page.svelte
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
track(TrackingEvent.ArticlePageView)
|
|
59
59
|
|
|
60
60
|
if (!aiInjections.length && !manualInjections.length) return
|
|
61
|
+
if (window.PlayPilotLinkInjections.no_affiliate) return
|
|
61
62
|
|
|
62
63
|
window.PlayPilotLinkInjections.ads = await fetchAds()
|
|
63
64
|
}
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
|
|
76
77
|
if (config?.custom_style) insertCustomStyle(config.custom_style || '')
|
|
77
78
|
if (config?.explore_navigation_selector) insertExploreIntoNavigation()
|
|
79
|
+
if (config?.no_affiliate) window.PlayPilotLinkInjections.no_affiliate = true
|
|
78
80
|
|
|
79
81
|
isUrlExcluded = !!(config?.exclude_urls_pattern && url.match(new RegExp(config.exclude_urls_pattern)))
|
|
80
82
|
if (isUrlExcluded) return
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import IconArrow from '../Icons/IconArrow.svelte'
|
|
3
|
-
import ImageTitlesList from '$lib/images/titles-list.webp'
|
|
4
3
|
import { openModal } from '$lib/modal'
|
|
5
4
|
</script>
|
|
6
5
|
|
|
7
6
|
<button class="call-to-action" onclick={() => openModal({ type: 'explore' })}>
|
|
8
|
-
<img src={ImageTitlesList} alt="" width="70" height="57" />
|
|
9
|
-
|
|
10
7
|
<div>
|
|
11
8
|
<strong>Looking for something else?</strong>
|
|
12
9
|
<div>Use our streamingguide</div>
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const { title, onclick = () => null }: Props = $props()
|
|
14
|
+
|
|
15
|
+
const noAffiliate = !!window.PlayPilotLinkInjections?.no_affiliate
|
|
14
16
|
</script>
|
|
15
17
|
|
|
16
18
|
<button class="title" {onclick} data-testid="title">
|
|
@@ -35,11 +37,13 @@
|
|
|
35
37
|
{/if}
|
|
36
38
|
</div>
|
|
37
39
|
|
|
38
|
-
<div class="description">
|
|
40
|
+
<div class="description" class:large={noAffiliate}>
|
|
39
41
|
{title.description}
|
|
40
42
|
</div>
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
{#if !noAffiliate}
|
|
45
|
+
<PlaylinksCompact playlinks={title.providers} />
|
|
46
|
+
{/if}
|
|
43
47
|
</div>
|
|
44
48
|
|
|
45
49
|
<div class="action">
|
|
@@ -129,6 +133,11 @@
|
|
|
129
133
|
-webkit-box-orient: vertical;
|
|
130
134
|
font-size: theme(detail-font-size-small, font-size-small);
|
|
131
135
|
color: theme(list-item-description-text-color, text-color);
|
|
136
|
+
|
|
137
|
+
&.large {
|
|
138
|
+
line-clamp: 2;
|
|
139
|
+
-webkit-line-clamp: 2;
|
|
140
|
+
}
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
.action {
|
|
@@ -24,9 +24,11 @@
|
|
|
24
24
|
|
|
25
25
|
const { title, small = false }: Props = $props()
|
|
26
26
|
|
|
27
|
+
const noAffiliate = !!window.PlayPilotLinkInjections?.no_affiliate
|
|
28
|
+
|
|
27
29
|
setContext('title', title)
|
|
28
30
|
|
|
29
|
-
const showDescription = $derived(!small && title.description)
|
|
31
|
+
const showDescription = $derived((!small || noAffiliate) && title.description)
|
|
30
32
|
|
|
31
33
|
let posterLoaded = $state(false)
|
|
32
34
|
let backgroundLoaded = $state(false)
|
|
@@ -71,7 +73,9 @@
|
|
|
71
73
|
<Description text={title.description!} blurb={title.blurb} onclick={() => track(TrackingEvent.ExpandTitleDescription, title)} />
|
|
72
74
|
{/if}
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
{#if !noAffiliate}
|
|
77
|
+
<Playlinks playlinks={title.providers} {title} />
|
|
78
|
+
{/if}
|
|
75
79
|
|
|
76
80
|
<ParticipantsRail {title} />
|
|
77
81
|
<SimilarRail {title} />
|
|
@@ -81,7 +81,7 @@ describe('$lib/api/ads', () => {
|
|
|
81
81
|
expect(getFirstAdOfType('top_scroll')).toBe(null)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
-
it('Should not return ads if valid ad was given but user did not consent to ads',
|
|
84
|
+
it('Should not return ads if valid ad was given but user did not consent to ads', () => {
|
|
85
85
|
vi.mocked(hasConsentedTo).mockImplementation(() => false)
|
|
86
86
|
|
|
87
87
|
// @ts-ignore
|
|
@@ -89,5 +89,12 @@ describe('$lib/api/ads', () => {
|
|
|
89
89
|
|
|
90
90
|
expect(getFirstAdOfType('top_scroll')).toBe(null)
|
|
91
91
|
})
|
|
92
|
+
|
|
93
|
+
it('Should not return ads even if given if no_affiliate is true', () => {
|
|
94
|
+
// @ts-ignore
|
|
95
|
+
window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'top_scroll' }], no_affiliate: true }
|
|
96
|
+
|
|
97
|
+
expect(getFirstAdOfType('top_scroll')).toBe(null)
|
|
98
|
+
})
|
|
92
99
|
})
|
|
93
100
|
})
|
|
@@ -459,6 +459,19 @@ describe('$routes/+page.svelte', () => {
|
|
|
459
459
|
expect(fetchAds).not.toHaveBeenCalled()
|
|
460
460
|
})
|
|
461
461
|
|
|
462
|
+
it('Should not fetch ads if no_affilite is true', async () => {
|
|
463
|
+
// @ts-ignore
|
|
464
|
+
vi.mocked(pollLinkInjections).mockResolvedValueOnce({ ai_injections: [{}], manual_injections: [{}] })
|
|
465
|
+
// @ts-ignore
|
|
466
|
+
window.PlayPilotLinkInjections = { no_affiliate: true }
|
|
467
|
+
|
|
468
|
+
render(page)
|
|
469
|
+
|
|
470
|
+
await new Promise(res => setTimeout(res, 100)) // Await possible fetches
|
|
471
|
+
|
|
472
|
+
expect(fetchAds).not.toHaveBeenCalled()
|
|
473
|
+
})
|
|
474
|
+
|
|
462
475
|
describe('Config', () => {
|
|
463
476
|
describe('exclude_urls_pattern', () => {
|
|
464
477
|
it('Should not inject if config exclude_urls_pattern matches current url', async () => {
|
|
@@ -617,5 +630,25 @@ describe('$routes/+page.svelte', () => {
|
|
|
617
630
|
expect(document.querySelector('#playpilot-custom-style')).not.toBeTruthy()
|
|
618
631
|
})
|
|
619
632
|
})
|
|
633
|
+
|
|
634
|
+
describe('no_affiliate', () => {
|
|
635
|
+
it('Should not set window object if value is not given', async () => {
|
|
636
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({})
|
|
637
|
+
|
|
638
|
+
render(page)
|
|
639
|
+
|
|
640
|
+
await new Promise(res => setTimeout(res, 100)) // Await all fetches
|
|
641
|
+
expect(window.PlayPilotLinkInjections.no_affiliate).not.toBe(true)
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
it('Should set window object if value is true', async () => {
|
|
645
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ no_affiliate: true })
|
|
646
|
+
|
|
647
|
+
render(page)
|
|
648
|
+
|
|
649
|
+
await new Promise(res => setTimeout(res, 100)) // Await all fetches
|
|
650
|
+
expect(window.PlayPilotLinkInjections.no_affiliate).toBe(true)
|
|
651
|
+
})
|
|
652
|
+
})
|
|
620
653
|
})
|
|
621
654
|
})
|
|
@@ -15,6 +15,8 @@ vi.mock('svelte', async (importActual) => ({
|
|
|
15
15
|
|
|
16
16
|
describe('ListTitle.svelte', () => {
|
|
17
17
|
beforeEach(() => {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
window.PlayPilotLinkInjections = {}
|
|
18
20
|
vi.resetAllMocks()
|
|
19
21
|
})
|
|
20
22
|
|
|
@@ -80,5 +82,14 @@ describe('ListTitle.svelte', () => {
|
|
|
80
82
|
|
|
81
83
|
expect(onclick).not.toHaveBeenCalled()
|
|
82
84
|
})
|
|
85
|
+
|
|
86
|
+
it('Should not render playlinks if no_affiliate is given in window object', () => {
|
|
87
|
+
window.PlayPilotLinkInjections.no_affiliate = true
|
|
88
|
+
|
|
89
|
+
// @ts-ignore
|
|
90
|
+
const { container } = render(ListTitle, { title: { ...title, providers: playlinks } })
|
|
91
|
+
|
|
92
|
+
expect(container.querySelectorAll('.playlink')).toHaveLength(0)
|
|
93
|
+
})
|
|
83
94
|
})
|
|
84
95
|
})
|
|
@@ -26,6 +26,8 @@ vi.mock('svelte', async (importActual) => ({
|
|
|
26
26
|
|
|
27
27
|
describe('Title.svelte', () => {
|
|
28
28
|
beforeEach(() => {
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
window.PlayPilotLinkInjections = {}
|
|
29
31
|
vi.resetAllMocks()
|
|
30
32
|
})
|
|
31
33
|
|
|
@@ -104,4 +106,26 @@ describe('Title.svelte', () => {
|
|
|
104
106
|
|
|
105
107
|
expect(queryByText('Watch trailer')).not.toBeTruthy()
|
|
106
108
|
})
|
|
109
|
+
|
|
110
|
+
it('Should render playlinks by default', () => {
|
|
111
|
+
const { getByText } = render(Title, { title })
|
|
112
|
+
|
|
113
|
+
expect(getByText('Where to stream online')).toBeTruthy()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('Should not render playlinks if no_affiliate is true on config', () => {
|
|
117
|
+
window.PlayPilotLinkInjections.no_affiliate = true
|
|
118
|
+
|
|
119
|
+
const { queryByText } = render(Title, { title })
|
|
120
|
+
|
|
121
|
+
expect(queryByText('Where to stream online')).not.toBeTruthy()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('Should render description if no_affiliate is true even if small is true', () => {
|
|
125
|
+
window.PlayPilotLinkInjections.no_affiliate = true
|
|
126
|
+
|
|
127
|
+
const { getByText } = render(Title, { title: { ...title, description: 'Some description' }, small: true })
|
|
128
|
+
|
|
129
|
+
expect(getByText('Some description')).toBeTruthy()
|
|
130
|
+
})
|
|
107
131
|
})
|