@playpilot/tpi 8.29.0-beta.1 → 8.29.0-beta.10

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.29.0-beta.1",
3
+ "version": "8.29.0-beta.10",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -1,3 +1,4 @@
1
+ import { participants } from '$lib/fakeData'
1
2
  import { getLanguage } from '$lib/language'
2
3
  import { getApiToken } from '$lib/token'
3
4
  import type { ParticipantData } from '$lib/types/participant'
@@ -25,3 +26,11 @@ export async function fetchParticipantsForTitle(title: TitleData): Promise<Parti
25
26
 
26
27
  return response.results
27
28
  }
29
+
30
+ export async function fetchParticipantBySid(sid: string): Promise<ParticipantData> {
31
+ const participant = await api<ParticipantData>(`/participants/${sid}?api-token=${getApiToken()}`)
32
+
33
+ if (!participant) throw new Error('No participant was returned for sid: ' + sid)
34
+
35
+ return participant
36
+ }
@@ -5,8 +5,9 @@ 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
+ import { exploreModalUrl, participantUrl, titleUrl } from './routes'
9
9
  import { clearInTextWidgets, insertInTextWidgets } from './inTextWidgets'
10
+ import { encodeHtmlEntities } from './html'
10
11
 
11
12
  export const keyDataAttribute = 'data-playpilot-injection-key'
12
13
  export const keySelector = `[${keyDataAttribute}]`
@@ -31,9 +32,7 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
31
32
 
32
33
  const failedMessages: Record<string, string> = {}
33
34
 
34
- // Start at -1 so that we can add +1 right at the top of the loop, making it start at 0.
35
- // This index is used in Option 3 below.
36
- let injectionIndex = -1
35
+ let injectionIndex = -1 // This index is used in Option 3 below.
37
36
  for (const injection of sortInjections(foundInjections)) {
38
37
  injectionIndex++
39
38
 
@@ -84,9 +83,21 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
84
83
  // We insert the html here separately from below, where it's done with replacementIndex because we need to capture all html
85
84
  // that may have been inside of the match.
86
85
  if (replacementIndex === -1) {
87
- const match = findShortestMatchBetweenPhrases(element.innerHTML, injection.title, phrase_before || '', phrase_after || '')
86
+ let match = findShortestMatchBetweenPhrases(element.innerHTML, injection.title, phrase_before || '', phrase_after || '')
88
87
 
89
88
  if (match) {
89
+ // This is a crude way of checking if a match contains HTML elements
90
+ // If the title is directly inside of an element we discard the rest.
91
+ // This happens sometimes in lists that consists of nothing but titles and may contain special characters
92
+ if (match.match.includes(">") && match.match.includes("<")) {
93
+ const encodedTitle = encodeHtmlEntities(injection.title)
94
+ const matchInsideHTMLIndex = match.match.search(new RegExp(`>\s*${encodedTitle}\s*<`)) + 1
95
+
96
+ if (matchInsideHTMLIndex > 0) {
97
+ match = { match: match.match.slice(matchInsideHTMLIndex, matchInsideHTMLIndex + encodedTitle.length), index: matchInsideHTMLIndex }
98
+ }
99
+ }
100
+
90
101
  const { leadingSpaces, trailingSpaces } = getNumberOfLeadingAndTrailingSpaces(match.match)
91
102
 
92
103
  linkElement.innerHTML = match.match.trim()
@@ -205,15 +216,17 @@ function createLinkInjectionElement(injection: LinkInjection): { injectionElemen
205
216
 
206
217
  const openInExplore = !!window.PlayPilotLinkInjections?.config?.open_tpi_links_in_explore
207
218
 
208
- const href = openInExplore ? exploreTitleUrl(injection.title_details!) : titleUrl(injection.title_details!)
219
+ const sid = injection.title_details?.sid || injection.participant_details?.sid
220
+ const href = openInExplore ? exploreModalUrl(sid!) : (injection.type === 'title' ? titleUrl(injection.title_details!) : participantUrl(injection.participant_details!))
209
221
 
210
222
  const linkElement = document.createElement('a')
211
- linkElement.dataset.playpilotPosterUrl = injection.title_details?.standing_poster
212
223
  linkElement.innerText = injection.title
213
224
  linkElement.href = href
214
225
  linkElement.target = openInExplore ? '' : '_blank'
215
226
  linkElement.rel = 'noopener nofollow noreferrer'
216
227
 
228
+ if (injection.type === 'title') linkElement.dataset.playpilotPosterUrl = injection.title_details?.standing_poster
229
+
217
230
  injectionElement.insertAdjacentElement('beforeend', linkElement)
218
231
 
219
232
  return { injectionElement, linkElement }
package/src/lib/modal.ts CHANGED
@@ -13,10 +13,12 @@ import { prefersReducedMotion } from 'svelte/motion'
13
13
  import TitlesRailModal from '../routes/components/Modals/TitlesRailModal.svelte'
14
14
  import TitlesReelModal from '../routes/components/Modals/TitlesReelModal.svelte'
15
15
 
16
+ export type ModalType = 'title' | 'participant' | 'titles-rail' | 'titles-reel'
17
+
16
18
  type Modal = {
17
19
  injection?: LinkInjection | null
18
20
  component?: object
19
- type: 'title' | 'participant' | 'titles-rail' | 'titles-reel'
21
+ type: ModalType
20
22
  data: TitleData | TitleData[] | ParticipantData | null
21
23
  scrollPosition?: number
22
24
  }
package/src/lib/routes.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  import { playPilotBaseUrl } from './constants'
2
+ import type { ParticipantData } from './types/participant'
2
3
  import type { TitleData } from './types/title'
3
4
 
4
5
  export function titleUrl(title: TitleData): string {
5
6
  return `${playPilotBaseUrl}/${title.type}/${title.slug}/`
6
7
  }
7
8
 
8
- export function exploreTitleUrl(title: TitleData): string {
9
- return window.PlayPilotLinkInjections?.config?.explore_navigation_path + `?route=modal&sid=${title.sid}`
9
+ export function participantUrl(participant: ParticipantData): string {
10
+ return `${playPilotBaseUrl}/name/${participant.sid}/`
11
+ }
12
+
13
+ export function exploreModalUrl(sid: string): string {
14
+ return window.PlayPilotLinkInjections?.config?.explore_navigation_path + `?route=modal&sid=${sid}`
10
15
  }
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
+ import { fetchParticipantBySid } from '$lib/api/participants'
2
3
  import { fetchSimilarTitles, fetchTitleBySid } from '$lib/api/titles'
3
4
  import { mobileBreakpoint } from '$lib/constants'
4
5
  import { SplitTest } from '$lib/enums/SplitTest'
5
6
  import { t } from '$lib/localization'
6
- import { openModal } from '$lib/modal'
7
+ import { openModal, type ModalType } from '$lib/modal'
7
8
  import { getSplitTestVariantName, trackSplitTestView } from '$lib/splitTest'
8
9
  import type { TitleData } from '$lib/types/title'
9
10
  import Modal from '../../Modals/Modal.svelte'
@@ -20,20 +21,27 @@
20
21
 
21
22
  if (!sid) return
22
23
 
23
- const isMobile = window.innerWidth < mobileBreakpoint
24
- const isInReelSplitTest = isMobile && getSplitTestVariantName(SplitTest.Reels) === 'Reel'
25
- const type = isMobile && isInReelSplitTest ? 'titles-reel' : 'titles-rail'
24
+ let type: ModalType = sid.startsWith('pr') ? 'participant' : 'title' // Sids with with `pr` for participants, `ti` for titles
25
+ let data: any = null
26
26
 
27
- if (isMobile) trackSplitTestView(SplitTest.Reels)
27
+ if (type === 'title') {
28
+ const isMobile = window.innerWidth < mobileBreakpoint
29
+ const isInReelSplitTest = isMobile && getSplitTestVariantName(SplitTest.Reels) === 'Reel'
28
30
 
29
- const [title, railTitles] = (await Promise.allSettled([
30
- fetchTitleBySid(sid),
31
- fetchSimilarTitles({ sid } as unknown as TitleData)])
32
- ).map(promise => (promise.status === 'fulfilled' ? promise.value : null))
31
+ if (isMobile) trackSplitTestView(SplitTest.Reels)
32
+
33
+ type = isMobile && isInReelSplitTest ? 'titles-reel' : 'titles-rail'
34
+
35
+ const [title, railTitles] = (await Promise.allSettled([fetchTitleBySid(sid), fetchSimilarTitles({ sid } as unknown as TitleData)])).map((promise) => promise.status === 'fulfilled' ? promise.value : null)
36
+
37
+ data = [title as TitleData, ...(railTitles as TitleData[])]
38
+ } else if (type === 'participant') {
39
+ data = await fetchParticipantBySid(sid)
40
+ }
33
41
 
34
42
  openModal({
35
43
  type,
36
- data: [(title as TitleData), ...(railTitles as TitleData[])],
44
+ data,
37
45
  props: {
38
46
  onclose: () => navigate('home'),
39
47
  pushState: false,
@@ -12,7 +12,7 @@
12
12
  initialScrollPosition?: number
13
13
  }
14
14
 
15
- const { participant, initialScrollPosition = 0 }: Props = $props()
15
+ const { participant, initialScrollPosition = 0, ...restProps }: Props = $props()
16
16
 
17
17
  let windowWidth = $state(0)
18
18
 
@@ -27,6 +27,6 @@
27
27
 
28
28
  <svelte:window bind:innerWidth={windowWidth} />
29
29
 
30
- <Modal {initialScrollPosition} closeButtonStyle="flat" tall>
30
+ <Modal {initialScrollPosition} {...restProps} closeButtonStyle="flat" tall>
31
31
  <Participant {participant} />
32
32
  </Modal>
@@ -107,6 +107,7 @@
107
107
  }
108
108
 
109
109
  &.small {
110
+ --participant-image-size: #{margin(6)};
110
111
  padding: margin(1);
111
112
  }
112
113
  }
@@ -5,7 +5,7 @@
5
5
  import Rail from './Rail.svelte'
6
6
  import type { TitleData } from '$lib/types/title'
7
7
  import { openModal } from '$lib/modal'
8
- import { exploreTitleUrl } from '$lib/routes'
8
+ import { exploreModalUrl } from '$lib/routes'
9
9
  import { generateRandomHash } from '$lib/hash'
10
10
  import { getFirstTitleWithAvailableTrailer } from '$lib/trailer'
11
11
  import { useExploreRouter } from '$lib/explore'
@@ -88,7 +88,7 @@
88
88
  onclick(title)
89
89
 
90
90
  if (navigateToExplore) {
91
- window.location.href = exploreTitleUrl(title)
91
+ window.location.href = exploreModalUrl(title.sid)
92
92
  return
93
93
  }
94
94
 
@@ -867,6 +867,26 @@ describe('injection.ts', () => {
867
867
  expect(document.querySelectorAll('a')).toHaveLength(4)
868
868
  })
869
869
 
870
+ it('Should properly inject into lists of titles that contain special characters', () => {
871
+ document.body.innerHTML = `<section>
872
+ <ul>
873
+ <li>Daisy Jones & The Six</li>
874
+ <li>Love & Death</li>
875
+ </ul>
876
+ </section>
877
+ `
878
+
879
+ const elements = getLinkInjectionElements(/** @type {HTMLElement} */ (document.querySelector('section')))
880
+ const injections = [
881
+ generateInjection('Daisy Jones & The Six', 'Daisy Jones & The Six'),
882
+ generateInjection('Love & Death', 'Love & Death'),
883
+ ]
884
+
885
+ injectLinksInDocument(elements, { aiInjections: [], manualInjections: injections })
886
+
887
+ expect(document.querySelectorAll('a')).toHaveLength(2)
888
+ })
889
+
870
890
  it('Should properly inject into titles that are split up by multiple elements when phrase_before and phrase_after are given', () => {
871
891
  document.body.innerHTML = '<section>Some text with a <strong>phra</strong><strong>se</strong> in it</section>'
872
892
 
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { exploreTitleUrl, titleUrl } from '$lib/routes'
2
+ import { exploreModalUrl, participantUrl, titleUrl } from '$lib/routes'
3
3
  import { playPilotBaseUrl } from '$lib/constants'
4
- import { title } from '$lib/fakeData'
4
+ import { participants, title } from '$lib/fakeData'
5
5
 
6
6
  describe('$lib/routes', () => {
7
7
  describe('titleUrl', () => {
@@ -14,14 +14,19 @@ describe('$lib/routes', () => {
14
14
  })
15
15
  })
16
16
 
17
- describe('exploreTitleUrl', () => {
17
+ describe('participantUrl', () => {
18
+ it('Should return url for given title', () => {
19
+ expect(participantUrl(participants[0])).toBe(`${playPilotBaseUrl}/name/${participants[0].sid}/`)
20
+ })
21
+ })
22
+
23
+ describe('exploreModalUrl', () => {
18
24
  it('Should return url for given title', () => {
19
25
  window.PlayPilotLinkInjections.config = {
20
- open_tpi_links_in_explore: true,
21
26
  explore_navigation_path: 'https://some-path.com/explore',
22
27
  }
23
28
 
24
- expect(exploreTitleUrl(title)).toBe(`https://some-path.com/explore?route=modal&sid=${title.sid}`)
29
+ expect(exploreModalUrl(title.sid)).toBe(`https://some-path.com/explore?route=modal&sid=${title.sid}`)
25
30
  })
26
31
  })
27
32
  })