@playpilot/tpi 8.14.0-beta.4 → 8.15.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/dist/editorial.mount.js +9 -9
- package/dist/link-injections.js +1 -1
- package/dist/mount.js +9 -9
- package/events.md +5 -0
- package/package.json +2 -2
- package/src/lib/api/externalPages.ts +0 -7
- package/src/lib/data/translations.ts +10 -0
- package/src/lib/enums/TrackingEvent.ts +3 -0
- package/src/lib/fakeData.ts +3 -3
- package/src/lib/inTextWidgets.ts +43 -0
- package/src/lib/injection.ts +4 -0
- package/src/lib/routes.ts +1 -5
- package/src/lib/types/explore.d.ts +1 -0
- package/src/routes/+layout.svelte +2 -0
- package/src/routes/components/Debugger.svelte +0 -5
- package/src/routes/components/Explore/ExploreLayout.svelte +19 -2
- package/src/routes/components/Explore/ExploreRouter.svelte +11 -28
- package/src/routes/components/Explore/Routes/ExploreModal.svelte +38 -0
- package/src/routes/components/Modals/Modal.svelte +3 -1
- package/src/routes/components/Modals/RailModal.svelte +2 -2
- package/src/routes/components/Modals/TitlesRailModal.svelte +5 -4
- package/src/routes/components/Rails/Rail.svelte +4 -4
- package/src/routes/components/Widgets/InjectionsWidgetRail.svelte +51 -0
- package/src/routes/explore/+page.svelte +4 -0
- package/src/tests/lib/inTextWidgets.test.js +160 -0
- package/src/tests/routes/components/Widgets/InjectionsWidgetRail.test.js +28 -0
package/events.md
CHANGED
|
@@ -100,6 +100,11 @@ Event | Action | Info | Payload
|
|
|
100
100
|
`ali_display_ad_playlink_click` | _Fires any time the playlink linked to the display ad is clicked_ | | `campaign_name`
|
|
101
101
|
`ali_ads_fetch_failed` | _Fires whenever ads tried to but failed to fetch_ | | `status` (Response status code)
|
|
102
102
|
|
|
103
|
+
## Widgets
|
|
104
|
+
Event | Action | Info | Payload
|
|
105
|
+
--- | --- | --- | ---
|
|
106
|
+
`ali_widget_article_rail_title_click` | _Fires when a title in an tpi rail widget is clicked_ | | `Title`
|
|
107
|
+
|
|
103
108
|
### Various
|
|
104
109
|
Event | Action | Info | Payload
|
|
105
110
|
--- | --- | --- | ---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playpilot/tpi",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"svelte": "5.44.1",
|
|
37
37
|
"svelte-check": "^4.0.0",
|
|
38
38
|
"svelte-preprocess": "^6.0.3",
|
|
39
|
-
"svelte-tiny-slider": "^2.7.
|
|
39
|
+
"svelte-tiny-slider": "^2.7.2",
|
|
40
40
|
"typescript": "^5.9.3",
|
|
41
41
|
"typescript-eslint": "^8.59.2",
|
|
42
42
|
"vite": "^5.4.21",
|
|
@@ -26,8 +26,6 @@ export async function fetchLinkInjections(
|
|
|
26
26
|
const apiUrl = `/external-pages/?api-token=${apiToken}&include_title_details=true${isEditorialMode ? '&editorial_mode_enabled=true' : ''}&language=${language}`
|
|
27
27
|
let response: LinkInjectionResponse
|
|
28
28
|
|
|
29
|
-
// We use separate requests when running the AI or setting the editor session vs when only getting the results.
|
|
30
|
-
// For regular requests we use a GET endpoint, but when saving data we POST to the same url.
|
|
31
29
|
if (method === 'POST') {
|
|
32
30
|
const { pageText } = getPageTextAndElements()
|
|
33
31
|
|
|
@@ -44,14 +42,11 @@ export async function fetchLinkInjections(
|
|
|
44
42
|
body: params,
|
|
45
43
|
})
|
|
46
44
|
} else {
|
|
47
|
-
// When getting injections without posting we append the URL of the page to the URL for the request.
|
|
48
|
-
// All other params are only relevant during the POST request.
|
|
49
45
|
response = await api<LinkInjectionResponse>(apiUrl + `&url=${url}`, {
|
|
50
46
|
method: 'GET',
|
|
51
47
|
})
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
// This is used when debugging (using window.PlayPilotLinkInjections.debug())
|
|
55
50
|
window.PlayPilotLinkInjections.last_successful_fetch = response
|
|
56
51
|
|
|
57
52
|
return response
|
|
@@ -67,8 +62,6 @@ export async function pollLinkInjections(
|
|
|
67
62
|
): Promise<LinkInjectionResponse> {
|
|
68
63
|
let currentTry = 0
|
|
69
64
|
|
|
70
|
-
// Clear pollTimeout if it is already running to prevent multiple timeouts from running at the same time
|
|
71
|
-
// This is mostly handy during HMR, but also during navigation changes
|
|
72
65
|
if (pollTimeout) clearTimeout(pollTimeout)
|
|
73
66
|
|
|
74
67
|
const poll = async (resolve: Function, reject: Function): Promise<void> => {
|
|
@@ -171,6 +171,11 @@ export const translations = {
|
|
|
171
171
|
[Language.Swedish]: 'Upptäck och sök bland alla filmer och tv-serier',
|
|
172
172
|
[Language.Danish]: 'Opdag og søg i alle film og tv-serier',
|
|
173
173
|
},
|
|
174
|
+
'Streaming Guide Disclaimer': {
|
|
175
|
+
[Language.English]: 'In collaboration with <a href="https://www.playpilot.com/" target="_blank" rel="sponsored">PlayPilot.com</a>',
|
|
176
|
+
[Language.Swedish]: 'I samarbete med <a href="https://www.playpilot.com/" target="_blank" rel="sponsored">PlayPilot.com</a>',
|
|
177
|
+
[Language.Danish]: 'I samarbejde med <a href="https://www.playpilot.com/" target="_blank" rel="sponsored">PlayPilot.com</a>',
|
|
178
|
+
},
|
|
174
179
|
'Streaming Guide Description': {
|
|
175
180
|
[Language.English]: 'Find where to watch movies online - the ultimate guide that helps you find the best movies and shows across streaming services.',
|
|
176
181
|
[Language.Swedish]: 'Sök bland alla filmer och serier för att ta reda på var du kan streama dem',
|
|
@@ -286,6 +291,11 @@ export const translations = {
|
|
|
286
291
|
[Language.Swedish]: 'Sidan hittades inte',
|
|
287
292
|
[Language.Danish]: 'Siden blev ikke fundet',
|
|
288
293
|
},
|
|
294
|
+
'Mentioned In This Article': {
|
|
295
|
+
[Language.English]: 'Mentioned in this article',
|
|
296
|
+
[Language.Swedish]: 'Nämnda i den här artikeln',
|
|
297
|
+
[Language.Danish]: 'Nævnt i denne artikel',
|
|
298
|
+
},
|
|
289
299
|
|
|
290
300
|
// List titles
|
|
291
301
|
'List: Trending': {
|
|
@@ -55,6 +55,9 @@ export const TrackingEvent = {
|
|
|
55
55
|
SplitTestView: 'ali_split_test_view',
|
|
56
56
|
SplitTestAction: 'ali_split_test_action',
|
|
57
57
|
|
|
58
|
+
// Widgets
|
|
59
|
+
WidgetArticleRailTitleClick: 'ali_widget_article_rail_title_click',
|
|
60
|
+
|
|
58
61
|
// Various
|
|
59
62
|
ShareTitle: 'ali_share_title',
|
|
60
63
|
SaveTitle: 'ali_save_title',
|
package/src/lib/fakeData.ts
CHANGED
|
@@ -49,7 +49,7 @@ export const linkInjections: LinkInjection[] = [{
|
|
|
49
49
|
sentence: 'In an interview with Epire Magazine, Quan reveals he quested starring in Love Hurts',
|
|
50
50
|
playpilot_url: 'https://playpilot.com/movie/example/',
|
|
51
51
|
key: 'some-key-1',
|
|
52
|
-
title_details: title,
|
|
52
|
+
title_details: { ...title, sid: '1' },
|
|
53
53
|
}, {
|
|
54
54
|
sid: '2',
|
|
55
55
|
title: 'The Long Kiss Goodnight',
|
|
@@ -57,7 +57,7 @@ export const linkInjections: LinkInjection[] = [{
|
|
|
57
57
|
playpilot_url: 'https://playpilot.com/movie/example-2/',
|
|
58
58
|
key: 'some-key-2',
|
|
59
59
|
after_article: false,
|
|
60
|
-
title_details: title,
|
|
60
|
+
title_details: { ...title, sid: '2' },
|
|
61
61
|
}, {
|
|
62
62
|
sid: '3',
|
|
63
63
|
title: 'Nobody',
|
|
@@ -65,7 +65,7 @@ export const linkInjections: LinkInjection[] = [{
|
|
|
65
65
|
playpilot_url: 'https://playpilot.com/movie/example-3/',
|
|
66
66
|
key: 'some-key-3',
|
|
67
67
|
after_article: true,
|
|
68
|
-
title_details: title,
|
|
68
|
+
title_details: { ...title, sid: '3' },
|
|
69
69
|
manual: false,
|
|
70
70
|
}, {
|
|
71
71
|
sid: '4',
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { mount, unmount } from 'svelte'
|
|
2
|
+
import type { LinkInjection } from './types/injection'
|
|
3
|
+
import InjectionsWidgetRail from '../routes/components/Widgets/InjectionsWidgetRail.svelte'
|
|
4
|
+
|
|
5
|
+
const widgets: Record<string, any> = {
|
|
6
|
+
'tpi-rail': InjectionsWidgetRail,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const inTextWidgetSelector = '[data-playpilot-widget]'
|
|
10
|
+
|
|
11
|
+
export let inTextWidgetInsertedComponents: any[] = []
|
|
12
|
+
|
|
13
|
+
export function insertInTextWidgets(linkInjections: LinkInjection[]): void {
|
|
14
|
+
clearInTextWidgets()
|
|
15
|
+
|
|
16
|
+
if (!linkInjections.length) return
|
|
17
|
+
|
|
18
|
+
const targets = document.querySelectorAll<HTMLElement>(inTextWidgetSelector)
|
|
19
|
+
|
|
20
|
+
targets.forEach(target => {
|
|
21
|
+
const widget = target.dataset.playpilotWidget || ''
|
|
22
|
+
|
|
23
|
+
const component = widgets[widget]
|
|
24
|
+
|
|
25
|
+
if (!component) return
|
|
26
|
+
|
|
27
|
+
const insertedComponent = mount(component, {
|
|
28
|
+
target,
|
|
29
|
+
props: {
|
|
30
|
+
linkInjections,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
inTextWidgetInsertedComponents.push(insertedComponent)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function clearInTextWidgets(): void {
|
|
39
|
+
inTextWidgetInsertedComponents.forEach(component => unmount(component))
|
|
40
|
+
document.querySelectorAll('[data-playpilot-widget]').forEach(element => element.innerHTML = '')
|
|
41
|
+
|
|
42
|
+
inTextWidgetInsertedComponents = []
|
|
43
|
+
}
|
package/src/lib/injection.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { clearCurrentlyHoveredInjection, destroyLinkPopover, destroyLinkPopoverO
|
|
|
6
6
|
import { clearAfterArticlePlaylinks, insertAfterArticlePlaylinks } from './afterArticle'
|
|
7
7
|
import { clearInTextDisclaimer, insertInTextDisclaimer } from './disclaimer'
|
|
8
8
|
import { exploreTitleUrl, titleUrl } from './routes'
|
|
9
|
+
import { clearInTextWidgets, insertInTextWidgets } from './inTextWidgets'
|
|
9
10
|
|
|
10
11
|
export const keyDataAttribute = 'data-playpilot-injection-key'
|
|
11
12
|
export const keySelector = `[${keyDataAttribute}]`
|
|
@@ -169,6 +170,8 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
|
|
|
169
170
|
// The function itself will decide whether or not it should actually insert the component based on the config.
|
|
170
171
|
if (document.querySelector(keySelector)) insertInTextDisclaimer(elements)
|
|
171
172
|
|
|
173
|
+
insertInTextWidgets(foundInjections)
|
|
174
|
+
|
|
172
175
|
return mergedInjections.filter(i => i.title_details).map((injection, index) => {
|
|
173
176
|
const hasManualEquivalent = !injection.manual && isAvailableAsManualInjection(injection, index, mergedInjections)
|
|
174
177
|
const duplicate = injection.duplicate ?? hasManualEquivalent
|
|
@@ -343,6 +346,7 @@ export function clearLinkInjections(): void {
|
|
|
343
346
|
|
|
344
347
|
clearAfterArticlePlaylinks()
|
|
345
348
|
clearInTextDisclaimer()
|
|
349
|
+
clearInTextWidgets()
|
|
346
350
|
destroyAllModals(false)
|
|
347
351
|
destroyLinkPopover(false)
|
|
348
352
|
}
|
package/src/lib/routes.ts
CHANGED
|
@@ -6,9 +6,5 @@ export function titleUrl(title: TitleData): string {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function exploreTitleUrl(title: TitleData): string {
|
|
9
|
-
|
|
10
|
-
return window.PlayPilotLinkInjections?.config?.explore_navigation_path + `?route=modal&sid=${title.sid}`
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return window.PlayPilotLinkInjections?.config?.explore_navigation_path + `?route=title&sid=${title.sid}`
|
|
9
|
+
return window.PlayPilotLinkInjections?.config?.explore_navigation_path + `?route=modal&sid=${title.sid}`
|
|
14
10
|
}
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
<p>De tre ’Jurassic World’-film kunne have givet indtryk af, at der ikke skal meget til, før dinosaurer igen ville kunne dominere kloden. Men det har vist sig ikke at holde stik.</p>
|
|
71
71
|
<p>Het komt elk jaar voor dat films met torenhoge budgetten flink floppen aan de box-office, ongeacht de kwaliteit van de film. Dit is het geval bij een aantal films die dit jaar zijn uitgekomen. Denk aan Mickey 17, Black Bag en de onlangs uitgebrachte animatiefilm Elio. In 2015 was de superheldenfilm Fantastic Four een van de grootste flops.</p>
|
|
72
72
|
|
|
73
|
+
<div data-playpilot-widget="tpi-rail"></div>
|
|
74
|
+
|
|
73
75
|
<h2>A matching link is already present</h2>
|
|
74
76
|
<p>Following their post-credits scene in <a href="/">John Wick</a>, in a new John Wick spinoff.</p>
|
|
75
77
|
|
|
@@ -226,11 +226,6 @@
|
|
|
226
226
|
|
|
227
227
|
<hr />
|
|
228
228
|
|
|
229
|
-
<button onclick={() => { localStorage.setItem('tpi-open-explore-as-modal', 'true'); onrerender() }}>Open links as modal in explore</button>
|
|
230
|
-
<button onclick={() => { localStorage.setItem('tpi-open-explore-as-modal', 'false'); onrerender() }}>Open links as separate page in explore</button>
|
|
231
|
-
|
|
232
|
-
<hr />
|
|
233
|
-
|
|
234
229
|
<button onclick={() => shown = false}>Close</button>
|
|
235
230
|
</div>
|
|
236
231
|
{/if}
|
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
{t('Streaming Guide Subheading')}
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
|
+
<p class="disclaimer">
|
|
56
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
57
|
+
{@html t('Streaming Guide Disclaimer')}
|
|
58
|
+
</p>
|
|
59
|
+
|
|
55
60
|
{#if !useExploreRouter()}
|
|
56
61
|
<p class="description">
|
|
57
62
|
{t('Streaming Guide Description')}
|
|
@@ -117,7 +122,7 @@
|
|
|
117
122
|
.heading,
|
|
118
123
|
.subheading {
|
|
119
124
|
color: theme(text-color);
|
|
120
|
-
font-size: theme(explore-heading-size, clamp(margin(1.5), 5vw, margin(2)));
|
|
125
|
+
font-size: theme(explore-heading-font-size, clamp(margin(1.5), 5vw, margin(2)));
|
|
121
126
|
font-weight: theme(explore-heading-font-weight, font-bold);
|
|
122
127
|
text-transform: theme(explore-heading-text-transform, normal);
|
|
123
128
|
line-height: theme(explore-heading-line-height, 1.5);
|
|
@@ -126,13 +131,25 @@
|
|
|
126
131
|
.subheading {
|
|
127
132
|
margin-top: margin(0.5);
|
|
128
133
|
max-width: margin(15);
|
|
129
|
-
font-size: theme(explore-subheading-size, clamp(margin(1), 2.5vw, margin(1.25)));
|
|
134
|
+
font-size: theme(explore-subheading-font-size, clamp(margin(1), 2.5vw, margin(1.25)));
|
|
130
135
|
|
|
131
136
|
@include desktop {
|
|
132
137
|
max-width: 100%;
|
|
133
138
|
}
|
|
134
139
|
}
|
|
135
140
|
|
|
141
|
+
.disclaimer {
|
|
142
|
+
margin: theme(explore-disclaimer-margin, 0);
|
|
143
|
+
font-size: theme(explore-diclaimer-font-size, font-size-small);
|
|
144
|
+
color: theme(explore-disclaimer-color, text-color-alt);
|
|
145
|
+
opacity: 0.75;
|
|
146
|
+
|
|
147
|
+
:global(a) {
|
|
148
|
+
color: inherit;
|
|
149
|
+
font-style: inherit;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
136
153
|
.description {
|
|
137
154
|
max-width: theme(explore-header-max-width, 600px);
|
|
138
155
|
margin: 0;
|
|
@@ -8,9 +8,7 @@
|
|
|
8
8
|
import ExploreResults from './Routes/ExploreResults.svelte'
|
|
9
9
|
import ExploreLayout from './ExploreLayout.svelte'
|
|
10
10
|
import ExploreTitle from './Routes/ExploreTitle.svelte'
|
|
11
|
-
import
|
|
12
|
-
import { openModal } from '$lib/modal'
|
|
13
|
-
import type { TitleData } from '$lib/types/title'
|
|
11
|
+
import ExploreModal from './Routes/ExploreModal.svelte'
|
|
14
12
|
|
|
15
13
|
const routes: ExploreRoute[] = [
|
|
16
14
|
{
|
|
@@ -29,6 +27,12 @@
|
|
|
29
27
|
key: 'title',
|
|
30
28
|
component: ExploreTitle,
|
|
31
29
|
})
|
|
30
|
+
|
|
31
|
+
routes.push({
|
|
32
|
+
key: 'modal',
|
|
33
|
+
component: ExploreHome,
|
|
34
|
+
appendComponent: ExploreModal,
|
|
35
|
+
})
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
const initialRouteKey = getCurrentRouteParam() || routes[0].key
|
|
@@ -38,8 +42,7 @@
|
|
|
38
42
|
let filter: ExploreFilter = $state({})
|
|
39
43
|
|
|
40
44
|
const CurrentRouteComponent = $derived(currentRoute.component)
|
|
41
|
-
|
|
42
|
-
if (initialRouteKey === 'modal') openModalViaRoute()
|
|
45
|
+
const CurrentAppendComponent = $derived(currentRoute.appendComponent)
|
|
43
46
|
|
|
44
47
|
$effect(() => {
|
|
45
48
|
if (searchQuery) currentRoute = routes.find(route => route.key === 'results')!
|
|
@@ -60,33 +63,12 @@
|
|
|
60
63
|
track(TrackingEvent.ExploreNavigate, null, { route: currentRoute.key })
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
function onhashchange(): void {
|
|
64
|
-
navigate(getCurrentRouteParam(), false)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
66
|
function getCurrentRouteParam(): string {
|
|
68
67
|
return new URL(document.location.toString()).searchParams.get('route') || routes[0].key
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const currentUrl = new URL(document.location.toString())
|
|
74
|
-
const sid = currentUrl.searchParams.get('sid')
|
|
75
|
-
|
|
76
|
-
if (!sid) return
|
|
77
|
-
|
|
78
|
-
const [title, railTitles] = (await Promise.allSettled([
|
|
79
|
-
fetchTitleBySid(sid),
|
|
80
|
-
fetchSimilarTitles({ sid } as unknown as TitleData)])
|
|
81
|
-
).map(promise => (promise.status === 'fulfilled' ? promise.value : null))
|
|
82
|
-
|
|
83
|
-
openModal({
|
|
84
|
-
type: 'titles-rail',
|
|
85
|
-
data: [(title as TitleData), ...(railTitles as TitleData[])],
|
|
86
|
-
props: {
|
|
87
|
-
onclose: () => navigate('home'),
|
|
88
|
-
},
|
|
89
|
-
})
|
|
70
|
+
function onhashchange(): void {
|
|
71
|
+
navigate(getCurrentRouteParam(), false)
|
|
90
72
|
}
|
|
91
73
|
</script>
|
|
92
74
|
|
|
@@ -94,4 +76,5 @@
|
|
|
94
76
|
|
|
95
77
|
<ExploreLayout {navigate} bind:searchQuery bind:filter>
|
|
96
78
|
<CurrentRouteComponent {searchQuery} {filter} {navigate} />
|
|
79
|
+
<CurrentAppendComponent {navigate} />
|
|
97
80
|
</ExploreLayout>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fetchSimilarTitles, fetchTitleBySid } from '$lib/api/titles'
|
|
3
|
+
import { openModal } from '$lib/modal'
|
|
4
|
+
import type { TitleData } from '$lib/types/title'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
navigate?: (key: string, pushState?: boolean) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { navigate = () => null }: Props = $props()
|
|
11
|
+
|
|
12
|
+
openModalViaRoute()
|
|
13
|
+
|
|
14
|
+
async function openModalViaRoute(): Promise<void> {
|
|
15
|
+
const currentUrl = new URL(document.location.toString())
|
|
16
|
+
const sid = currentUrl.searchParams.get('sid')
|
|
17
|
+
|
|
18
|
+
if (!sid) return
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const [title, railTitles] = (await Promise.allSettled([
|
|
22
|
+
fetchTitleBySid(sid),
|
|
23
|
+
fetchSimilarTitles({ sid } as unknown as TitleData)])
|
|
24
|
+
).map(promise => (promise.status === 'fulfilled' ? promise.value : null))
|
|
25
|
+
|
|
26
|
+
openModal({
|
|
27
|
+
type: 'titles-rail',
|
|
28
|
+
data: [(title as TitleData), ...(railTitles as TitleData[])],
|
|
29
|
+
props: {
|
|
30
|
+
onclose: () => navigate('home'),
|
|
31
|
+
pushState: false,
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
} catch {
|
|
35
|
+
navigate('home')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
blur?: boolean
|
|
22
22
|
closeButtonStyle?: 'shadow' | 'flat'
|
|
23
23
|
initialScrollPosition?: number
|
|
24
|
+
pushState?: boolean
|
|
24
25
|
onscroll?: () => void
|
|
25
26
|
onclose?: () => void
|
|
26
27
|
}
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
blur = false,
|
|
36
37
|
closeButtonStyle = 'shadow',
|
|
37
38
|
initialScrollPosition = 0,
|
|
39
|
+
pushState = true,
|
|
38
40
|
onscroll = () => null,
|
|
39
41
|
onclose = () => null,
|
|
40
42
|
}: Props = $props()
|
|
@@ -63,7 +65,7 @@
|
|
|
63
65
|
|
|
64
66
|
// Add modal state to the browser history. This allows us to close to modal when using the back button.
|
|
65
67
|
// Only do this for the very first modal opened in the stack. The back button always fully closes all modals.
|
|
66
|
-
if (!hasPreviousModal) window.history.pushState({ modal: true }, '', historyHash)
|
|
68
|
+
if (pushState && !hasPreviousModal) window.history.pushState({ modal: true }, '', historyHash)
|
|
67
69
|
|
|
68
70
|
requestAnimationFrame(setInitialScrollPosition)
|
|
69
71
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
each: Snippet<[item: any, currentIndex: number]>
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { items, initialIndex = 0, onchange = () => null, onclose = () => null, each }: Props = $props()
|
|
19
|
+
const { items, initialIndex = 0, onchange = () => null, onclose = () => null, each, ...restProps }: Props = $props()
|
|
20
20
|
|
|
21
21
|
const transitionDuration = 300
|
|
22
22
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
</script>
|
|
38
38
|
|
|
39
|
-
<Modal blur {onclose}>
|
|
39
|
+
<Modal blur {onclose} {...restProps}>
|
|
40
40
|
{#snippet dialog()}
|
|
41
41
|
<div class="rail-modal" style:--transition-duration="{transitionDuration}ms">
|
|
42
42
|
<TinySlider threshold={40} moveThreshold={40} transitionDuration={initialized ? transitionDuration : 0} bind:this={slider}>
|
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
import RailModal from './RailModal.svelte'
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
titles: TitleData[]
|
|
10
|
-
initialIndex?: number
|
|
9
|
+
titles: TitleData[],
|
|
10
|
+
initialIndex?: number,
|
|
11
|
+
onclose?: () => void
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const { titles, initialIndex = 0 }: Props = $props()
|
|
14
|
+
const { titles, initialIndex = 0, onclose = () => null, ...restProps }: Props = $props()
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
|
-
<RailModal items={titles} {initialIndex} onchange={(index) => track(TrackingEvent.ExploreTitleRailSetIndex, titles[index], { index })}>
|
|
17
|
+
<RailModal items={titles} {initialIndex} {onclose} onchange={(index) => track(TrackingEvent.ExploreTitleRailSetIndex, titles[index], { index })} {...restProps}>
|
|
17
18
|
{#snippet each(title, currentIndex)}
|
|
18
19
|
<Title {title} useVideoBackground={title.sid === titles[currentIndex]?.sid} />
|
|
19
20
|
{/snippet}
|
|
@@ -58,15 +58,15 @@
|
|
|
58
58
|
.rail {
|
|
59
59
|
--gap: var(--rail-gap, #{margin(0.5)});
|
|
60
60
|
position: relative;
|
|
61
|
-
width: calc(100% + margin(2)
|
|
62
|
-
margin: 0 margin(-1);
|
|
61
|
+
width: calc(100% + var(--rail-margin, margin(1)) * 2);
|
|
62
|
+
margin: 0 calc(var(--rail-margin, margin(1)) * -1);
|
|
63
63
|
|
|
64
64
|
:global(.slider) {
|
|
65
|
-
padding: 0 margin(1);
|
|
65
|
+
padding: 0 var(--rail-margin, margin(1));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
:global(.slider-content > :last-child) {
|
|
69
|
-
margin-right: margin(2);
|
|
69
|
+
margin-right: calc(var(--rail-margin, margin(1)) * 2);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
3
|
+
import { t } from '$lib/localization'
|
|
4
|
+
import { track } from '$lib/tracking'
|
|
5
|
+
import type { LinkInjection } from '$lib/types/injection'
|
|
6
|
+
import type { TitleData } from '$lib/types/title'
|
|
7
|
+
import TitlesRail from '../Rails/TitlesRail.svelte'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
linkInjections: LinkInjection[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { linkInjections }: Props = $props()
|
|
14
|
+
|
|
15
|
+
let element: HTMLElement | null = $state(null)
|
|
16
|
+
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
const heading = $derived(element?.parentNode?.dataset.heading || t('Mentioned In This Article'))
|
|
19
|
+
|
|
20
|
+
const titles: TitleData[] = $derived.by(() => {
|
|
21
|
+
const uniqueTitles: TitleData[] = []
|
|
22
|
+
|
|
23
|
+
linkInjections.forEach(injection => {
|
|
24
|
+
const title = injection.title_details!
|
|
25
|
+
|
|
26
|
+
if (!title) return
|
|
27
|
+
if (uniqueTitles.some(t => t.sid === title.sid)) return
|
|
28
|
+
|
|
29
|
+
uniqueTitles.push(title)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return uniqueTitles
|
|
33
|
+
})
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
{#if titles.length}
|
|
37
|
+
<div class="widget" bind:this={element} data-testid="widget">
|
|
38
|
+
<TitlesRail {titles} {heading} onclick={(title) => track(TrackingEvent.WidgetArticleRailTitleClick, title)} />
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
41
|
+
|
|
42
|
+
<style lang="scss">
|
|
43
|
+
.widget {
|
|
44
|
+
--rail-margin: 0;
|
|
45
|
+
--playpilot-rails-arrow-background: black;
|
|
46
|
+
--playpilot-detail-background-light: black;
|
|
47
|
+
--playpilot-rail-title-text-color: currentColor;
|
|
48
|
+
--playpilot-rail-text-color: currentColor;
|
|
49
|
+
margin: margin(1) 0;
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
setTimeout(insertExplore, 100)
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
|
+
<svelte:head>
|
|
33
|
+
<title>PlayPilot Link Injections - Explore</title>
|
|
34
|
+
</svelte:head>
|
|
35
|
+
|
|
32
36
|
<button onclick={(() => {
|
|
33
37
|
destroyExplore()
|
|
34
38
|
window.PlayPilotLinkInjections.config.explore_use_router = !useExploreRouter()
|