@playpilot/tpi 8.14.0-beta.1 → 8.14.0-beta.3
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/package.json +1 -1
- package/src/lib/api/titles.ts +1 -1
- package/src/lib/injection.ts +13 -3
- package/src/lib/routes.ts +8 -0
- package/src/lib/types/config.d.ts +5 -0
- package/src/routes/components/Debugger.svelte +5 -0
- package/src/routes/components/Explore/ExploreRouter.svelte +34 -3
- package/src/routes/components/Modals/RailModal.svelte +6 -3
- package/src/tests/lib/injection.test.js +44 -3
- package/src/tests/lib/routes.test.js +14 -2
package/package.json
CHANGED
package/src/lib/api/titles.ts
CHANGED
|
@@ -33,7 +33,7 @@ export async function fetchTitleBySid(sid: string): Promise<TitleData> {
|
|
|
33
33
|
|
|
34
34
|
const title = data.results[0]
|
|
35
35
|
|
|
36
|
-
if (!title) throw new Error('No title was returned')
|
|
36
|
+
if (!title) throw new Error('No title was returned for sid: ' + sid)
|
|
37
37
|
|
|
38
38
|
return title
|
|
39
39
|
}
|
package/src/lib/injection.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { destroyAllModals, openModalForInjectedLink } from './modal'
|
|
|
5
5
|
import { clearCurrentlyHoveredInjection, destroyLinkPopover, destroyLinkPopoverOnMouseleave, isPopoverActive, openPopoverForInjectedLink } from './popover'
|
|
6
6
|
import { clearAfterArticlePlaylinks, insertAfterArticlePlaylinks } from './afterArticle'
|
|
7
7
|
import { clearInTextDisclaimer, insertInTextDisclaimer } from './disclaimer'
|
|
8
|
+
import { exploreTitleUrl, titleUrl } from './routes'
|
|
8
9
|
|
|
9
10
|
export const keyDataAttribute = 'data-playpilot-injection-key'
|
|
10
11
|
export const keySelector = `[${keyDataAttribute}]`
|
|
@@ -199,11 +200,15 @@ function createLinkInjectionElement(injection: LinkInjection): { injectionElemen
|
|
|
199
200
|
const injectionElement = document.createElement('span')
|
|
200
201
|
injectionElement.dataset.playpilotInjectionKey = injection.key
|
|
201
202
|
|
|
203
|
+
const openInExplore = !!window.PlayPilotLinkInjections?.config?.open_tpi_links_in_explore
|
|
204
|
+
|
|
205
|
+
const href = openInExplore ? exploreTitleUrl(injection.title_details!) : titleUrl(injection.title_details!)
|
|
206
|
+
|
|
202
207
|
const linkElement = document.createElement('a')
|
|
203
208
|
linkElement.dataset.playpilotPosterUrl = injection.title_details?.standing_poster
|
|
204
209
|
linkElement.innerText = injection.title
|
|
205
|
-
linkElement.href =
|
|
206
|
-
linkElement.target = '_blank'
|
|
210
|
+
linkElement.href = href
|
|
211
|
+
linkElement.target = openInExplore ? '' : '_blank'
|
|
207
212
|
linkElement.rel = 'noopener nofollow noreferrer'
|
|
208
213
|
|
|
209
214
|
injectionElement.insertAdjacentElement('beforeend', linkElement)
|
|
@@ -308,7 +313,12 @@ function addCSSVariablesToLinks(): void {
|
|
|
308
313
|
|
|
309
314
|
function addLinkInjectionEventListeners(injections: LinkInjection[]): void {
|
|
310
315
|
window.addEventListener('mousemove', destroyLinkPopoverOnMouseleave)
|
|
311
|
-
|
|
316
|
+
|
|
317
|
+
window.addEventListener('click', (event) => {
|
|
318
|
+
if (window.PlayPilotLinkInjections?.config?.open_tpi_links_in_explore) return
|
|
319
|
+
|
|
320
|
+
openModalForInjectedLink(event, injections)
|
|
321
|
+
})
|
|
312
322
|
|
|
313
323
|
const createdInjectionElements = document.querySelectorAll<HTMLElement>(keySelector)
|
|
314
324
|
|
package/src/lib/routes.ts
CHANGED
|
@@ -4,3 +4,11 @@ import type { TitleData } from './types/title'
|
|
|
4
4
|
export function titleUrl(title: TitleData): string {
|
|
5
5
|
return `${playPilotBaseUrl}/${title.type}/${title.slug}/`
|
|
6
6
|
}
|
|
7
|
+
|
|
8
|
+
export function exploreTitleUrl(title: TitleData): string {
|
|
9
|
+
if (localStorage.getItem('tpi-open-explore-as-modal') === 'true') {
|
|
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}`
|
|
14
|
+
}
|
|
@@ -97,6 +97,11 @@ export type ConfigResponse = {
|
|
|
97
97
|
in_text_disclaimer_selector?: string
|
|
98
98
|
in_text_disclaimer_insert_position?: InsertPosition
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Open TPI links in explore rather than in modals in the article
|
|
102
|
+
*/
|
|
103
|
+
open_tpi_links_in_explore?: boolean
|
|
104
|
+
|
|
100
105
|
/**
|
|
101
106
|
* These options are all relevant for the Explore component, which can be inserted as a widget on any page or as a modal.
|
|
102
107
|
* `explore_navigation_selector` is used to select the navigation element that should be copied and inserted _after_.
|
|
@@ -226,6 +226,11 @@
|
|
|
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
|
+
|
|
229
234
|
<button onclick={() => shown = false}>Close</button>
|
|
230
235
|
</div>
|
|
231
236
|
{/if}
|
|
@@ -8,6 +8,9 @@
|
|
|
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 { fetchSimilarTitles, fetchTitleBySid } from '$lib/api/titles'
|
|
12
|
+
import { openModal } from '$lib/modal'
|
|
13
|
+
import type { TitleData } from '$lib/types/title'
|
|
11
14
|
|
|
12
15
|
const routes: ExploreRoute[] = [
|
|
13
16
|
{
|
|
@@ -36,30 +39,58 @@
|
|
|
36
39
|
|
|
37
40
|
const CurrentRouteComponent = $derived(currentRoute.component)
|
|
38
41
|
|
|
42
|
+
if (initialRouteKey === 'modal') openModalViaRoute()
|
|
43
|
+
|
|
39
44
|
$effect(() => {
|
|
40
45
|
if (searchQuery) currentRoute = routes.find(route => route.key === 'results')!
|
|
41
46
|
})
|
|
42
47
|
|
|
43
|
-
function navigate(key: string): void {
|
|
48
|
+
function navigate(key: string, pushState: boolean = true): void {
|
|
44
49
|
currentRoute = routes.find(route => route.key === key) || routes[0]
|
|
45
50
|
|
|
46
51
|
const currentUrl = new URL(document.location.toString())
|
|
47
52
|
|
|
53
|
+
if (key !== 'title' && key !== 'modal') currentUrl.searchParams.delete('sid')
|
|
54
|
+
|
|
48
55
|
if (key === routes[0].key) currentUrl.searchParams.delete('route')
|
|
49
56
|
else currentUrl.searchParams.set('route', currentRoute.key)
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
if (pushState) {
|
|
59
|
+
history.pushState({}, '', currentUrl)
|
|
60
|
+
console.log('navigate push state')
|
|
61
|
+
}
|
|
52
62
|
|
|
53
63
|
track(TrackingEvent.ExploreNavigate, null, { route: currentRoute.key })
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
function onhashchange(): void {
|
|
57
|
-
navigate(getCurrentRouteParam())
|
|
67
|
+
navigate(getCurrentRouteParam(), false)
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
function getCurrentRouteParam(): string {
|
|
61
71
|
return new URL(document.location.toString()).searchParams.get('route') || routes[0].key
|
|
62
72
|
}
|
|
73
|
+
|
|
74
|
+
// This is temporary while testing, please clean me up later
|
|
75
|
+
async function openModalViaRoute(): Promise<void> {
|
|
76
|
+
const currentUrl = new URL(document.location.toString())
|
|
77
|
+
const sid = currentUrl.searchParams.get('sid')
|
|
78
|
+
|
|
79
|
+
if (!sid) return
|
|
80
|
+
|
|
81
|
+
const [title, railTitles] = (await Promise.allSettled([
|
|
82
|
+
fetchTitleBySid(sid),
|
|
83
|
+
fetchSimilarTitles({ sid } as unknown as TitleData)])
|
|
84
|
+
).map(promise => (promise.status === 'fulfilled' ? promise.value : null))
|
|
85
|
+
|
|
86
|
+
openModal({
|
|
87
|
+
type: 'titles-rail',
|
|
88
|
+
data: [(title as TitleData), ...(railTitles as TitleData[])],
|
|
89
|
+
props: {
|
|
90
|
+
onclose: () => navigate('home'),
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
}
|
|
63
94
|
</script>
|
|
64
95
|
|
|
65
96
|
<svelte:window on:popstate={onhashchange} />
|
|
@@ -12,13 +12,16 @@
|
|
|
12
12
|
items: Record<string, any>[]
|
|
13
13
|
initialIndex?: number
|
|
14
14
|
onchange?: (index: number) => void
|
|
15
|
+
onclose?: () => void
|
|
15
16
|
each: Snippet<[item: any, currentIndex: number]>
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const { items, initialIndex = 0, onchange = () => null, each }: Props = $props()
|
|
19
|
+
const { items, initialIndex = 0, onchange = () => null, onclose = () => null, each }: Props = $props()
|
|
19
20
|
|
|
20
21
|
const transitionDuration = 300
|
|
21
22
|
|
|
23
|
+
console.log({onclose})
|
|
24
|
+
|
|
22
25
|
let slider: TinySlider
|
|
23
26
|
let initialized = $state(false)
|
|
24
27
|
|
|
@@ -35,7 +38,7 @@
|
|
|
35
38
|
}
|
|
36
39
|
</script>
|
|
37
40
|
|
|
38
|
-
<Modal blur>
|
|
41
|
+
<Modal blur {onclose}>
|
|
39
42
|
{#snippet dialog()}
|
|
40
43
|
<div class="rail-modal" style:--transition-duration="{transitionDuration}ms">
|
|
41
44
|
<TinySlider threshold={40} moveThreshold={40} transitionDuration={initialized ? transitionDuration : 0} bind:this={slider}>
|
|
@@ -70,7 +73,7 @@
|
|
|
70
73
|
</div>
|
|
71
74
|
|
|
72
75
|
<div class="close" transition:scale|global>
|
|
73
|
-
<RoundButton size="42px" onclick={() => destroyAllModals()} aria-label="Close">
|
|
76
|
+
<RoundButton size="42px" onclick={() => { onclose(); destroyAllModals() }} aria-label="Close">
|
|
74
77
|
<IconClose size={24} />
|
|
75
78
|
</RoundButton>
|
|
76
79
|
</div>
|
|
@@ -6,6 +6,7 @@ import { mount, unmount } from 'svelte'
|
|
|
6
6
|
import { fakeFetch, generateInjection } from '../helpers'
|
|
7
7
|
import { openModalForInjectedLink } from '$lib/modal'
|
|
8
8
|
import { getLinkInjectionElements } from '$lib/injectionElements'
|
|
9
|
+
import { titleUrl } from '$lib/routes'
|
|
9
10
|
|
|
10
11
|
vi.mock('svelte', () => ({
|
|
11
12
|
mount: vi.fn(),
|
|
@@ -64,8 +65,9 @@ describe('injection.ts', () => {
|
|
|
64
65
|
|
|
65
66
|
const link = /** @type {HTMLAnchorElement} */ (document.querySelector('a'))
|
|
66
67
|
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
expect(link.href).toBe(titleUrl(injection.title_details))
|
|
67
70
|
expect(link.innerText).toBe(injection.title)
|
|
68
|
-
expect(link.href).toBe(injection.playpilot_url)
|
|
69
71
|
})
|
|
70
72
|
|
|
71
73
|
it('Should replace given words as expected when more than 1 injection per sentence is present', () => {
|
|
@@ -83,11 +85,13 @@ describe('injection.ts', () => {
|
|
|
83
85
|
|
|
84
86
|
const links = /** @type {HTMLAnchorElement[]} */ (Array.from(document.querySelectorAll('a')))
|
|
85
87
|
|
|
88
|
+
// @ts-ignore
|
|
89
|
+
expect(links[0].href).toBe(titleUrl(linkInjections[0].title_details))
|
|
86
90
|
expect(links[0].innerText).toBe(linkInjections[0].title)
|
|
87
|
-
expect(links[0].href).toBe(linkInjections[0].playpilot_url)
|
|
88
91
|
|
|
92
|
+
// @ts-ignore
|
|
93
|
+
expect(links[1].href).toBe(titleUrl(linkInjections[1].title_details))
|
|
89
94
|
expect(links[1].innerText).toBe(linkInjections[1].title)
|
|
90
|
-
expect(links[1].href).toBe(linkInjections[1].playpilot_url)
|
|
91
95
|
})
|
|
92
96
|
|
|
93
97
|
it('Should ignore injections that are marked as inactive', () => {
|
|
@@ -946,6 +950,43 @@ describe('injection.ts', () => {
|
|
|
946
950
|
|
|
947
951
|
expect(document.querySelector('a')?.closest('[data-playpilot-injection-key]')).toBeTruthy()
|
|
948
952
|
})
|
|
953
|
+
|
|
954
|
+
describe('config.open_tpi_links_in_explore', () => {
|
|
955
|
+
beforeEach(() => {
|
|
956
|
+
window.PlayPilotLinkInjections.config = {
|
|
957
|
+
open_tpi_links_in_explore: true,
|
|
958
|
+
explore_navigation_path: 'https://some-path.com/explore',
|
|
959
|
+
}
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
it('Should use href with explore links if open_tpi_links_in_explore is true', () => {
|
|
963
|
+
const injection = generateInjection('This is a sentence with an injection.', 'an injection')
|
|
964
|
+
|
|
965
|
+
document.body.innerHTML = `<p>${injection.sentence}</p>`
|
|
966
|
+
|
|
967
|
+
const elements = Array.from(document.querySelectorAll('p'))
|
|
968
|
+
|
|
969
|
+
injectLinksInDocument(elements, { aiInjections: [injection], manualInjections: [] })
|
|
970
|
+
|
|
971
|
+
const link = /** @type {HTMLAnchorElement} */ (document.querySelector('a'))
|
|
972
|
+
expect(link.href).toBe(window.PlayPilotLinkInjections.config.explore_navigation_path + `?route=title&sid=${injection.title_details?.sid}`)
|
|
973
|
+
expect(link.target).not.toBeTruthy()
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
it('Should not open modal when link is clicked when open_tpi_links_in_explore is true', async () => {
|
|
977
|
+
document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
|
|
978
|
+
|
|
979
|
+
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
980
|
+
const injection = generateInjection('This is a sentence with an injection.', 'an injection')
|
|
981
|
+
|
|
982
|
+
injectLinksInDocument(elements, { aiInjections: [injection], manualInjections: [] })
|
|
983
|
+
|
|
984
|
+
const link = /** @type {HTMLAnchorElement} */ (document.querySelector('a'))
|
|
985
|
+
await fireEvent.click(link)
|
|
986
|
+
|
|
987
|
+
expect(openModalForInjectedLink).not.toHaveBeenCalled()
|
|
988
|
+
})
|
|
989
|
+
})
|
|
949
990
|
})
|
|
950
991
|
|
|
951
992
|
describe('clearLinkInjections', () => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { titleUrl } from '$lib/routes'
|
|
2
|
+
import { exploreTitleUrl, titleUrl } from '$lib/routes'
|
|
3
3
|
import { playPilotBaseUrl } from '$lib/constants'
|
|
4
|
+
import { title } from '$lib/fakeData'
|
|
4
5
|
|
|
5
6
|
describe('$lib/routes', () => {
|
|
6
|
-
describe('
|
|
7
|
+
describe('titleUrl', () => {
|
|
7
8
|
it('Should return url for given title', () => {
|
|
8
9
|
// @ts-ignore
|
|
9
10
|
expect(titleUrl({ type: 'series', slug: 'some-slug' })).toBe(`${playPilotBaseUrl}/series/some-slug/`)
|
|
@@ -12,4 +13,15 @@ describe('$lib/routes', () => {
|
|
|
12
13
|
expect(titleUrl({ type: 'movie', slug: 'some-other-slug' })).toBe(`${playPilotBaseUrl}/movie/some-other-slug/`)
|
|
13
14
|
})
|
|
14
15
|
})
|
|
16
|
+
|
|
17
|
+
describe('exploreTitleUrl', () => {
|
|
18
|
+
it('Should return url for given title', () => {
|
|
19
|
+
window.PlayPilotLinkInjections.config = {
|
|
20
|
+
open_tpi_links_in_explore: true,
|
|
21
|
+
explore_navigation_path: 'https://some-path.com/explore',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
expect(exploreTitleUrl(title)).toBe(`https://some-path.com/explore?route=title&sid=${title.sid}`)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
15
27
|
})
|