@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "6.11.0",
3
+ "version": "6.12.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -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({
@@ -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
- <PlaylinksCompact playlinks={title.providers} />
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
- <Playlinks playlinks={title.providers} {title} />
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', async () => {
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
  })