@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "8.14.0-beta.1",
3
+ "version": "8.14.0-beta.3",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -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
  }
@@ -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 = injection.playpilot_url
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
- window.addEventListener('click', (event) => openModalForInjectedLink(event, injections))
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
- history.pushState({}, '', currentUrl)
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('mergePlaylinks', () => {
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
  })