@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/dist/editorial.mount.js +11 -11
- package/dist/link-injections.js +1 -1
- package/dist/mount.js +6 -6
- package/package.json +1 -1
- package/src/lib/api/participants.ts +9 -0
- package/src/lib/injection.ts +20 -7
- package/src/lib/modal.ts +3 -1
- package/src/lib/routes.ts +7 -2
- package/src/routes/components/Explore/Routes/ExploreModal.svelte +18 -10
- package/src/routes/components/Modals/ParticipantModal.svelte +2 -2
- package/src/routes/components/Participants/Participant.svelte +1 -0
- package/src/routes/components/Rails/TitlesRail.svelte +2 -2
- package/src/tests/lib/injection.test.js +20 -0
- package/src/tests/lib/routes.test.js +10 -5
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/src/lib/injection.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
9
|
-
return
|
|
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
|
-
|
|
24
|
-
|
|
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 (
|
|
27
|
+
if (type === 'title') {
|
|
28
|
+
const isMobile = window.innerWidth < mobileBreakpoint
|
|
29
|
+
const isInReelSplitTest = isMobile && getSplitTestVariantName(SplitTest.Reels) === 'Reel'
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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>
|
|
@@ -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 {
|
|
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 =
|
|
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 {
|
|
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('
|
|
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(
|
|
29
|
+
expect(exploreModalUrl(title.sid)).toBe(`https://some-path.com/explore?route=modal&sid=${title.sid}`)
|
|
25
30
|
})
|
|
26
31
|
})
|
|
27
32
|
})
|