@playpilot/tpi 5.6.0-beta.video.7 → 5.7.0-beta.display-2
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 -9
- package/events.md +8 -2
- package/package.json +1 -1
- package/src/lib/ads.ts +38 -0
- package/src/lib/constants.ts +2 -1
- package/src/lib/enums/ImageDimensions.ts +7 -0
- package/src/lib/enums/SplitTest.ts +23 -0
- package/src/lib/enums/TrackingEvent.ts +3 -0
- package/src/lib/image.ts +12 -0
- package/src/lib/linkInjection.ts +36 -2
- package/src/lib/splitTest.ts +19 -14
- package/src/lib/types/campaign.d.ts +45 -0
- package/src/lib/types/playlink.d.ts +2 -0
- package/src/lib/types/script.d.ts +5 -0
- package/src/main.ts +6 -0
- package/src/routes/+layout.svelte +4 -0
- package/src/routes/+page.svelte +13 -2
- package/src/routes/components/Ads/Display.svelte +140 -0
- package/src/routes/components/Ads/TopScroll.svelte +240 -0
- package/src/routes/components/DragHandle.svelte +105 -0
- package/src/routes/components/Modal.svelte +125 -30
- package/src/routes/components/Playlink.svelte +185 -0
- package/src/routes/components/Playlinks.svelte +34 -168
- package/src/routes/components/Popover.svelte +30 -2
- package/src/routes/components/Title.svelte +13 -43
- package/src/routes/components/TitleModal.svelte +24 -10
- package/src/routes/components/TitlePopover.svelte +12 -6
- package/src/tests/lib/ads.test.js +55 -0
- package/src/tests/lib/splitTest.test.js +97 -18
- package/src/tests/routes/+page.test.js +33 -0
- package/src/tests/routes/components/Ads/Display.test.js +109 -0
- package/src/tests/routes/components/Ads/TopScroll.test.js +104 -0
- package/src/tests/routes/components/Playlink.test.js +95 -0
- package/src/tests/routes/components/Playlinks.test.js +0 -30
- package/src/tests/routes/components/TitleModal.test.js +36 -0
- package/src/tests/routes/components/TitlePopover.test.js +20 -0
- package/src/routes/components/Ads/FloatingPlaylink.svelte +0 -62
- package/src/routes/components/Ads/VideoBackground.svelte +0 -28
package/events.md
CHANGED
|
@@ -64,5 +64,11 @@ Event | Action | Info | Payload
|
|
|
64
64
|
### Split Testing
|
|
65
65
|
Event | Action | Info | Payload
|
|
66
66
|
--- | --- | --- | ---
|
|
67
|
-
`ali_split_test_view` | _Should be fired for any active split test_ | | `key`, `variant` (
|
|
68
|
-
`ali_split_test_action` | _Should be fired for any assertion in split tests_ | | `key` (matches the key of `ali_split_test_view`), `variant` (
|
|
67
|
+
`ali_split_test_view` | _Should be fired for any active split test_ | | `key`, `variant` (a whole number starting at 0)
|
|
68
|
+
`ali_split_test_action` | _Should be fired for any assertion in split tests_ | | `key` (matches the key of `ali_split_test_view`), `variant` (a whole number starting at 0), `action`
|
|
69
|
+
|
|
70
|
+
### Ads
|
|
71
|
+
Event | Action | Info | Payload
|
|
72
|
+
--- | --- | --- | ---
|
|
73
|
+
`ali_top_scroll_click` | _Fired any time the top scroll ad is called_ | | `campaign_name`
|
|
74
|
+
`ali_display_ad_click` | _Fired any time the top scroll ad is clicked_ | | `campaign_name`
|
package/package.json
CHANGED
package/src/lib/ads.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getApiToken } from "./api"
|
|
2
|
+
import { apiBaseUrl } from "./constants"
|
|
3
|
+
import type { Campaign, CampaignFormat } from "./types/campaign"
|
|
4
|
+
import type { PlaylinkData } from "./types/playlink"
|
|
5
|
+
|
|
6
|
+
export async function fetchAds() {
|
|
7
|
+
const headers = new Headers({ 'Content-Type': 'application/json' })
|
|
8
|
+
const apiToken = getApiToken()
|
|
9
|
+
|
|
10
|
+
if (!apiToken) throw new Error('No token was provided')
|
|
11
|
+
|
|
12
|
+
const response = await fetch(apiBaseUrl + `/ads/browse/?region=nl&api-token=${apiToken}`, { headers })
|
|
13
|
+
|
|
14
|
+
if (!response.ok) throw response
|
|
15
|
+
|
|
16
|
+
const parsed = await response.json()
|
|
17
|
+
|
|
18
|
+
return parsed
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getFirstAdOfType(format: CampaignFormat): Campaign | null {
|
|
22
|
+
return (window.PlayPilotLinkInjections?.ads || []).find(i => i.campaign_format === format) || null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function campaignToPlaylink(campaign: Campaign): PlaylinkData {
|
|
26
|
+
return {
|
|
27
|
+
sid: '',
|
|
28
|
+
name: campaign.content.header || '',
|
|
29
|
+
url: campaign.cta.url || '',
|
|
30
|
+
logo_url: campaign.cta.image || '',
|
|
31
|
+
highlighted: true,
|
|
32
|
+
cta_text: campaign.content.subheader,
|
|
33
|
+
action_text: campaign.cta.header,
|
|
34
|
+
extra_info: {
|
|
35
|
+
category: 'SVOD'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/lib/constants.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const playPilotBaseUrl = 'https://www.playpilot.com'
|
|
2
|
-
export const apiBaseUrl = 'https://partner-api.playpilot.tech/1.0.0'
|
|
2
|
+
export const apiBaseUrl = 'https://partner-api-staging.playpilot.tech/1.0.0'
|
|
3
|
+
export const imageBaseUrl = 'https://img-staging.playpilot.tech'
|
|
3
4
|
|
|
4
5
|
export const imagePlaceholderDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFQAAAB+CAMAAACNgsajAAAAb1BMVEU1Q2fI1N5YZoNVYoFndI9qd5JBT3FFU3S8yNS3xNC4xNFBTnDG0t2lscJJV3e9ytaptcWToLOOm6/BzdiZprh3hJ1aaIWJlatte5VPXHx7iJ9zgZlfbYk3RWmNmq5jcY1XZIO2wtCjsMB+i6I9S25IprTeAAABqUlEQVRo3u3W2ZKCMBCF4T5ExRmRVXBfZnn/Z5wKiU5pz1AJ7ZXV/x03HxVCB0jTNE3TNE3TNO0l2rbPNxcFdk8334AINTUD5eSaWbPoQs0qw0CVN98BzNNgE4NVv+ZHsJliuNVt7UgotATAafp/5mZiG4waAB0N5k0kUeg0wMykKDfLvRTl5pxyCFFupjQVo9ykiRTlphzl5nNQNu8C9Hv2lylDT0W2NMyUoeXdLC68KUNLuM7O9HskQ0uLLAEUR2aOQjfORE5LzHP2PMehxpl2k6otM8eh2aQGkBlieyRBYbs3y5Rk6IPZn8mT65XJR6LcROGErwaoxqLm4XvkiTVsy1FoYe5n06zcjazp1Wk0umHz3k9BT6+bXjXR6PnRtKpr755PfRjx4WPz7tXW/26gGYnOvOmrM7xtiK4qYtDOrpGZtnR7JFcSi+Z1XZt7k5d4NCZmcrWxqMzkbRgqN+nAULHpx1RiylFvftJwlxjUz2bWdpPB7NnSxZih5RFrD20Vai4izGOgeenPukMSUE6hte5denI7NMyU1xrSNE3TNE3TNE17hX4ADHsS0j0OCOoAAAAASUVORK5CYII='
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Image dimensions to be used to generate images from the supplied image UUIDs.
|
|
2
|
+
// The number given is a multiplier of the base 32 pixels.
|
|
3
|
+
// https://docs.google.com/spreadsheets/d/1Nx73ux1fgGKJfuaUeQRwSXUJdSQonPG5DxjW2zxnyz0
|
|
4
|
+
export const ImageDimensions = {
|
|
5
|
+
TopScrollBackground: 'topscrollBackgroundMobile', // 600x140
|
|
6
|
+
TopScrollContent: '8by1x23',
|
|
7
|
+
} as const
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const SplitTest = {
|
|
2
|
+
InitialTest: {
|
|
3
|
+
key: 'initial_test',
|
|
4
|
+
numberOfVariants: 2,
|
|
5
|
+
},
|
|
6
|
+
MultipleVariants: {
|
|
7
|
+
key: 'multiple_variants',
|
|
8
|
+
numberOfVariants: 4,
|
|
9
|
+
},
|
|
10
|
+
TopScrollFormat: {
|
|
11
|
+
key: 'top_scroll_format',
|
|
12
|
+
numberOfVariants: 2,
|
|
13
|
+
// Variant 0 is separate bubble
|
|
14
|
+
// Variant 1 is inline bubble
|
|
15
|
+
},
|
|
16
|
+
DisplayAdPosition: {
|
|
17
|
+
key: 'display_ad_position',
|
|
18
|
+
numberOfVariants: 3,
|
|
19
|
+
// Variant 0 is display ad in playlinks
|
|
20
|
+
// Variant 1 is display ad above card with highlighted playlink
|
|
21
|
+
// Variant 2 is display ad above card without highlighted playlink
|
|
22
|
+
},
|
|
23
|
+
} as const
|
|
@@ -26,6 +26,9 @@ export const TrackingEvent = Object.freeze({
|
|
|
26
26
|
EditorError: 'ali_editor_error',
|
|
27
27
|
InjectionError: 'ali_injection_error',
|
|
28
28
|
|
|
29
|
+
TopScrollClick: 'ali_top_scroll_click',
|
|
30
|
+
DisplayAdClick: 'ali_display_ad_click',
|
|
31
|
+
|
|
29
32
|
SplitTestView: 'ali_split_test_view',
|
|
30
33
|
SplitTestAction: 'ali_split_test_action'
|
|
31
34
|
})
|
package/src/lib/image.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { imageBaseUrl } from "./constants"
|
|
2
|
+
import type { ImageDimensions } from "./enums/ImageDimensions"
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* NOTE: This is a temporary measure. Images url from the API use a previous format which is to be replaced,
|
|
3
6
|
* but that isn't live yet and requires some extra work. In the meantime we remove part of the URL ourselves.
|
|
@@ -6,3 +9,12 @@ export function removeImageUrlPrefix(url: string | null): string | null {
|
|
|
6
9
|
if (!url) return null
|
|
7
10
|
return url.replace('/src/img', '')
|
|
8
11
|
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a CDN image URL from a UUID.
|
|
15
|
+
*/
|
|
16
|
+
export function imageFromUUID(uuid: string | null, dimensions: (typeof ImageDimensions)[keyof typeof ImageDimensions] | null = null): string {
|
|
17
|
+
if (!uuid?.length) return ''
|
|
18
|
+
|
|
19
|
+
return `${imageBaseUrl}/${uuid}?${dimensions ? `class=${dimensions}&` : ''}optimizer=image`
|
|
20
|
+
}
|
package/src/lib/linkInjection.ts
CHANGED
|
@@ -219,7 +219,9 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
|
|
|
219
219
|
if (replacementIndex === -1) continue
|
|
220
220
|
if (hasBeenReplaced) continue
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
if (!replaceIfSafeInjection(element.innerHTML, injection.title, element, injectionElement, replacementIndex)) {
|
|
223
|
+
failedMessages[injection.key] = 'Injection would have lead to broken HTML.'
|
|
224
|
+
}
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
addLinkInjectionEventListeners(validInjections)
|
|
@@ -267,6 +269,31 @@ function createLinkInjectionElement(injection: LinkInjection): { injectionElemen
|
|
|
267
269
|
return { injectionElement, linkElement }
|
|
268
270
|
}
|
|
269
271
|
|
|
272
|
+
/**
|
|
273
|
+
* In some coses injections lead to broken HTML. The reason for this varies and is hard to figure out. In any case, we
|
|
274
|
+
* should never serve broken HTML.
|
|
275
|
+
* Before replacing the HTML, we apply the the replacement to a dummy element. From here we check if the number of links
|
|
276
|
+
* in the HTML increased by one. If it didn't, something went wrong. What exactly is hard to say, we just know something
|
|
277
|
+
* didn't go as intended and we should not inject.
|
|
278
|
+
*
|
|
279
|
+
* No tests exists for this function because writing tests would require finding a scenario in which things break. If we
|
|
280
|
+
* knew when things break we'd fix it instead.
|
|
281
|
+
*/
|
|
282
|
+
function replaceIfSafeInjection(originalHtml: string, phrase: string, sentenceElement: HTMLElement, injectionElement: HTMLElement, replacementIndex: number): boolean {
|
|
283
|
+
const dummyElement = document.createElement('div')
|
|
284
|
+
dummyElement.innerHTML = originalHtml
|
|
285
|
+
|
|
286
|
+
const startingNumberOfLinks = Array.from(dummyElement.querySelectorAll<HTMLAnchorElement>('a')).filter(a => a.innerText).length
|
|
287
|
+
dummyElement.innerHTML = replaceStartingFrom(originalHtml, phrase, injectionElement.outerHTML, replacementIndex)
|
|
288
|
+
const finalNumberOfLinks = Array.from(dummyElement.querySelectorAll<HTMLAnchorElement>('a')).filter(a => a.innerText).length
|
|
289
|
+
|
|
290
|
+
if (finalNumberOfLinks != startingNumberOfLinks + 1) return false
|
|
291
|
+
|
|
292
|
+
sentenceElement.innerHTML = dummyElement.innerHTML
|
|
293
|
+
|
|
294
|
+
return true
|
|
295
|
+
}
|
|
296
|
+
|
|
270
297
|
/**
|
|
271
298
|
* Add all used CSS variables to a data attribute. This data attribute is then used for selectors that for each
|
|
272
299
|
* individual CSS variable. This is done this way so that CSS variables are only set when they are used.
|
|
@@ -399,7 +426,14 @@ function openLinkPopover(event: MouseEvent, injection: LinkInjection): void {
|
|
|
399
426
|
* Unmount the popover, removing it from the dom
|
|
400
427
|
*/
|
|
401
428
|
function destroyLinkPopover(outro: boolean = true) {
|
|
402
|
-
if (!activePopoverInsertedComponent)
|
|
429
|
+
if (!activePopoverInsertedComponent) {
|
|
430
|
+
// In some cases a popover lingers even if it should have been removed. This happens sometimes during
|
|
431
|
+
// HMR during development. In that case we remove the element straight from the dom.
|
|
432
|
+
// Doing this will prevent the outro animation from playing, but this being a fallback, that's ok.
|
|
433
|
+
document.querySelectorAll<HTMLElement>('[data-playpilot-title-popover]').forEach(element => element.remove())
|
|
434
|
+
|
|
435
|
+
return
|
|
436
|
+
}
|
|
403
437
|
|
|
404
438
|
unmount(activePopoverInsertedComponent, { outro })
|
|
405
439
|
|
package/src/lib/splitTest.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { TrackingEvent } from "./enums/TrackingEvent"
|
|
2
2
|
import { track } from "./tracking"
|
|
3
3
|
|
|
4
|
+
type SplitTest = {
|
|
5
|
+
key: string
|
|
6
|
+
numberOfVariants: number
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* Each split test uses a different split test identifier so a user can be part of one, multiple, or none, tests.
|
|
6
11
|
* The identifier is saved on the window object so that it is consistent across this session.
|
|
7
12
|
*/
|
|
8
|
-
export function getSplitTestIdentifier(key:
|
|
13
|
+
export function getSplitTestIdentifier({ key }: SplitTest): number {
|
|
9
14
|
const windowIdentifier = window.PlayPilotLinkInjections?.split_test_identifiers?.[key]
|
|
10
15
|
if (windowIdentifier) return windowIdentifier
|
|
11
16
|
|
|
@@ -17,24 +22,24 @@ export function getSplitTestIdentifier(key: string): number {
|
|
|
17
22
|
return randomIdentifier
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const isInSplitTest = identifier > 0.5
|
|
25
|
+
export function getSplitTestVariantIndex(test: SplitTest): number {
|
|
26
|
+
const identifier = getSplitTestIdentifier(test)
|
|
27
|
+
|
|
28
|
+
return Math.floor(identifier * test.numberOfVariants)
|
|
29
|
+
}
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
export function isInSplitTestVariant(test: SplitTest, variant = 1): boolean {
|
|
32
|
+
return getSplitTestVariantIndex(test) === variant
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
export function trackSplitTestView(
|
|
31
|
-
const variant =
|
|
35
|
+
export function trackSplitTestView(test: SplitTest): void {
|
|
36
|
+
const variant = getSplitTestVariantIndex(test)
|
|
32
37
|
|
|
33
|
-
track(TrackingEvent.SplitTestView, null, { key, variant })
|
|
38
|
+
track(TrackingEvent.SplitTestView, null, { key: test.key, variant })
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
export function trackSplitTestAction(
|
|
37
|
-
const variant =
|
|
41
|
+
export function trackSplitTestAction(test: SplitTest, action: string): void {
|
|
42
|
+
const variant = getSplitTestVariantIndex(test) ? 1 : 0
|
|
38
43
|
|
|
39
|
-
track(TrackingEvent.SplitTestAction, null, { key, variant, action })
|
|
44
|
+
track(TrackingEvent.SplitTestAction, null, { key: test.key, variant, action })
|
|
40
45
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type Campaign = {
|
|
2
|
+
campaign_format: CampaignFormat
|
|
3
|
+
campaign_type: 'playlist' | 'video' | 'image'
|
|
4
|
+
campaign_platforms: CampaignPlatform[]
|
|
5
|
+
campaign_name: string
|
|
6
|
+
campaign_start: string
|
|
7
|
+
campaign_end: string
|
|
8
|
+
campaign_region: string
|
|
9
|
+
content: CampaignContent
|
|
10
|
+
cta: CampaignCTA
|
|
11
|
+
content_playlist: object
|
|
12
|
+
provider: object | null
|
|
13
|
+
impression_trackers: string[]
|
|
14
|
+
target_title_uuid: string
|
|
15
|
+
target_title_sid: string
|
|
16
|
+
backfill_providers: string[]
|
|
17
|
+
autogenerated: boolean
|
|
18
|
+
enabled: boolean
|
|
19
|
+
hide_imdb_score: boolean
|
|
20
|
+
hide_sponsored_message: boolean
|
|
21
|
+
disclaimer: string | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type CampaignContent = {
|
|
25
|
+
header: string | null
|
|
26
|
+
header_logo: string | null
|
|
27
|
+
header_logo_uuid: string | null
|
|
28
|
+
subheader: string | null
|
|
29
|
+
image: string | null
|
|
30
|
+
image_uuid: string | null
|
|
31
|
+
video: string | null
|
|
32
|
+
format: string | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type CampaignCTA = {
|
|
36
|
+
header: string | null
|
|
37
|
+
subheader: string | null
|
|
38
|
+
image: string | null
|
|
39
|
+
image_uuid: string | null
|
|
40
|
+
url: string | null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type CampaignPlatform = 'web' | 'android_app' | 'ios_app'
|
|
44
|
+
|
|
45
|
+
export type CampaignFormat = 'takeover' | 'horizontal' | 'hero' | 'top_scroll' | 'card'
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { Campaign } from "./campaign"
|
|
2
|
+
import type { LinkInjection } from "./injection"
|
|
3
|
+
|
|
1
4
|
export type ScriptConfig = {
|
|
2
5
|
token: string
|
|
3
6
|
editorial_token?: string
|
|
@@ -10,4 +13,6 @@ export type ScriptConfig = {
|
|
|
10
13
|
last_successful_fetch?: LinkInjectionResponse | null
|
|
11
14
|
tracked_events?: { event: string, payload: Record<string, any> }[]
|
|
12
15
|
split_test_identifiers?: Record<string, number>
|
|
16
|
+
evaluated_link_injections?: LinkInjection[]
|
|
17
|
+
ads?: Campaign[]
|
|
13
18
|
}
|
package/src/main.ts
CHANGED
|
@@ -15,6 +15,8 @@ window.PlayPilotLinkInjections = {
|
|
|
15
15
|
last_successful_fetch: null,
|
|
16
16
|
tracked_events: [],
|
|
17
17
|
split_test_identifiers: {},
|
|
18
|
+
evaluated_link_injections: [],
|
|
19
|
+
ads: [],
|
|
18
20
|
app: null,
|
|
19
21
|
|
|
20
22
|
initialize(config = { token: '', selector: '', after_article_selector: '', after_article_insert_position: '', language: null, organization_sid: null, domain_sid: null, editorial_token: '' }): void {
|
|
@@ -79,6 +81,10 @@ window.PlayPilotLinkInjections = {
|
|
|
79
81
|
console.log(getPageText(elements))
|
|
80
82
|
console.groupEnd()
|
|
81
83
|
|
|
84
|
+
console.groupCollapsed('Evaluated injections')
|
|
85
|
+
console.log(this.evaluated_link_injections)
|
|
86
|
+
console.groupEnd()
|
|
87
|
+
|
|
82
88
|
console.groupCollapsed('Tracked events')
|
|
83
89
|
console.log(this.tracked_events)
|
|
84
90
|
console.groupEnd()
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { browser } from '$app/environment'
|
|
3
3
|
import { page } from '$app/state'
|
|
4
|
+
import { SplitTest } from '$lib/enums/SplitTest'
|
|
5
|
+
import { getSplitTestVariantIndex } from '$lib/splitTest'
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* !! NOTE: This layout file is for development purposes only and will not be compiled with the final script.
|
|
@@ -75,6 +77,8 @@
|
|
|
75
77
|
|
|
76
78
|
{#if browser}
|
|
77
79
|
{@render children()}
|
|
80
|
+
|
|
81
|
+
Viewing with split test index: {getSplitTestVariantIndex(SplitTest.MultipleVariants)}
|
|
78
82
|
{/if}
|
|
79
83
|
{/key}
|
|
80
84
|
{/if}
|
package/src/routes/+page.svelte
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
9
9
|
import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/auth'
|
|
10
10
|
import { trackSplitTestView } from '$lib/splitTest'
|
|
11
|
+
import { SplitTest } from '$lib/enums/SplitTest'
|
|
11
12
|
import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
|
|
12
13
|
import Editor from './components/Editorial/Editor.svelte'
|
|
13
14
|
import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
|
|
14
15
|
import Alert from './components/Editorial/Alert.svelte'
|
|
15
16
|
import TrackingPixels from './components/TrackingPixels.svelte'
|
|
17
|
+
import { fetchAds } from '$lib/ads'
|
|
16
18
|
|
|
17
19
|
let parentElement: HTMLElement | null = $state(null)
|
|
18
20
|
let elements: HTMLElement[] = $state([])
|
|
@@ -42,7 +44,9 @@
|
|
|
42
44
|
await initialize()
|
|
43
45
|
track(TrackingEvent.ArticlePageView)
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
if (aiInjections.length || manualInjections.length) window.PlayPilotLinkInjections.ads = await fetchAds()
|
|
48
|
+
|
|
49
|
+
trackSplitTestView(SplitTest.MultipleVariants)
|
|
46
50
|
})()
|
|
47
51
|
|
|
48
52
|
return () => clearLinkInjections()
|
|
@@ -120,7 +124,14 @@
|
|
|
120
124
|
// Get filtered injections as they are shown on the page.
|
|
121
125
|
// Only update state if it they are different from current injections.
|
|
122
126
|
const filteredInjections = injectLinksInDocument(elements, injections)
|
|
123
|
-
if (JSON.stringify(filteredInjections) !== JSON.stringify(linkInjections))
|
|
127
|
+
if (JSON.stringify(filteredInjections) !== JSON.stringify(linkInjections)) {
|
|
128
|
+
linkInjections = filteredInjections
|
|
129
|
+
|
|
130
|
+
// This is added to the window both for debugging purposes and for the benchmarking tool.
|
|
131
|
+
// Just to have some way to evaluate injections outside of editorial mode.
|
|
132
|
+
// This shouldn't be used anywhere else.
|
|
133
|
+
window.PlayPilotLinkInjections.evaluated_link_injections = filteredInjections
|
|
134
|
+
}
|
|
124
135
|
|
|
125
136
|
const successfulInjections = filteredInjections.filter(i => !i.failed)
|
|
126
137
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { SplitTest } from '$lib/enums/SplitTest'
|
|
3
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
4
|
+
import { trackSplitTestAction, trackSplitTestView } from '$lib/splitTest'
|
|
5
|
+
import { track } from '$lib/tracking'
|
|
6
|
+
import type { Campaign } from '$lib/types/campaign'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
campaign: Campaign
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { campaign }: Props = $props()
|
|
13
|
+
|
|
14
|
+
const { disclaimer, content, cta } = $derived(campaign)
|
|
15
|
+
const { header, subheader, header_logo: logo, image } = $derived(content)
|
|
16
|
+
const { header: buttonLabel, url: href } = $derived(cta)
|
|
17
|
+
|
|
18
|
+
trackSplitTestView(SplitTest.DisplayAdPosition)
|
|
19
|
+
|
|
20
|
+
function onclick(): void {
|
|
21
|
+
track(TrackingEvent.DisplayAdClick, null, { campaign_name: campaign.campaign_name })
|
|
22
|
+
trackSplitTestAction(SplitTest.DisplayAdPosition, 'click')
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<a {href} target="_blank" class="display" rel="sponsored" {onclick}>
|
|
27
|
+
{#if disclaimer}
|
|
28
|
+
<div class="disclaimer">
|
|
29
|
+
{disclaimer}
|
|
30
|
+
</div>
|
|
31
|
+
{/if}
|
|
32
|
+
|
|
33
|
+
{#if logo || header || subheader || buttonLabel}
|
|
34
|
+
<div class="cta">
|
|
35
|
+
{#if logo}
|
|
36
|
+
<img class="logo" src={logo} alt="" height="40" width="40" />
|
|
37
|
+
{/if}
|
|
38
|
+
|
|
39
|
+
<div>
|
|
40
|
+
{#if header}
|
|
41
|
+
<span class="name">{header}</span>
|
|
42
|
+
{/if}
|
|
43
|
+
|
|
44
|
+
{#if subheader}
|
|
45
|
+
<div class="subheader">{subheader}</div>
|
|
46
|
+
{/if}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{#if buttonLabel}
|
|
50
|
+
<div class="action">{buttonLabel}</div>
|
|
51
|
+
{/if}
|
|
52
|
+
</div>
|
|
53
|
+
{/if}
|
|
54
|
+
|
|
55
|
+
{#if image}
|
|
56
|
+
<img class="background" src={image} alt="" />
|
|
57
|
+
{/if}
|
|
58
|
+
</a>
|
|
59
|
+
|
|
60
|
+
<style lang="scss">
|
|
61
|
+
.display {
|
|
62
|
+
display: block;
|
|
63
|
+
position: relative;
|
|
64
|
+
border-radius: var(--playpilot-display-ad-border-radius, margin(0.5));
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
background: black;
|
|
67
|
+
aspect-ratio: 16 / 9;
|
|
68
|
+
color: white !important;
|
|
69
|
+
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.35);
|
|
70
|
+
font-family: var(--playpilot-detail-font-family, var(--playpilot-font-family));
|
|
71
|
+
line-height: 1.2;
|
|
72
|
+
font-style: normal !important;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.disclaimer {
|
|
76
|
+
position: absolute;
|
|
77
|
+
top: 0;
|
|
78
|
+
left: 0;
|
|
79
|
+
padding: margin(0.5);
|
|
80
|
+
font-size: margin(0.75);
|
|
81
|
+
text-decoration: underline;
|
|
82
|
+
|
|
83
|
+
@media (min-width: 600px) {
|
|
84
|
+
padding: margin(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.cta {
|
|
89
|
+
z-index: 1;
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: margin(0.5);
|
|
93
|
+
position: absolute;
|
|
94
|
+
right: 0;
|
|
95
|
+
bottom: 0;
|
|
96
|
+
left: 0;
|
|
97
|
+
padding: margin(0.5);
|
|
98
|
+
background: linear-gradient(to right, rgba(110, 110, 110, 1), rgba(85, 85, 85, 0.3));
|
|
99
|
+
font-size: margin(0.875);
|
|
100
|
+
text-decoration: none;
|
|
101
|
+
|
|
102
|
+
@media (min-width: 600px) {
|
|
103
|
+
padding: margin(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.logo {
|
|
108
|
+
border-radius: var(--playpilot-display-ad-border-radius, margin(0.5));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.name {
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.subheader {
|
|
116
|
+
font-size: margin(0.75);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.action {
|
|
120
|
+
margin-left: auto;
|
|
121
|
+
padding: margin(0.5) margin(0.75);
|
|
122
|
+
background: var(--playpilot-display-ad-action-background, white);
|
|
123
|
+
border-radius: var(--playpilot-display-action-border-radius, margin(2));
|
|
124
|
+
transition: transform 50ms;
|
|
125
|
+
text-shadow: none;
|
|
126
|
+
font-weight: var(--playpilot-display-action-font-weight, 500);
|
|
127
|
+
color: var(--playpilot-display-action-text-color, black);
|
|
128
|
+
white-space: nowrap;
|
|
129
|
+
|
|
130
|
+
&:hover {
|
|
131
|
+
transform: scale(1.05);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.background {
|
|
136
|
+
display: block;
|
|
137
|
+
width: 100%;
|
|
138
|
+
height: auto;
|
|
139
|
+
}
|
|
140
|
+
</style>
|