@playpilot/tpi 6.0.0-beta.explore.24 → 6.0.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/link-injections.js +9 -38
- package/package.json +1 -1
- package/src/lib/api/auth.ts +0 -18
- package/src/lib/api/externalPages.ts +2 -14
- package/src/lib/api/search.ts +0 -3
- package/src/lib/api/session.ts +0 -7
- package/src/lib/api/titles.ts +5 -0
- package/src/lib/array.ts +0 -3
- package/src/lib/crawler.ts +0 -3
- package/src/lib/event.ts +0 -3
- package/src/lib/html.ts +2 -2
- package/src/lib/image.ts +0 -3
- package/src/lib/injection.ts +0 -18
- package/src/lib/localization.ts +0 -3
- package/src/lib/meta.ts +0 -6
- package/src/lib/modal.ts +0 -2
- package/src/lib/popover.ts +0 -3
- package/src/lib/routes.ts +0 -3
- package/src/lib/selection.ts +1 -1
- package/src/lib/splitTest.ts +1 -2
- package/src/lib/text.ts +1 -15
- package/src/lib/tracking.ts +3 -2
- package/src/lib/types/config.d.ts +2 -0
- package/src/lib/types/title.d.ts +1 -1
- package/src/main.ts +1 -1
- package/src/routes/+page.svelte +1 -13
- package/src/routes/components/Debugger.svelte +1 -46
- package/src/routes/components/Explore/Filter/Filter.svelte +1 -1
- package/src/routes/components/Rails/ParticipantsRail.svelte +1 -1
- package/src/routes/components/Rails/SimilarRail.svelte +4 -1
- package/src/routes/components/TitleModal.svelte +2 -1
- package/src/tests/lib/api/titles.test.js +3 -3
- package/src/tests/lib/tracking.test.js +3 -5
- package/src/tests/main.test.js +48 -0
- package/src/tests/routes/components/TitleModal.test.js +29 -1
package/package.json
CHANGED
package/src/lib/api/auth.ts
CHANGED
|
@@ -7,11 +7,6 @@ const cookieName = 'EncryptedToken'
|
|
|
7
7
|
const urlParam = 'articleReplacementEditToken'
|
|
8
8
|
const editorialParam = 'playpilot-editorial-mode'
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Authorize the user
|
|
12
|
-
* @param href The current window.location.href
|
|
13
|
-
* @returns Whether the user is authorized or not
|
|
14
|
-
*/
|
|
15
10
|
export async function authorize(href: string = window.location.href): Promise<boolean> {
|
|
16
11
|
try {
|
|
17
12
|
const apiToken = getApiToken()
|
|
@@ -41,11 +36,6 @@ export async function authorize(href: string = window.location.href): Promise<bo
|
|
|
41
36
|
return false
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
/**
|
|
45
|
-
* Get the auth token from the URL, a stored cookie, or from the window object
|
|
46
|
-
* @param [href] URL that the param is extracted from
|
|
47
|
-
* @returns Auth token
|
|
48
|
-
*/
|
|
49
39
|
export function getAuthToken(href: string = ''): string {
|
|
50
40
|
// @ts-ignore
|
|
51
41
|
const configToken = window?.PlayPilotLinkInjections?.editorial_token
|
|
@@ -60,10 +50,6 @@ export function getAuthToken(href: string = ''): string {
|
|
|
60
50
|
return cookieToken || ''
|
|
61
51
|
}
|
|
62
52
|
|
|
63
|
-
/**
|
|
64
|
-
* Set auth cookie equal to given value
|
|
65
|
-
* @param value The auth token value
|
|
66
|
-
*/
|
|
67
53
|
function setAuthCookie(value: string): void {
|
|
68
54
|
const time = new Date()
|
|
69
55
|
const days = 30
|
|
@@ -78,10 +64,6 @@ export function removeAuthCookie(): void {
|
|
|
78
64
|
document.cookie = cookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/'
|
|
79
65
|
}
|
|
80
66
|
|
|
81
|
-
/**
|
|
82
|
-
* Returns whether or not the user has requested editorial mode to be enabled.
|
|
83
|
-
* This won't enable editorial mode by itself, as that also requires authentication.
|
|
84
|
-
*/
|
|
85
67
|
export function isEditorialModeEnabled(): boolean {
|
|
86
68
|
const windowToken = window?.PlayPilotLinkInjections?.editorial_token
|
|
87
69
|
return new URLSearchParams(window.location.search).get(editorialParam) === 'true' || !!windowToken
|
|
@@ -11,11 +11,6 @@ let pollTimeout: ReturnType<typeof setTimeout> | null = null
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Fetch link injections for a URL. This will be either a POST or a GET request depending on whether on not the AI should run.
|
|
14
|
-
* @param pageText Text content of the article
|
|
15
|
-
* @param options
|
|
16
|
-
* @param [options.url] URL of the given article
|
|
17
|
-
* @param [options.hash] unique key to identify the HTML
|
|
18
|
-
* @param [options.params] Any rest params to include in the request body
|
|
19
14
|
*/
|
|
20
15
|
export async function fetchLinkInjections(
|
|
21
16
|
pageText: string | null,
|
|
@@ -41,7 +36,6 @@ export async function fetchLinkInjections(
|
|
|
41
36
|
params.metadata = getPageMetaData()
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
// Always add the given URL, this is used to match the article to the API record
|
|
45
39
|
params.url = url
|
|
46
40
|
|
|
47
41
|
response = await api<LinkInjectionResponse>(apiUrl, {
|
|
@@ -77,11 +71,6 @@ export async function pollLinkInjections(
|
|
|
77
71
|
// This is mostly handy during HMR, but also during navigation changes
|
|
78
72
|
if (pollTimeout) clearTimeout(pollTimeout)
|
|
79
73
|
|
|
80
|
-
/**
|
|
81
|
-
* Polls the endpoint recursively until it is ready.
|
|
82
|
-
* @param resolve Injections are ready and returned as expected
|
|
83
|
-
* @param reject Injections are not yet ready
|
|
84
|
-
*/
|
|
85
74
|
const poll = async (resolve: Function, reject: Function): Promise<void> => {
|
|
86
75
|
let response
|
|
87
76
|
try {
|
|
@@ -134,7 +123,6 @@ export async function pollLinkInjections(
|
|
|
134
123
|
export async function runAiBasedOnResponse(pageText: string, response: LinkInjectionResponse): Promise<LinkInjectionResponse> {
|
|
135
124
|
if (!pageText) return response
|
|
136
125
|
if (!response.ai_enabled) return response
|
|
137
|
-
// Stop if ai is already running, no need to run it while it's already running
|
|
138
126
|
if (response.ai_running) return response
|
|
139
127
|
|
|
140
128
|
const currentModifiedTime = getPageMetaData().content_modified_time
|
|
@@ -149,7 +137,7 @@ export async function runAiBasedOnResponse(pageText: string, response: LinkInjec
|
|
|
149
137
|
export async function saveLinkInjections(linkInjections: LinkInjection[], pageText: string): Promise<LinkInjection[]> {
|
|
150
138
|
const selector = window.PlayPilotLinkInjections?.selector
|
|
151
139
|
|
|
152
|
-
// Only save manual injections, AI injections should be left intact
|
|
140
|
+
// Only save manual injections, AI injections should be left intact and can't be saved over
|
|
153
141
|
const filteredLinkInjections = linkInjections.filter((i: LinkInjection) => i.manual)
|
|
154
142
|
|
|
155
143
|
const newLinkInjections = filteredLinkInjections.map((linkInjection: LinkInjection) => ({
|
|
@@ -180,7 +168,7 @@ export async function saveLinkInjections(linkInjections: LinkInjection[], pageTe
|
|
|
180
168
|
}
|
|
181
169
|
|
|
182
170
|
/**
|
|
183
|
-
*
|
|
171
|
+
* These are used to identify the links on the page.
|
|
184
172
|
* We can't just use SIDs, as a page might include multiple links of the same title
|
|
185
173
|
*/
|
|
186
174
|
function insertRandomKeys(linkInjections: LinkInjection[]): LinkInjection[] {
|
package/src/lib/api/search.ts
CHANGED
|
@@ -2,9 +2,6 @@ import { getApiToken } from '$lib/token'
|
|
|
2
2
|
import type { TitleData } from '../types/title'
|
|
3
3
|
import { api } from './api'
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Search for movies & shows. Requires valid API token.
|
|
7
|
-
*/
|
|
8
5
|
export async function searchTitles(query: string): Promise<TitleData[]> {
|
|
9
6
|
const apiToken = getApiToken()
|
|
10
7
|
|
package/src/lib/api/session.ts
CHANGED
|
@@ -73,17 +73,10 @@ export function isAllowedToEdit(responseSessionId: string | null, responseSessio
|
|
|
73
73
|
return currentSessionId === responseSessionId
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
/**
|
|
77
|
-
* The user's session id is stored in local storage. Of it is not present we generate a new id and store it,
|
|
78
|
-
* returning it immediately.
|
|
79
|
-
*/
|
|
80
76
|
export function getSessionId(): string {
|
|
81
77
|
return sessionStorage.getItem(sessionKey) || setSessionId()
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
/**
|
|
85
|
-
* Store a session id in local storage. If the id is already present in local storage we use that instead.
|
|
86
|
-
*/
|
|
87
80
|
export function setSessionId(): string {
|
|
88
81
|
const id = sessionStorage.getItem(sessionKey) || generateRandomHash()
|
|
89
82
|
sessionStorage.setItem(sessionKey, id)
|
package/src/lib/api/titles.ts
CHANGED
|
@@ -8,6 +8,11 @@ export async function fetchTitles(params: Record<string, string | number> = {}):
|
|
|
8
8
|
|
|
9
9
|
if (!apiToken) throw new Error('No token was provided')
|
|
10
10
|
|
|
11
|
+
params = {
|
|
12
|
+
...params,
|
|
13
|
+
include_count: 'false'
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
const paramsAsString = Object.entries(params).map(([key, value]) => `${key}=${value}`).join('&')
|
|
12
17
|
const response = await api<APIPaginatedResult<TitleData>>(`/titles/browse?api-token=${apiToken}&` + paramsAsString)
|
|
13
18
|
|
package/src/lib/array.ts
CHANGED
package/src/lib/crawler.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Detect whether the user is a bot or crawler simply by their user agent.
|
|
3
|
-
*/
|
|
4
1
|
export function isCrawler(): boolean {
|
|
5
2
|
const userAgent = navigator.userAgent.toLowerCase()
|
|
6
3
|
return /bot|crawl|spider|google|baidu|bing|msn|teoma|slurp|yandex|facebookexternalhit|facebot/i.test(userAgent)
|
package/src/lib/event.ts
CHANGED
package/src/lib/html.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* For instance & -> &
|
|
3
3
|
*/
|
|
4
4
|
export function encodeHtmlEntities(string: string): string {
|
|
5
5
|
const tempElement = document.createElement('div')
|
|
@@ -9,7 +9,7 @@ export function encodeHtmlEntities(string: string): string {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* For instance & -> &
|
|
13
13
|
*/
|
|
14
14
|
export function decodeHtmlEntities(string: string): string {
|
|
15
15
|
const tempElement = document.createElement('div')
|
package/src/lib/image.ts
CHANGED
|
@@ -10,9 +10,6 @@ export function removeImageUrlPrefix(url: string | null): string | null {
|
|
|
10
10
|
return url.replace('/src/img', '')
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Creates a CDN image URL from a UUID.
|
|
15
|
-
*/
|
|
16
13
|
export function imageFromUUID(uuid: string | null, dimensions: (typeof ImageDimensions)[keyof typeof ImageDimensions] | null = null): string {
|
|
17
14
|
if (!uuid?.length) return ''
|
|
18
15
|
|
package/src/lib/injection.ts
CHANGED
|
@@ -420,9 +420,6 @@ function addLinkInjectionEventListeners(injections: LinkInjection[]): void {
|
|
|
420
420
|
})
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
/**
|
|
424
|
-
* Clear link injections from the page
|
|
425
|
-
*/
|
|
426
423
|
export function clearLinkInjections(): void {
|
|
427
424
|
const elements = document.querySelectorAll(keySelector)
|
|
428
425
|
elements.forEach((element) => clearLinkInjection(element.getAttribute(keyDataAttribute) || ''))
|
|
@@ -464,16 +461,10 @@ export function removePlayPilotTitleLinks(): void {
|
|
|
464
461
|
})
|
|
465
462
|
}
|
|
466
463
|
|
|
467
|
-
/**
|
|
468
|
-
* Merge different injection types
|
|
469
|
-
*/
|
|
470
464
|
export function mergeInjectionTypes({ aiInjections, manualInjections }: LinkInjectionTypes): LinkInjection[] {
|
|
471
465
|
return [...manualInjections.map(i => ({ ...i, manual: true })), ...aiInjections]
|
|
472
466
|
}
|
|
473
467
|
|
|
474
|
-
/**
|
|
475
|
-
* Separate an array of flat injections into ai and manual arrays.
|
|
476
|
-
*/
|
|
477
468
|
export function separateLinkInjectionTypes(injections: LinkInjection[]): LinkInjectionTypes {
|
|
478
469
|
return {
|
|
479
470
|
aiInjections: injections.filter(i => !i.manual),
|
|
@@ -481,9 +472,6 @@ export function separateLinkInjectionTypes(injections: LinkInjection[]): LinkInj
|
|
|
481
472
|
}
|
|
482
473
|
}
|
|
483
474
|
|
|
484
|
-
/**
|
|
485
|
-
* Returns whether or not an injection would be valid for any sort of injection, text or after_article
|
|
486
|
-
*/
|
|
487
475
|
export function isValidInjection(injection: LinkInjection): boolean {
|
|
488
476
|
return !injection.inactive && !injection.removed && !injection.duplicate && !!injection.title_details && isValidPlaylinkType(injection)
|
|
489
477
|
}
|
|
@@ -541,18 +529,12 @@ export function sortInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
|
541
529
|
})
|
|
542
530
|
}
|
|
543
531
|
|
|
544
|
-
/**
|
|
545
|
-
* Return whether or not an injection is also available as manual injection
|
|
546
|
-
*/
|
|
547
532
|
export function isAvailableAsManualInjection(injection: LinkInjection, injectionIndex: number, injections: LinkInjection[]): boolean {
|
|
548
533
|
return injections.some((i, index) => {
|
|
549
534
|
return injectionIndex !== index && i.manual && isEquivalentInjection(i, injection)
|
|
550
535
|
})
|
|
551
536
|
}
|
|
552
537
|
|
|
553
|
-
/**
|
|
554
|
-
* Returns whether or not 2 injections match in title and sentence
|
|
555
|
-
*/
|
|
556
538
|
export function isEquivalentInjection(injection1: LinkInjection, injection2: LinkInjection): boolean {
|
|
557
539
|
return injection1.title === injection2.title && cleanPhrase(injection1.sentence) === cleanPhrase(injection2.sentence)
|
|
558
540
|
}
|
package/src/lib/localization.ts
CHANGED
|
@@ -2,9 +2,6 @@ import { translations } from './data/translations'
|
|
|
2
2
|
import { Language } from './enums/Language'
|
|
3
3
|
import type { LanguageCode } from './types/language'
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Super basic implementation to get a string for the given key and language
|
|
7
|
-
*/
|
|
8
5
|
export function t(key: string, language: LanguageCode = getLanguage()): string {
|
|
9
6
|
// @ts-ignore It's fine if it's undefined
|
|
10
7
|
return translations[key]?.[language] || key
|
package/src/lib/meta.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { getLinkInjectionsParentElement } from './injection'
|
|
2
2
|
import type { ArticleMetaData } from './types/injection'
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Returns meta data to be included with injections.
|
|
6
|
-
*/
|
|
7
4
|
export function getPageMetaData(): ArticleMetaData {
|
|
8
5
|
const parent = getLinkInjectionsParentElement()
|
|
9
6
|
|
|
@@ -62,9 +59,6 @@ export function getPagePublishedTime(parent: HTMLElement): string | null {
|
|
|
62
59
|
return getDatetime(datetime)
|
|
63
60
|
}
|
|
64
61
|
|
|
65
|
-
/**
|
|
66
|
-
* Get datetime string as ISO datetime string
|
|
67
|
-
*/
|
|
68
62
|
export function getDatetime(datetime: string): string | null {
|
|
69
63
|
try {
|
|
70
64
|
return new Date(datetime).toISOString()
|
package/src/lib/modal.ts
CHANGED
|
@@ -11,8 +11,6 @@ import { getPlayPilotWrapperElement, keyDataAttribute, keySelector } from "./inj
|
|
|
11
11
|
import { playFallbackViewTransition } from "./viewTransition"
|
|
12
12
|
import { destroyLinkPopover } from "./popover"
|
|
13
13
|
import { prefersReducedMotion } from "svelte/motion"
|
|
14
|
-
import { trackSplitTestAction } from "./splitTest"
|
|
15
|
-
import { SplitTest } from "./enums/SplitTest"
|
|
16
14
|
|
|
17
15
|
type ModalType = 'title' | 'participant' | 'explore'
|
|
18
16
|
|
package/src/lib/popover.ts
CHANGED
|
@@ -47,9 +47,6 @@ export async function destroyLinkPopover(outro: boolean = true) {
|
|
|
47
47
|
document.querySelectorAll<HTMLElement>('[data-playpilot-title-popover]').forEach(element => element.remove())
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
* Destroy active the popover when the mouse leaves any popover element
|
|
52
|
-
*/
|
|
53
50
|
export function destroyLinkPopoverOnMouseleave(event: MouseEvent): void {
|
|
54
51
|
if (!activePopoverInsertedComponent) return
|
|
55
52
|
|
package/src/lib/routes.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { playPilotBaseUrl } from "./constants"
|
|
2
2
|
import type { TitleData } from "./types/title"
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* @returns Full url to PlayPilot page for the given title
|
|
6
|
-
*/
|
|
7
4
|
export function titleUrl(title: TitleData): string {
|
|
8
5
|
return `${playPilotBaseUrl}/${title.type}/${title.slug}/`
|
|
9
6
|
}
|
package/src/lib/selection.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The returned index will be exclusing any leading or trailing spaces. We contain it to actual phrases.
|
|
3
3
|
*/
|
|
4
4
|
export function getIndexOfSelection(selection: Selection, node: Node | null = selection.anchorNode): { start: number, end: number } {
|
|
5
5
|
if (!selection.anchorNode || !node) return { start: -1, end: -1 }
|
package/src/lib/splitTest.ts
CHANGED
|
@@ -54,8 +54,7 @@ export function trackSplitTestView(test: SplitTest): void {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* @param action
|
|
58
|
-
* but will often be as simple as 'click'. Could be any string, though. Whatever fits the use case.
|
|
57
|
+
* @param action This can be just about anything, but will often be as simple as 'click'. Could be any string, though. Whatever fits the use case.
|
|
59
58
|
*/
|
|
60
59
|
export function trackSplitTestAction(test: SplitTest, action: string): void {
|
|
61
60
|
const variant = getSplitTestVariantName(test)
|
package/src/lib/text.ts
CHANGED
|
@@ -20,9 +20,6 @@ export function findTextNodeContaining(text: string, element: HTMLElement, ignor
|
|
|
20
20
|
return node
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* Returns whether or not the node itself is a link or is inside of a link tag.
|
|
25
|
-
*/
|
|
26
23
|
export function isNodeInLink(node: Node): boolean {
|
|
27
24
|
const parentNode = node.parentNode as HTMLElement | null
|
|
28
25
|
|
|
@@ -55,9 +52,6 @@ export function replaceBetween(text: string, replacement: string, startIndex: nu
|
|
|
55
52
|
return text.substring(0, startIndex) + replacement + text.substring(endIndex)
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
/**
|
|
59
|
-
* Returns a string for most consistent string comparisons, decoding Html symbols and removing spaces.
|
|
60
|
-
*/
|
|
61
55
|
export function cleanPhrase(phrase: string): string {
|
|
62
56
|
return decodeHtmlEntities(phrase)
|
|
63
57
|
.toLowerCase()
|
|
@@ -117,7 +111,7 @@ export function reverseString(string: string): string {
|
|
|
117
111
|
}
|
|
118
112
|
|
|
119
113
|
/**
|
|
120
|
-
*
|
|
114
|
+
* Only returns full words, but can contain special characters like &, commas, period, etc.
|
|
121
115
|
*/
|
|
122
116
|
export function getFirstNumberOfWordsInString(sentence: string, numberOfWords = 1): string {
|
|
123
117
|
const match = sentence.match(/[^\s]+/g)
|
|
@@ -190,10 +184,6 @@ export function getNumberOfLeadingAndTrailingSpaces(text: string): { leadingSpac
|
|
|
190
184
|
}
|
|
191
185
|
}
|
|
192
186
|
|
|
193
|
-
/**
|
|
194
|
-
* Get the index of a phrase inside of an element based on a specific word boundary. Returns either the first match
|
|
195
|
-
* or the index of the given occurrence.
|
|
196
|
-
*/
|
|
197
187
|
export function getIndexOfPhraseInElement(phrase: string, element: Element, occurrence = 0): number {
|
|
198
188
|
const sentence = element.innerHTML
|
|
199
189
|
const matches = []
|
|
@@ -239,7 +229,6 @@ export function getRangesOfTextNodesInElement(element: Element): Range[] {
|
|
|
239
229
|
continue
|
|
240
230
|
}
|
|
241
231
|
|
|
242
|
-
|
|
243
232
|
// We are inside of an element and a qoute was given, signaling that we're either opening or closing and attribute.
|
|
244
233
|
// We need to keep track of this in case an attribute value contains ">" or "<"
|
|
245
234
|
if (isInsideElement && character === '"') isInsideAttribute = !isInsideAttribute
|
|
@@ -265,9 +254,6 @@ export function getRangesOfTextNodesInElement(element: Element): Range[] {
|
|
|
265
254
|
return ranges
|
|
266
255
|
}
|
|
267
256
|
|
|
268
|
-
/**
|
|
269
|
-
* Attempt to find a phrase inside a word boundary, only matching phrases between given characters.
|
|
270
|
-
*/
|
|
271
257
|
export function getIndexOfPhraseInBoundary(phrase: string, text: string): number {
|
|
272
258
|
const boundaryCharacters = [' ', '.', ',', '<', '>']
|
|
273
259
|
|
package/src/lib/tracking.ts
CHANGED
|
@@ -26,6 +26,7 @@ export async function track(event: string, title: TitleData | null = null, paylo
|
|
|
26
26
|
if (title) {
|
|
27
27
|
payload = {
|
|
28
28
|
original_title: title.original_title,
|
|
29
|
+
title: title.title,
|
|
29
30
|
title_sid: title.sid,
|
|
30
31
|
title_type: title.type,
|
|
31
32
|
providers: [...new Set(title.providers?.map(p => p.name) || [])],
|
|
@@ -35,8 +36,8 @@ export async function track(event: string, title: TitleData | null = null, paylo
|
|
|
35
36
|
|
|
36
37
|
payload.version = __SCRIPT_VERSION__
|
|
37
38
|
payload.url = getFullUrlPath()
|
|
38
|
-
payload.organization_sid = window.PlayPilotLinkInjections?.organization_sid
|
|
39
|
-
payload.domain_sid = window.PlayPilotLinkInjections?.domain_sid
|
|
39
|
+
payload.organization_sid = window.PlayPilotLinkInjections?.organization_sid || 'undefined'
|
|
40
|
+
payload.domain_sid = window.PlayPilotLinkInjections?.domain_sid || 'undefined'
|
|
40
41
|
payload.device = {
|
|
41
42
|
type: window.innerWidth > mobileBreakpoint ? 'desktop' : 'mobile',
|
|
42
43
|
width: window.innerWidth,
|
|
@@ -46,9 +46,11 @@ export type ConfigResponse = {
|
|
|
46
46
|
* `explore_navigation_label` will end up being the label of the navigation item, defaults to `Streaming Guide`.
|
|
47
47
|
* `explore_navigation_path` is the path that the navigation item will lead to, this will be set up by the third party.
|
|
48
48
|
* `explore_navigation_insert_position` is used to determine if the navigation item should be inserted before or after the given selector.
|
|
49
|
+
* `explore_insert_cta_in_tpi` is used to insert a call to action in modals which opens the explore page as a modal.
|
|
49
50
|
*/
|
|
50
51
|
explore_navigation_selector?: string
|
|
51
52
|
explore_navigation_label?: string
|
|
52
53
|
explore_navigation_path?: string
|
|
53
54
|
explore_navigation_insert_position?: InsertPosition
|
|
55
|
+
explore_insert_cta_in_tpi: boolean
|
|
54
56
|
}
|
package/src/lib/types/title.d.ts
CHANGED
package/src/main.ts
CHANGED
package/src/routes/+page.svelte
CHANGED
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
import { fetchConfig } from '$lib/api/config'
|
|
11
11
|
import { insertExplore, insertExploreIntoNavigation } from '$lib/explore'
|
|
12
12
|
import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/api/auth'
|
|
13
|
-
import { getSplitTestVariantName } from '$lib/splitTest'
|
|
14
|
-
import { SplitTest } from '$lib/enums/SplitTest'
|
|
15
13
|
import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
|
|
16
14
|
import Editor from './components/Editorial/Editor.svelte'
|
|
17
15
|
import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
|
|
@@ -37,10 +35,8 @@
|
|
|
37
35
|
|
|
38
36
|
const pageText = $derived(getPageText(elements))
|
|
39
37
|
|
|
40
|
-
// Store initializing promise for later, we'll need that to be awaited after the user has given or refused consent.
|
|
41
38
|
const initializePromise = initialize()
|
|
42
39
|
|
|
43
|
-
// Rerender link injections when linkInjections change. This is only relevant for editiorial mode.
|
|
44
40
|
$effect(() => {
|
|
45
41
|
if (isEditorialMode && !loading) rerender()
|
|
46
42
|
})
|
|
@@ -76,7 +72,6 @@
|
|
|
76
72
|
|
|
77
73
|
window.PlayPilotLinkInjections.config = config
|
|
78
74
|
|
|
79
|
-
// If the URL was marked as being excluded in the config, we stop injections here.
|
|
80
75
|
isUrlExcluded = !!(config?.exclude_urls_pattern && url.match(new RegExp(config.exclude_urls_pattern)))
|
|
81
76
|
if (isUrlExcluded) return
|
|
82
77
|
|
|
@@ -85,7 +80,7 @@
|
|
|
85
80
|
|
|
86
81
|
setElements(config?.html_selector || '', config?.exclude_elements_selector || '')
|
|
87
82
|
} catch(error) {
|
|
88
|
-
// We
|
|
83
|
+
// We return if the config did not get fetched properly, as we can't determine what should and should
|
|
89
84
|
// get injected without it.
|
|
90
85
|
track(TrackingEvent.FetchingConfigFailed)
|
|
91
86
|
console.error('TPI Config failed to fetch', error)
|
|
@@ -103,16 +98,11 @@
|
|
|
103
98
|
|
|
104
99
|
setTrackingSids({ organizationSid: response.organization_sid, domainSid: response.domain_sid })
|
|
105
100
|
|
|
106
|
-
// We only show results once they are fully processed. We only inject if AI is no longer running, or isn't
|
|
107
|
-
// meant to run to begin with.
|
|
108
101
|
if (!response.ai_running || !response.ai_enabled) {
|
|
109
102
|
inject({ aiInjections, manualInjections })
|
|
110
103
|
return
|
|
111
104
|
}
|
|
112
105
|
|
|
113
|
-
// A response was previous returned, but injections were still being generated in the backend.
|
|
114
|
-
// With this second request we wait until AI links are ready. We only do this in editorial
|
|
115
|
-
// so as not to suddenly insert new links while a user is reading the article.
|
|
116
106
|
if (!isEditorialMode || isCrawler()) return
|
|
117
107
|
|
|
118
108
|
response = await pollLinkInjections(pageText, { requireCompletedResult: true, onpoll: (update) => response = update })
|
|
@@ -137,8 +127,6 @@
|
|
|
137
127
|
}
|
|
138
128
|
|
|
139
129
|
function inject(injections: LinkInjectionTypes = { aiInjections, manualInjections }): void {
|
|
140
|
-
// Get filtered injections as they are shown on the page.
|
|
141
|
-
// Only update state if it they are different from current injections.
|
|
142
130
|
const filteredInjections = injectLinksInDocument(elements, injections)
|
|
143
131
|
|
|
144
132
|
if (JSON.stringify(filteredInjections) !== JSON.stringify(linkInjections)) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { SplitTest } from '$lib/enums/SplitTest'
|
|
3
|
-
import { insertExplore } from '$lib/explore'
|
|
4
3
|
import { getFullUrlPath } from '$lib/url'
|
|
5
4
|
import { onDestroy } from 'svelte'
|
|
6
5
|
|
|
@@ -85,6 +84,7 @@
|
|
|
85
84
|
})
|
|
86
85
|
}
|
|
87
86
|
|
|
87
|
+
// Replace the current script with the beta version of the script
|
|
88
88
|
function replaceWithBetaScript() {
|
|
89
89
|
const currentScriptTag: HTMLScriptElement | null = document.querySelector('script[src*="scripts.playpilot.com/link-injection"]')
|
|
90
90
|
if (!currentScriptTag) return
|
|
@@ -108,43 +108,6 @@
|
|
|
108
108
|
currentScriptTag.insertAdjacentElement('afterend', newScriptTag)
|
|
109
109
|
currentScriptTag.remove()
|
|
110
110
|
}
|
|
111
|
-
|
|
112
|
-
function replacePageWithExplore() {
|
|
113
|
-
const element = document.querySelector("main, article")
|
|
114
|
-
|
|
115
|
-
element!.innerHTML = `
|
|
116
|
-
<div data-playpilot-explore style="
|
|
117
|
-
background: white;
|
|
118
|
-
--playpilot-dark: white;
|
|
119
|
-
--playpilot-light: white;
|
|
120
|
-
--playpilot-lighter: #e1e1e1;
|
|
121
|
-
--playpilot-content: #e5e5e5;
|
|
122
|
-
--playpilot-content-light: #c1c1c1;
|
|
123
|
-
--playpilot-text-color: black;
|
|
124
|
-
--playpilot-text-color-alt: black;
|
|
125
|
-
--playpilot-border-radius: 0;
|
|
126
|
-
--playpilot-playlink-border-radius: 0;
|
|
127
|
-
--playpilot-explore-search-border: 1px solid black;
|
|
128
|
-
--playpilot-explore-search-background: white;
|
|
129
|
-
--playpilot-explore-divider-height: 4px;
|
|
130
|
-
--playpilot-explore-divider-background: linear-gradient(to right,#51B3E0,#51B3E0 2.5rem,#E5ADAE 2.5rem,#E5ADAE 5rem,#E5E54F 5rem,#E5E54F 7.5rem,black 7.5rem,black);">
|
|
131
|
-
<div style="padding: 64px 32px; min-height: 100vh; color: black; max-width: 1264px; margin: 0 auto">
|
|
132
|
-
<div class="divider" style="width: 100%; max-width: 600px; height: 0.25rem;background-image: var(--playpilot-explore-divider-background)"></div>
|
|
133
|
-
|
|
134
|
-
<div class="heading" style="margin: 4px 0; font-size: clamp(24px, 5vw, 32px); line-height: 1.5; font-weight: bold; text-transform: uppercase;">
|
|
135
|
-
Streaming Guide
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
<div role="status" aria-live="polite">
|
|
139
|
-
<svg fill="currentColor" viewBox="0 0 24 24" width="72"><path fill="currentColor" d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"><animateTransform attributeName="transform" type="rotate" dur="500ms" values="0 12 12;360 12 12" repeatCount="indefinite"/></path></svg>
|
|
140
|
-
<div style="margin: 12px 0; font-weight: bold; text-transform: uppercase;">Loading…</div>
|
|
141
|
-
<noscript>Sorry, this page requires JavaScript to be enabled.</noscript>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
</div>`
|
|
145
|
-
|
|
146
|
-
insertExplore()
|
|
147
|
-
}
|
|
148
111
|
</script>
|
|
149
112
|
|
|
150
113
|
<svelte:window {onkeydown} />
|
|
@@ -201,14 +164,6 @@
|
|
|
201
164
|
{:else}
|
|
202
165
|
<button onclick={replaceWithBetaScript}>Use beta script</button>
|
|
203
166
|
{/if}
|
|
204
|
-
|
|
205
|
-
<hr />
|
|
206
|
-
|
|
207
|
-
<button onclick={replacePageWithExplore}>Replace page with explore</button>
|
|
208
|
-
|
|
209
|
-
<hr />
|
|
210
|
-
|
|
211
|
-
<button onclick={() => shown = false}>Close debugger</button>
|
|
212
167
|
</div>
|
|
213
168
|
{/if}
|
|
214
169
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
const items = [{
|
|
22
22
|
label: 'Services',
|
|
23
23
|
param: 'provider',
|
|
24
|
-
data: [{ label: '
|
|
24
|
+
data: [{ label: 'Netflix', value: 'netflix' }, { label: 'Disney+', value: 'disney-plus' }],
|
|
25
25
|
}, {
|
|
26
26
|
label: 'Genres',
|
|
27
27
|
param: 'genres',
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
function onclick(event: MouseEvent, participant: ParticipantData): void {
|
|
45
45
|
openModal({ event, type: 'participant', data: participant })
|
|
46
|
-
track(TrackingEvent.ParticipantClick, null, { title_source: title.original_title, participant: participant.name })
|
|
46
|
+
track(TrackingEvent.ParticipantClick, null, { title_source: title.original_title || title.title, participant: participant.name })
|
|
47
47
|
}
|
|
48
48
|
</script>
|
|
49
49
|
|
|
@@ -15,4 +15,7 @@
|
|
|
15
15
|
const titles = fetchSimilarTitles(title)
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
|
-
<TitlesRail
|
|
18
|
+
<TitlesRail
|
|
19
|
+
{titles}
|
|
20
|
+
heading={t('Similar Titles')}
|
|
21
|
+
onclick={(targetTitle) => track(TrackingEvent.SimilarTitleClick, targetTitle, { title_source: title.original_title || title.title })} />
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
const topScrollAd = getFirstAdOfType('top_scroll')
|
|
21
21
|
const displayAd = getFirstAdOfType('card')
|
|
22
|
+
const insertExploreCta = window.PlayPilotLinkInjections?.config?.explore_insert_cta_in_tpi
|
|
22
23
|
|
|
23
24
|
let hasTrackedScrolling = false
|
|
24
25
|
|
|
@@ -52,6 +53,6 @@
|
|
|
52
53
|
{/if}
|
|
53
54
|
{/snippet}
|
|
54
55
|
|
|
55
|
-
<Modal {onscroll} {initialScrollPosition} {bubble} prepend={displayAd ? prepend : null} tall>
|
|
56
|
+
<Modal {onscroll} {initialScrollPosition} bubble={!!topScrollAd || insertExploreCta ? bubble : null} prepend={displayAd ? prepend : null} tall>
|
|
56
57
|
<Title {title} />
|
|
57
58
|
</Modal>
|