@playpilot/tpi 8.27.0 → 8.28.0-beta.1

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": "8.27.0",
3
+ "version": "8.28.0-beta.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -23,7 +23,7 @@ export async function fetchLinkInjections(
23
23
 
24
24
  if (!apiToken) throw new Error('No token was provided')
25
25
 
26
- const apiUrl = `/external-pages/?api-token=${apiToken}&include_title_details=true${isEditorialMode ? '&editorial_mode_enabled=true' : ''}&language=${language}`
26
+ const apiUrl = `/external-pages/?api-token=${apiToken}&include_title_details=true&include_participant_details=true${isEditorialMode ? '&editorial_mode_enabled=true' : ''}&language=${language}`
27
27
  let response: LinkInjectionResponse
28
28
 
29
29
  if (method === 'POST') {
@@ -31,6 +31,7 @@
31
31
 
32
32
  <style lang="scss">
33
33
  .back {
34
+ z-index: 10;
34
35
  position: absolute;
35
36
  top: margin(1.5);
36
37
  left: var(--modal-close-overlay-left, margin(1));
@@ -57,6 +58,7 @@
57
58
  .close {
58
59
  --playpilot-button-background: transparent;
59
60
  --playpilot-button-text-color: white;
61
+ z-index: 10;
60
62
  position: absolute;
61
63
  top: margin(1);
62
64
  right: margin(1);
@@ -24,7 +24,7 @@
24
24
 
25
25
  let slider: TinySlider
26
26
  let initialized = $state(false)
27
- let availableHeight: number = $state(window.innerHeight)
27
+ let availableHeight: number | null = $state(null)
28
28
  let element: HTMLElement | null = $state(null)
29
29
 
30
30
  onMount(() => {
@@ -47,6 +47,11 @@
47
47
  }
48
48
 
49
49
  function setAvailableHeight(): void {
50
+ if (!displayAd) {
51
+ availableHeight = null
52
+ return
53
+ }
54
+
50
55
  const modal = document.querySelector('.modal')!
51
56
  availableHeight = window.innerHeight - (element!.getBoundingClientRect().top + modal.scrollTop + parseInt(window.getComputedStyle(modal).paddingBottom))
52
57
  }
@@ -64,7 +69,7 @@
64
69
 
65
70
  <Modal blur {onclose} {prepend} {...restProps}>
66
71
  {#snippet dialog()}
67
- <div class="rail-modal" class:initialized style:--transition-duration="{transitionDuration}ms" style:--available-height="{availableHeight}px" bind:this={element}>
72
+ <div class="rail-modal" class:initialized style:--transition-duration="{transitionDuration}ms" style:--available-height={availableHeight && availableHeight + 'px'} bind:this={element}>
68
73
  <TinySlider threshold={40} moveThreshold={40} transitionDuration={initialized ? transitionDuration : 0} bind:this={slider}>
69
74
  {#snippet children({ currentIndex })}
70
75
  {#each items as item, index}
@@ -165,7 +170,7 @@
165
170
  border-radius: theme(rail-modal-item-border-radius, border-radius-huge) theme(rail-modal-item-border-radius, border-radius-huge) 0 0;
166
171
  background: theme(rail-modal-item-background, light);
167
172
  box-shadow: none;
168
- height: calc(var(--available-height, 90vh) - env(safe-area-inset-top));
173
+ height: calc(var(--available-height, 90vh) - margin(1) - env(safe-area-inset-top));
169
174
  overflow: auto;
170
175
  overscroll-behavior: contain;
171
176
  transition: box-shadow var(--transition-duration);
@@ -1,160 +1,160 @@
1
- <script lang="ts">
2
- import { onMount } from 'svelte'
3
- import { heading } from '$lib/actions/heading'
4
- import { fetchTitlesForParticipant } from '$lib/api/participants'
5
- import { openModal } from '$lib/modal'
6
- import type { ParticipantData } from '$lib/types/participant'
7
- import type { TitleData } from '$lib/types/title'
8
- import ListTitle from './ListTitle.svelte'
9
- import { t } from '$lib/localization'
10
- import { isRetargetingAllowed } from '$lib/retargeting'
11
- import { trackViaPixel } from '@playpilot/retargeting-tracking'
12
- import { MetaEvent } from '$lib/enums/TrackingEvent'
13
-
14
- interface Props {
15
- participant: ParticipantData
16
- small?: boolean
17
- }
18
-
19
- const { participant, small = false }: Props = $props()
20
-
21
- const { name, birth_date, death_date } = $derived(participant)
22
-
23
- const pageSize = 30
24
-
25
- let titles: TitleData[] = $state([])
26
- let page = $state(1)
27
- let hasMorePages = $state(true)
28
- let loading = $state(true)
29
-
30
- onMount(loadMore)
31
-
32
- if (isRetargetingAllowed()) trackViaPixel(MetaEvent.ParticipantView, { participant: participant.name })
33
-
34
- function formatDate(dateString: string): string {
35
- const date = new Date(dateString)
36
- return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
37
- }
38
-
39
- async function loadMore(): Promise<void> {
40
- loading = true
41
-
42
- try {
43
- const results = await fetchTitlesForParticipant(participant, { page })
44
-
45
- titles = [...titles, ...results]
46
- hasMorePages = results?.length >= pageSize
47
- } catch {
48
- hasMorePages = false
49
- } finally {
50
- loading = false
51
- page += 1
52
- }
53
- }
54
- </script>
55
-
56
- <div class="header" class:small>
57
- <div class="heading" use:heading={2} id="heading">{name}</div>
58
-
59
- {#if birth_date}
60
- <p class="dates">
61
- {t('Born On')} <strong>{formatDate(birth_date)}</strong>{#if death_date}, {t('Died On')} <strong>{formatDate(death_date)}</strong>{/if}
62
- </p>
63
- {/if}
64
- </div>
65
-
66
- <div class="content">
67
- {#if !small}
68
- <div class="heading subheading" use:heading={3} id="credits">{t('Credits')}</div>
69
- {/if}
70
-
71
- <div class="list">
72
- {#each titles as title}
73
- <ListTitle {title} compact={small} onclick={(event) => openModal({ event, data: title })} />
74
- {/each}
75
-
76
- {#if loading}
77
- {#each { length: 6 }}
78
- <div class="skeleton" data-testid="skeleton"></div>
79
- {/each}
80
- {:else if hasMorePages}
81
- <button class="more" onclick={loadMore}>{t('Show More')}</button>
82
- {/if}
83
- </div>
84
- </div>
85
-
86
- <style lang="scss">
87
- .header {
88
- padding: margin(4) margin(1) margin(2);
89
- font-family: theme(detail-font-family, font-family);
90
- font-weight: theme(detail-font-weight, normal);
91
- font-size: theme(detail-font-size, font-size-base);
92
- line-height: theme(participant-description-line-height, normal);
93
- color: theme(detail-text-color, text-color);
94
-
95
- &.small {
96
- padding: margin(1);
97
- }
98
- }
99
-
100
- .heading {
101
- margin: 0;
102
- font-family: theme(detail-title-font-family, font-family);
103
- font-weight: theme(detail-title-font-weight, lighter);
104
- font-size: theme(detail-title-font-size, margin(1.5));
105
- line-height: normal;
106
- font-style: theme(detail-title-font-style, normal);
107
-
108
- &.subheading {
109
- margin: 0 0 margin(0.5);
110
- font-size: theme(detail-title-small-font-size, margin(1.25));
111
- }
112
- }
113
-
114
- .dates {
115
- margin: margin(0.5) 0 0;
116
- }
117
-
118
- .content {
119
- padding: 0 margin(1) margin(1);
120
- color: theme(detail-text-color, text-color);
121
- font-family: theme(detail-font-family, font-family);
122
- font-weight: theme(detail-font-weight, normal);
123
- font-size: theme(detail-font-size, font-size-base);
124
- line-height: normal;
125
- font-style: normal;
126
-
127
- ::view-transition-old(content),
128
- ::view-transition-new(content) {
129
- height: 100%;
130
- }
131
- }
132
-
133
- .list {
134
- display: flex;
135
- flex-direction: column;
136
- gap: margin(0.5);
137
- }
138
-
139
- .skeleton {
140
- min-height: margin(7.25);
141
- border-radius: theme(playlink-border-radius, border-radius);
142
- background: theme(detail-background-light, lighter);
143
- }
144
-
145
- .more {
146
- cursor: pointer;
147
- appearance: none;
148
- padding: margin(0.5) margin(1);
149
- border: 0;
150
- border-radius: theme(participants-load-more-border-radius, border-radius);
151
- background: theme(participants-load-more-background, content);
152
- color: inherit;
153
- font-size: inherit;
154
- font-family: inherit;
155
-
156
- &:hover {
157
- filter: brightness(1.2);
158
- }
159
- }
160
- </style>
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte'
3
+ import { heading } from '$lib/actions/heading'
4
+ import { fetchTitlesForParticipant } from '$lib/api/participants'
5
+ import { openModal } from '$lib/modal'
6
+ import type { ParticipantData } from '$lib/types/participant'
7
+ import type { TitleData } from '$lib/types/title'
8
+ import ListTitle from './ListTitle.svelte'
9
+ import { t } from '$lib/localization'
10
+ import { isRetargetingAllowed } from '$lib/retargeting'
11
+ import { trackViaPixel } from '@playpilot/retargeting-tracking'
12
+ import { MetaEvent } from '$lib/enums/TrackingEvent'
13
+
14
+ interface Props {
15
+ participant: ParticipantData
16
+ small?: boolean
17
+ }
18
+
19
+ const { participant, small = false }: Props = $props()
20
+
21
+ const { name, birth_date, death_date } = $derived(participant)
22
+
23
+ const pageSize = 30
24
+
25
+ let titles: TitleData[] = $state([])
26
+ let page = $state(1)
27
+ let hasMorePages = $state(true)
28
+ let loading = $state(true)
29
+
30
+ onMount(loadMore)
31
+
32
+ if (isRetargetingAllowed()) trackViaPixel(MetaEvent.ParticipantView, { participant: participant.name })
33
+
34
+ function formatDate(dateString: string): string {
35
+ const date = new Date(dateString)
36
+ return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
37
+ }
38
+
39
+ async function loadMore(): Promise<void> {
40
+ loading = true
41
+
42
+ try {
43
+ const results = await fetchTitlesForParticipant(participant, { page })
44
+
45
+ titles = [...titles, ...results]
46
+ hasMorePages = results?.length >= pageSize
47
+ } catch {
48
+ hasMorePages = false
49
+ } finally {
50
+ loading = false
51
+ page += 1
52
+ }
53
+ }
54
+ </script>
55
+
56
+ <div class="header" class:small>
57
+ <div class="heading" use:heading={2} id="heading">{name}</div>
58
+
59
+ {#if birth_date}
60
+ <p class="dates">
61
+ {t('Born On')} <strong>{formatDate(birth_date)}</strong>{#if death_date}, {t('Died On')} <strong>{formatDate(death_date)}</strong>{/if}
62
+ </p>
63
+ {/if}
64
+ </div>
65
+
66
+ <div class="content">
67
+ {#if !small}
68
+ <div class="heading subheading" use:heading={3} id="credits">{t('Credits')}</div>
69
+ {/if}
70
+
71
+ <div class="list">
72
+ {#each titles as title}
73
+ <ListTitle {title} compact={small} onclick={(event) => openModal({ event, data: title })} />
74
+ {/each}
75
+
76
+ {#if loading}
77
+ {#each { length: 6 }}
78
+ <div class="skeleton" data-testid="skeleton"></div>
79
+ {/each}
80
+ {:else if hasMorePages}
81
+ <button class="more" onclick={loadMore}>{t('Show More')}</button>
82
+ {/if}
83
+ </div>
84
+ </div>
85
+
86
+ <style lang="scss">
87
+ .header {
88
+ padding: margin(4) margin(1) margin(2);
89
+ font-family: theme(detail-font-family, font-family);
90
+ font-weight: theme(detail-font-weight, normal);
91
+ font-size: theme(detail-font-size, font-size-base);
92
+ line-height: theme(participant-description-line-height, normal);
93
+ color: theme(detail-text-color, text-color);
94
+
95
+ &.small {
96
+ padding: margin(1);
97
+ }
98
+ }
99
+
100
+ .heading {
101
+ margin: 0;
102
+ font-family: theme(detail-title-font-family, font-family);
103
+ font-weight: theme(detail-title-font-weight, lighter);
104
+ font-size: theme(detail-title-font-size, margin(1.5));
105
+ line-height: normal;
106
+ font-style: theme(detail-title-font-style, normal);
107
+
108
+ &.subheading {
109
+ margin: 0 0 margin(0.5);
110
+ font-size: theme(detail-title-small-font-size, margin(1.25));
111
+ }
112
+ }
113
+
114
+ .dates {
115
+ margin: margin(0.5) 0 0;
116
+ }
117
+
118
+ .content {
119
+ padding: 0 margin(1) margin(1);
120
+ color: theme(detail-text-color, text-color);
121
+ font-family: theme(detail-font-family, font-family);
122
+ font-weight: theme(detail-font-weight, normal);
123
+ font-size: theme(detail-font-size, font-size-base);
124
+ line-height: normal;
125
+ font-style: normal;
126
+
127
+ ::view-transition-old(content),
128
+ ::view-transition-new(content) {
129
+ height: 100%;
130
+ }
131
+ }
132
+
133
+ .list {
134
+ display: flex;
135
+ flex-direction: column;
136
+ gap: margin(0.5);
137
+ }
138
+
139
+ .skeleton {
140
+ min-height: margin(7.25);
141
+ border-radius: theme(playlink-border-radius, border-radius);
142
+ background: theme(detail-background-light, lighter);
143
+ }
144
+
145
+ .more {
146
+ cursor: pointer;
147
+ appearance: none;
148
+ padding: margin(0.5) margin(1);
149
+ border: 0;
150
+ border-radius: theme(participants-load-more-border-radius, border-radius);
151
+ background: theme(participants-load-more-background, content);
152
+ color: inherit;
153
+ font-size: inherit;
154
+ font-family: inherit;
155
+
156
+ &:hover {
157
+ filter: brightness(1.2);
158
+ }
159
+ }
160
+ </style>
@@ -40,7 +40,7 @@ describe('$lib/api/externalPages', () => {
40
40
 
41
41
  expect(response).toBe('Some response')
42
42
  expect(api).toHaveBeenCalledWith(
43
- '/external-pages/?api-token=a&include_title_details=true&language=en-US&url=https://some-url',
43
+ '/external-pages/?api-token=a&include_title_details=true&include_participant_details=true&language=en-US&url=https://some-url',
44
44
  expect.objectContaining({
45
45
  method: 'GET',
46
46
  }),
@@ -1,108 +1,108 @@
1
- import { cleanup } from '@testing-library/svelte'
2
- import { afterEach, beforeAll, beforeEach, vi } from 'vitest'
3
- import { fakeFetch } from './helpers'
4
-
5
- vi.mock('svelte/transition')
6
-
7
- // https://github.com/jsdom/jsdom/issues/3429#issuecomment-1936128876
8
- const mockAnimations = () => {
9
- Element.prototype.animate ??= vi.fn().mockReturnValue({
10
- onfinish: null,
11
- finished: Promise.resolve(),
12
- cancel: vi.fn(),
13
- startTime: null,
14
- currentTime: null,
15
- })
16
-
17
- Element.prototype.getAnimations ??= vi.fn().mockReturnValue([])
18
- }
19
-
20
- const mockLocalStorage = () => {
21
- /** @type {Record<string, string>} */
22
- let store = {}
23
-
24
- // @ts-ignore
25
- ;(window).localStorage = {
26
- getItem: function (key) {
27
- return store[key] || null
28
- },
29
- setItem: function (key, value) {
30
- store[key] = value.toString()
31
- },
32
- removeItem: function (key) {
33
- delete store[key]
34
- },
35
- clear: function () {
36
- store = {}
37
- },
38
- }
39
- }
40
-
41
- const mockSessionStorage = () => {
42
- /** @type {Record<string, string>} */
43
- let store = {}
44
-
45
- // @ts-ignore
46
- ;(window).sessionStorage = {
47
- getItem: function (key) {
48
- return store[key] || null
49
- },
50
- setItem: function (key, value) {
51
- store[key] = value.toString()
52
- },
53
- removeItem: function (key) {
54
- delete store[key]
55
- },
56
- clear: function () {
57
- store = {}
58
- },
59
- }
60
- }
61
-
62
- export const mockIntersectionObserver = () => {
63
- // @ts-ignore
64
- global.IntersectionObserver = vi.fn((callback) => ({
65
- disconnect: vi.fn(),
66
- observe: vi.fn(() => callback([{ isIntersecting: true }])),
67
- unobserve: vi.fn(),
68
- }))
69
- }
70
-
71
- beforeAll(() => {
72
- fakeFetch()
73
- mockLocalStorage()
74
- mockSessionStorage()
75
- mockIntersectionObserver()
76
- })
77
-
78
- beforeEach(() => {
79
- // @ts-ignore
80
- window.PlayPilotLinkInjections = { token: 'some-token', require_consent: false }
81
-
82
- // Enable all consent
83
- // @ts-ignore
84
- window.__tcfapi = (command, _version, callback) => {
85
- if (command !== 'addEventListener') return
86
-
87
- setTimeout(() => callback({
88
- purpose: { consents: { 1: true, 2: true, 7: true, 8: true, 9: true, 10: true } },
89
- eventStatus: 'tcloaded',
90
- }, true), 0)
91
-
92
- return 1
93
- }
94
-
95
- // Reset cookies
96
- document.cookie.split(';').forEach((cookie) => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, '=;'))
97
-
98
- window.HTMLElement.prototype.scrollIntoView = vi.fn()
99
-
100
- localStorage.clear()
101
- sessionStorage.clear()
102
-
103
- mockAnimations()
104
- })
105
-
106
- afterEach(() => {
107
- cleanup()
108
- })
1
+ import { cleanup } from '@testing-library/svelte'
2
+ import { afterEach, beforeAll, beforeEach, vi } from 'vitest'
3
+ import { fakeFetch } from './helpers'
4
+
5
+ vi.mock('svelte/transition')
6
+
7
+ // https://github.com/jsdom/jsdom/issues/3429#issuecomment-1936128876
8
+ const mockAnimations = () => {
9
+ Element.prototype.animate ??= vi.fn().mockReturnValue({
10
+ onfinish: null,
11
+ finished: Promise.resolve(),
12
+ cancel: vi.fn(),
13
+ startTime: null,
14
+ currentTime: null,
15
+ })
16
+
17
+ Element.prototype.getAnimations ??= vi.fn().mockReturnValue([])
18
+ }
19
+
20
+ const mockLocalStorage = () => {
21
+ /** @type {Record<string, string>} */
22
+ let store = {}
23
+
24
+ // @ts-ignore
25
+ ;(window).localStorage = {
26
+ getItem: function (key) {
27
+ return store[key] || null
28
+ },
29
+ setItem: function (key, value) {
30
+ store[key] = value.toString()
31
+ },
32
+ removeItem: function (key) {
33
+ delete store[key]
34
+ },
35
+ clear: function () {
36
+ store = {}
37
+ },
38
+ }
39
+ }
40
+
41
+ const mockSessionStorage = () => {
42
+ /** @type {Record<string, string>} */
43
+ let store = {}
44
+
45
+ // @ts-ignore
46
+ ;(window).sessionStorage = {
47
+ getItem: function (key) {
48
+ return store[key] || null
49
+ },
50
+ setItem: function (key, value) {
51
+ store[key] = value.toString()
52
+ },
53
+ removeItem: function (key) {
54
+ delete store[key]
55
+ },
56
+ clear: function () {
57
+ store = {}
58
+ },
59
+ }
60
+ }
61
+
62
+ export const mockIntersectionObserver = () => {
63
+ // @ts-ignore
64
+ global.IntersectionObserver = vi.fn((callback) => ({
65
+ disconnect: vi.fn(),
66
+ observe: vi.fn(() => callback([{ isIntersecting: true }])),
67
+ unobserve: vi.fn(),
68
+ }))
69
+ }
70
+
71
+ beforeAll(() => {
72
+ fakeFetch()
73
+ mockLocalStorage()
74
+ mockSessionStorage()
75
+ mockIntersectionObserver()
76
+ })
77
+
78
+ beforeEach(() => {
79
+ // @ts-ignore
80
+ window.PlayPilotLinkInjections = { token: 'some-token', require_consent: false }
81
+
82
+ // Enable all consent
83
+ // @ts-ignore
84
+ window.__tcfapi = (command, _version, callback) => {
85
+ if (command !== 'addEventListener') return
86
+
87
+ setTimeout(() => callback({
88
+ purpose: { consents: { 1: true, 2: true, 7: true, 8: true, 9: true, 10: true } },
89
+ eventStatus: 'tcloaded',
90
+ }, true), 0)
91
+
92
+ return 1
93
+ }
94
+
95
+ // Reset cookies
96
+ document.cookie.split(';').forEach((cookie) => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, '=;'))
97
+
98
+ window.HTMLElement.prototype.scrollIntoView = vi.fn()
99
+
100
+ localStorage.clear()
101
+ sessionStorage.clear()
102
+
103
+ mockAnimations()
104
+ })
105
+
106
+ afterEach(() => {
107
+ cleanup()
108
+ })