@playpilot/tpi 8.10.2 → 8.10.4-beta.1
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/.env +1 -1
- package/dist/editorial.mount.js +9 -9
- package/dist/link-injections.js +2 -2
- package/dist/mount.js +6 -6
- package/package.json +1 -1
- package/src/lib/api/externalPages.ts +4 -0
- package/src/lib/enums/TrackingEvent.ts +4 -4
- package/src/lib/fakeData.ts +32 -44
- package/src/lib/injection.ts +9 -10
- package/src/lib/meta.ts +2 -1
- package/src/lib/modal.ts +1 -3
- package/src/lib/popover.ts +12 -13
- package/src/lib/types/injection.d.ts +0 -5
- package/src/routes/+layout.svelte +6 -1
- package/src/routes/+page.svelte +2 -0
- package/src/routes/components/Editorial/Editor.svelte +3 -2
- package/src/routes/components/Editorial/EditorItem.svelte +9 -23
- package/src/routes/components/Editorial/ManualInjection.svelte +0 -1
- package/src/routes/components/ListTitle.svelte +6 -27
- package/src/routes/components/Participant.svelte +6 -18
- package/src/routes/components/Playlinks/Playlinks.svelte +1 -2
- package/src/routes/components/Title.svelte +1 -1
- package/src/routes/components/{InjectionPopover.svelte → TitlePopover.svelte} +9 -26
- package/src/routes/elements/+page.svelte +3 -3
- package/src/tests/helpers.js +0 -1
- package/src/tests/lib/injection.test.js +3 -44
- package/src/tests/lib/meta.test.js +11 -2
- package/src/tests/lib/popover.test.js +7 -7
- package/src/tests/routes/components/Editorial/EditorItem.test.js +0 -10
- package/src/tests/routes/components/Editorial/ManualInjection.test.js +0 -4
- package/src/tests/routes/components/Editorial/PlaylinkTypeSelect.test.js +0 -2
- package/src/tests/routes/components/ListTitle.test.js +0 -7
- package/src/tests/routes/components/Participant.test.js +0 -7
- package/src/tests/routes/components/Playlinks/AfterArticlePlaylinks.test.js +0 -4
- package/src/tests/routes/components/Playlinks/Playlinks.test.js +1 -1
- package/src/tests/routes/components/TitlePopover.test.js +78 -0
- package/src/tests/routes/components/InjectionPopover.test.js +0 -117
package/package.json
CHANGED
|
@@ -26,6 +26,8 @@ export async function fetchLinkInjections(
|
|
|
26
26
|
const apiUrl = `/external-pages/?api-token=${apiToken}&include_title_details=true${isEditorialMode ? '&editorial_mode_enabled=true' : ''}&language=${language}`
|
|
27
27
|
let response: LinkInjectionResponse
|
|
28
28
|
|
|
29
|
+
console.log('page text in fetchLinkInjections', { pageText })
|
|
30
|
+
|
|
29
31
|
// We use separate requests when running the AI or setting the editor session vs when only getting the results.
|
|
30
32
|
// For regular requests we use a GET endpoint, but when saving data we POST to the same url.
|
|
31
33
|
if (method === 'POST') {
|
|
@@ -70,6 +72,8 @@ export async function pollLinkInjections(
|
|
|
70
72
|
// This is mostly handy during HMR, but also during navigation changes
|
|
71
73
|
if (pollTimeout) clearTimeout(pollTimeout)
|
|
72
74
|
|
|
75
|
+
console.log('page text in pollLinkInjections', { pageText })
|
|
76
|
+
|
|
73
77
|
const poll = async (resolve: Function, reject: Function): Promise<void> => {
|
|
74
78
|
let response
|
|
75
79
|
try {
|
|
@@ -16,10 +16,10 @@ export const TrackingEvent = {
|
|
|
16
16
|
ParticipantModalClose: 'ali_participant_modal_close',
|
|
17
17
|
|
|
18
18
|
// Popover
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
TitlePopoverView: 'ali_title_popover_view',
|
|
20
|
+
TitlePopoverClose: 'ali_title_popover_close',
|
|
21
|
+
TitlePopoverSaveClick: 'ali_title_popover_save_click',
|
|
22
|
+
TitlePopoverPlaylinkClick: 'ali_title_popover_playlink_click',
|
|
23
23
|
|
|
24
24
|
// Rails
|
|
25
25
|
SimilarTitleClick: 'ali_similar_title_click',
|
package/src/lib/fakeData.ts
CHANGED
|
@@ -43,6 +43,38 @@ export const title: TitleData = {
|
|
|
43
43
|
embeddable_url: null,
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export const linkInjections: LinkInjection[] = [{
|
|
47
|
+
sid: '1',
|
|
48
|
+
title: 'Quan',
|
|
49
|
+
sentence: 'In an interview with Epire Magazine, Quan reveals he quested starring in Love Hurts',
|
|
50
|
+
playpilot_url: 'https://playpilot.com/movie/example/',
|
|
51
|
+
key: 'some-key-1',
|
|
52
|
+
title_details: title,
|
|
53
|
+
}, {
|
|
54
|
+
sid: '2',
|
|
55
|
+
title: 'The Long Kiss Goodnight',
|
|
56
|
+
sentence: 'The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody',
|
|
57
|
+
playpilot_url: 'https://playpilot.com/movie/example-2/',
|
|
58
|
+
key: 'some-key-2',
|
|
59
|
+
after_article: false,
|
|
60
|
+
title_details: title,
|
|
61
|
+
}, {
|
|
62
|
+
sid: '3',
|
|
63
|
+
title: 'Nobody',
|
|
64
|
+
sentence: 'The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody',
|
|
65
|
+
playpilot_url: 'https://playpilot.com/movie/example-3/',
|
|
66
|
+
key: 'some-key-3',
|
|
67
|
+
after_article: true,
|
|
68
|
+
title_details: title,
|
|
69
|
+
manual: false,
|
|
70
|
+
}, {
|
|
71
|
+
sid: '4',
|
|
72
|
+
title: 'The Wheel of Time',
|
|
73
|
+
sentence: '(The more I think about the Bene Gesserit the more I see how much they influenced not just the Jedi in Star Wars but also the Aes Sedai in The Wheel of Time and even the sorceresses in The Witcher books, though Herbert\'s order is the most ominous and terrifying of them all)',
|
|
74
|
+
playpilot_url: 'https://playpilot.com/movie/example-4/',
|
|
75
|
+
key: 'some-key-4',
|
|
76
|
+
}]
|
|
77
|
+
|
|
46
78
|
export const participants: ParticipantData[] = [
|
|
47
79
|
{
|
|
48
80
|
sid: 'pr5C5W',
|
|
@@ -112,50 +144,6 @@ export const participants: ParticipantData[] = [
|
|
|
112
144
|
},
|
|
113
145
|
]
|
|
114
146
|
|
|
115
|
-
export const linkInjections: LinkInjection[] = [{
|
|
116
|
-
sid: '1',
|
|
117
|
-
title: 'Quan',
|
|
118
|
-
sentence: 'In an interview with Epire Magazine, Quan reveals he quested starring in Love Hurts',
|
|
119
|
-
playpilot_url: 'https://playpilot.com/movie/example/',
|
|
120
|
-
key: 'some-key-1',
|
|
121
|
-
title_details: title,
|
|
122
|
-
type: 'title',
|
|
123
|
-
}, {
|
|
124
|
-
sid: '2',
|
|
125
|
-
title: 'The Long Kiss Goodnight',
|
|
126
|
-
sentence: 'The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody',
|
|
127
|
-
playpilot_url: 'https://playpilot.com/movie/example-2/',
|
|
128
|
-
key: 'some-key-2',
|
|
129
|
-
after_article: false,
|
|
130
|
-
title_details: title,
|
|
131
|
-
type: 'title',
|
|
132
|
-
}, {
|
|
133
|
-
sid: '3',
|
|
134
|
-
title: 'Nobody',
|
|
135
|
-
sentence: 'The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody',
|
|
136
|
-
playpilot_url: 'https://playpilot.com/movie/example-3/',
|
|
137
|
-
key: 'some-key-3',
|
|
138
|
-
after_article: true,
|
|
139
|
-
title_details: title,
|
|
140
|
-
manual: false,
|
|
141
|
-
type: 'title',
|
|
142
|
-
}, {
|
|
143
|
-
sid: '4',
|
|
144
|
-
title: 'The Wheel of Time',
|
|
145
|
-
sentence: '(The more I think about the Bene Gesserit the more I see how much they influenced not just the Jedi in Star Wars but also the Aes Sedai in The Wheel of Time and even the sorceresses in The Witcher books, though Herbert\'s order is the most ominous and terrifying of them all)',
|
|
146
|
-
playpilot_url: 'https://playpilot.com/movie/example-4/',
|
|
147
|
-
key: 'some-key-4',
|
|
148
|
-
type: 'title',
|
|
149
|
-
}, {
|
|
150
|
-
sid: '5',
|
|
151
|
-
title: 'Jack Black',
|
|
152
|
-
sentence: 'Jason Momoa (”Aquaman”), Jack Black (”Nacho Libre”) och Jennifer Coolidge (”The White Lotus”) medverkar i den Jared Hess-regisserade (”Napolen Dynamite”) filmen.',
|
|
153
|
-
playpilot_url: 'https://playpilot.com/name/pr875J-jack-black/',
|
|
154
|
-
key: 'some-key-5',
|
|
155
|
-
participant_details: participants[2],
|
|
156
|
-
type: 'participant',
|
|
157
|
-
}]
|
|
158
|
-
|
|
159
147
|
export const campaign: Campaign = {
|
|
160
148
|
campaign_format: 'card',
|
|
161
149
|
campaign_type: 'image',
|
package/src/lib/injection.ts
CHANGED
|
@@ -168,7 +168,7 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
|
|
|
168
168
|
// The function itself will decide whether or not it should actually insert the component based on the config.
|
|
169
169
|
if (document.querySelector(keySelector)) insertInTextDisclaimer(elements)
|
|
170
170
|
|
|
171
|
-
return mergedInjections.filter(
|
|
171
|
+
return mergedInjections.filter(i => i.title_details).map((injection, index) => {
|
|
172
172
|
const hasManualEquivalent = !injection.manual && isAvailableAsManualInjection(injection, index, mergedInjections)
|
|
173
173
|
const duplicate = injection.duplicate ?? hasManualEquivalent
|
|
174
174
|
|
|
@@ -376,11 +376,11 @@ export function separateLinkInjectionTypes(injections: LinkInjection[]): LinkInj
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
export function isValidInjection(injection: LinkInjection): boolean {
|
|
379
|
-
return !injection.inactive && !injection.removed && !injection.duplicate &&
|
|
379
|
+
return !injection.inactive && !injection.removed && !injection.duplicate && !!injection.title_details && isValidPlaylinkType(injection)
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
/**
|
|
383
|
-
* An injection can be of various playlink types, when all are false equivalent, the link is not valid.
|
|
383
|
+
* An injection can have be of various playlink types, when all are false equivalent, the link is not valid.
|
|
384
384
|
* It should be treated similar to an inactive playlink in this case.
|
|
385
385
|
*/
|
|
386
386
|
export function isValidPlaylinkType(injection: LinkInjection): boolean {
|
|
@@ -388,10 +388,16 @@ export function isValidPlaylinkType(injection: LinkInjection): boolean {
|
|
|
388
388
|
return !!injection.after_article
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Filter links for in-text injections, removing after article, inactive, removed, duplicate, and items without title_details
|
|
393
|
+
*/
|
|
391
394
|
export function filterInvalidInTextInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
392
395
|
return filterRemovedAndInactiveInjections(injections).filter(i => i.in_text !== false && isValidInjection(i))
|
|
393
396
|
}
|
|
394
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Filter links for after article injections, removing in-text only, inactive, removed, duplicate, and items without title_details
|
|
400
|
+
*/
|
|
395
401
|
export function filterInvalidAfterArticleInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
396
402
|
return filterRemovedAndInactiveInjections(injections).filter(i => i.after_article === true && isValidInjection(i))
|
|
397
403
|
}
|
|
@@ -436,13 +442,6 @@ export function isEquivalentInjection(injection1: LinkInjection, injection2: Lin
|
|
|
436
442
|
return injection1.title === injection2.title && cleanPhrase(injection1.sentence) === cleanPhrase(injection2.sentence)
|
|
437
443
|
}
|
|
438
444
|
|
|
439
|
-
export function hasValidTypeData(injection: LinkInjection): boolean {
|
|
440
|
-
if (injection.type === 'title' && !!injection.title_details) return true
|
|
441
|
-
if (injection.type === 'participant' && !!injection.participant_details) return true
|
|
442
|
-
|
|
443
|
-
return false
|
|
444
|
-
}
|
|
445
|
-
|
|
446
445
|
export function getPlayPilotWrapperElement(): Element {
|
|
447
446
|
return document.querySelector('[data-playpilot-link-injections]') || document.body
|
|
448
447
|
}
|
package/src/lib/meta.ts
CHANGED
|
@@ -68,7 +68,8 @@ export function getDatetime(datetime: string): string | null {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export function getSchemaJson(): Record<string, any> {
|
|
71
|
-
const
|
|
71
|
+
const schemaElements = Array.from(document.querySelectorAll('[type="application/ld+json"]'))
|
|
72
|
+
const schemaElement = schemaElements.find(element => element.textContent.includes('dateModified') || element.textContent.includes('datePublished'))
|
|
72
73
|
|
|
73
74
|
if (!schemaElement) return {}
|
|
74
75
|
|
package/src/lib/modal.ts
CHANGED
|
@@ -133,9 +133,7 @@ export function openModalForInjectedLink(event: MouseEvent, injections: LinkInje
|
|
|
133
133
|
event.preventDefault()
|
|
134
134
|
|
|
135
135
|
playFallbackViewTransition(() => {
|
|
136
|
-
const data = injection.title_details || injection.participant_details
|
|
137
|
-
|
|
138
136
|
destroyLinkPopover(false)
|
|
139
|
-
openModal({ event, injection, data
|
|
137
|
+
openModal({ event, injection, data: injection.title_details })
|
|
140
138
|
}, !prefersReducedMotion.current && window.innerWidth >= mobileBreakpoint && !window.matchMedia('(pointer: coarse)').matches)
|
|
141
139
|
}
|
package/src/lib/popover.ts
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import { mount, unmount } from 'svelte'
|
|
2
|
+
import TitlePopover from '../routes/components/TitlePopover.svelte'
|
|
2
3
|
import { getPlayPilotWrapperElement, keyDataAttribute, keySelector } from './injection'
|
|
3
4
|
import type { LinkInjection } from './types/injection'
|
|
4
|
-
import InjectionPopover from '../routes/components/InjectionPopover.svelte'
|
|
5
5
|
|
|
6
6
|
export let currentlyHoveredInjection: EventTarget | null = null
|
|
7
7
|
export let activePopoverInsertedComponent: object | null = null
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* When a link is hovered it is shown as a popover. The component is mounted when a mouse enters the link,
|
|
11
|
+
* and removed when clicked or on mouseleave.
|
|
12
|
+
*/
|
|
9
13
|
export function openPopoverForInjectedLink(event: MouseEvent, injection: LinkInjection): void {
|
|
14
|
+
// Skip touch devices
|
|
10
15
|
if (window.matchMedia('(pointer: coarse)').matches) return
|
|
11
16
|
if (activePopoverInsertedComponent) destroyLinkPopover()
|
|
12
17
|
|
|
13
|
-
const
|
|
14
|
-
currentlyHoveredInjection =
|
|
18
|
+
const target = event.currentTarget as Element
|
|
19
|
+
currentlyHoveredInjection = target
|
|
15
20
|
|
|
16
21
|
// Only show if the link is hovered for more than 100ms. This is to prevent the popover from showing
|
|
17
22
|
// when a user just happens to mouseover as they are moving their mouse about.
|
|
18
23
|
setTimeout(() => {
|
|
19
|
-
if (currentlyHoveredInjection !==
|
|
24
|
+
if (currentlyHoveredInjection !== target) return // User is no longer hovering this link
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (injection.type === 'title') {
|
|
24
|
-
activePopoverInsertedComponent = mount(InjectionPopover, { target, props: { event, type: 'title', data: injection.title_details! } })
|
|
25
|
-
} else if (injection.type === 'participant') {
|
|
26
|
-
activePopoverInsertedComponent = mount(InjectionPopover, { target, props: { event, type: 'participant', data: injection.participant_details! } })
|
|
27
|
-
}
|
|
26
|
+
activePopoverInsertedComponent = mount(TitlePopover, { target: getPlayPilotWrapperElement(), props: { event, title: injection.title_details! } })
|
|
28
27
|
}, 100)
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -45,7 +44,7 @@ export async function destroyLinkPopover(outro: boolean = true): Promise<void> {
|
|
|
45
44
|
// In that case we remove the element straight from the dom.
|
|
46
45
|
// Doing this will prevent the outro animation from playing, but this being a fallback, that's ok.
|
|
47
46
|
// TODO: Find the actual cause of this bug.
|
|
48
|
-
document.querySelectorAll<HTMLElement>('[data-playpilot-
|
|
47
|
+
document.querySelectorAll<HTMLElement>('[data-playpilot-title-popover]').forEach(element => element.remove())
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
export function destroyLinkPopoverOnMouseleave(event: MouseEvent): void {
|
|
@@ -54,7 +53,7 @@ export function destroyLinkPopoverOnMouseleave(event: MouseEvent): void {
|
|
|
54
53
|
const target = event.target as Element
|
|
55
54
|
|
|
56
55
|
// Mousemove is inside of popover or link that popover
|
|
57
|
-
if (target.hasAttribute('data-playpilot-
|
|
56
|
+
if (target.hasAttribute('data-playpilot-title-popover') || target.closest('[data-playpilot-title-popover]') ||
|
|
58
57
|
target.hasAttribute(keyDataAttribute) || target.closest(keySelector)) return
|
|
59
58
|
|
|
60
59
|
destroyLinkPopover()
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type { ParticipantData } from './participant'
|
|
2
1
|
import type { TitleData } from './title'
|
|
3
2
|
|
|
4
|
-
export type LinkInjectionDataType = 'title' | 'participant'
|
|
5
|
-
|
|
6
3
|
export type LinkInjection = {
|
|
7
4
|
sid: string
|
|
8
5
|
title: string
|
|
@@ -10,7 +7,6 @@ export type LinkInjection = {
|
|
|
10
7
|
playpilot_url: string
|
|
11
8
|
key: string
|
|
12
9
|
title_details?: TitleData
|
|
13
|
-
participant_details?: ParticipantData
|
|
14
10
|
phrase_before?: string | null
|
|
15
11
|
phrase_after?: string | null
|
|
16
12
|
inactive?: boolean
|
|
@@ -23,7 +19,6 @@ export type LinkInjection = {
|
|
|
23
19
|
removed?: boolean
|
|
24
20
|
duplicate?: boolean
|
|
25
21
|
matchingElement?: null | Element
|
|
26
|
-
type: LinkInjectionDataType
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
export type LinkInjectionRanges = Record<string, { elementIndex: number, from: number, to: number }>
|
|
@@ -128,9 +128,14 @@
|
|
|
128
128
|
line-height: 1.8;
|
|
129
129
|
|
|
130
130
|
nav {
|
|
131
|
-
display:
|
|
131
|
+
display: grid;
|
|
132
|
+
grid-template-columns: 1fr 1fr;
|
|
132
133
|
gap: margin(1);
|
|
133
134
|
|
|
135
|
+
@include desktop {
|
|
136
|
+
display: flex;
|
|
137
|
+
}
|
|
138
|
+
|
|
134
139
|
a {
|
|
135
140
|
padding: margin(0.25) margin(0.75);
|
|
136
141
|
border: 1px solid currentColor;
|
package/src/routes/+page.svelte
CHANGED
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
const linkInjectionsString = $derived(JSON.stringify(linkInjections))
|
|
59
59
|
const showControls = $derived(!aiRunning && allowEditing)
|
|
60
60
|
const hasChanged = $derived(initialStateString && initialStateString !== linkInjectionsString)
|
|
61
|
-
|
|
61
|
+
// Filter out injections without title_details, injections that are removed, duplicate, or are AI injections that failed to inject
|
|
62
|
+
const filteredInjections = $derived(linkInjections.filter((i) => i.title_details && !i.removed && !i.duplicate && (i.manual || (!i.manual && !i.failed))))
|
|
62
63
|
const sortedInjections = $derived(sortInjections(filteredInjections))
|
|
63
64
|
const initialAiRunning = $derived(!loading && untrack(() => aiStatus.aiRunning))
|
|
64
65
|
|
|
@@ -74,7 +75,7 @@
|
|
|
74
75
|
function sortInjections(injections: LinkInjection[]): LinkInjection[] {
|
|
75
76
|
return injections.sort((a, b) => {
|
|
76
77
|
if (a.failed !== b.failed) return a.failed ? 1 : -1
|
|
77
|
-
return
|
|
78
|
+
return a.title_details!.title.localeCompare(b.title_details!.title)
|
|
78
79
|
})
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { track } from '$lib/tracking'
|
|
12
12
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
13
13
|
import type { LinkInjection } from '$lib/types/injection'
|
|
14
|
+
import type { TitleData } from '$lib/types/title'
|
|
14
15
|
import { cleanPhrase, truncateAroundPhrase } from '$lib/text'
|
|
15
16
|
import { isValidPlaylinkType } from '$lib/injection'
|
|
16
17
|
import { getLinkInjectionElements, getLinkInjectionsParentElement } from '$lib/injectionElements'
|
|
@@ -26,8 +27,9 @@
|
|
|
26
27
|
|
|
27
28
|
const { linkInjection = $bindable(), onremove = () => null, onhighlight = () => null }: Props = $props()
|
|
28
29
|
|
|
29
|
-
const { key,
|
|
30
|
+
const { key, sentence, title_details, failed, failed_message, inactive } = $derived(linkInjection || {})
|
|
30
31
|
|
|
32
|
+
const title: TitleData = $derived(title_details!)
|
|
31
33
|
const truncatedSentence = $derived(truncateAroundPhrase(linkInjection.sentence, linkInjection.title, 60))
|
|
32
34
|
|
|
33
35
|
let expanded = $state(false)
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
let element: HTMLElement | null = $state(null)
|
|
37
39
|
|
|
38
40
|
onMount(() => {
|
|
39
|
-
if (failed) track(TrackingEvent.InjectionFailed,
|
|
41
|
+
if (failed) track(TrackingEvent.InjectionFailed, title, { phrase: linkInjection.title, sentence})
|
|
40
42
|
})
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -111,14 +113,10 @@
|
|
|
111
113
|
bind:this={element}
|
|
112
114
|
out:slide|global={{ duration: 200 }}>
|
|
113
115
|
<div class="header">
|
|
114
|
-
{
|
|
115
|
-
<img class="poster" src={removeImageUrlPrefix(title_details!.standing_poster)} alt="" width="32" height="48" onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl} />
|
|
116
|
-
{:else}
|
|
117
|
-
<div class="placeholder-image"></div>
|
|
118
|
-
{/if}
|
|
116
|
+
<img class="poster" src={removeImageUrlPrefix(title.standing_poster)} alt="" width="32" height="48" onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl} />
|
|
119
117
|
|
|
120
118
|
<div class="info">
|
|
121
|
-
<div class="title">{
|
|
119
|
+
<div class="title">{title.title}</div>
|
|
122
120
|
|
|
123
121
|
<div class="sentence">
|
|
124
122
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
@@ -145,13 +143,9 @@
|
|
|
145
143
|
<Alert>{failed_message}</Alert>
|
|
146
144
|
{:else}
|
|
147
145
|
<div class="actions">
|
|
148
|
-
{
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
</button>
|
|
152
|
-
{:else}
|
|
153
|
-
<div></div>
|
|
154
|
-
{/if}
|
|
146
|
+
<button class="expand" onclick={() => expanded = !expanded} aria-label="Expand" aria-expanded={expanded}>
|
|
147
|
+
<IconChevron {expanded} />
|
|
148
|
+
</button>
|
|
155
149
|
|
|
156
150
|
{#if !isValidPlaylinkType(linkInjection)}
|
|
157
151
|
<div class="warning" transition:fade={{ duration: 100 }} aria-label="Invalid playlink settings">
|
|
@@ -216,14 +210,6 @@
|
|
|
216
210
|
}
|
|
217
211
|
}
|
|
218
212
|
|
|
219
|
-
.placeholder-image {
|
|
220
|
-
flex: 0 0 margin(2);
|
|
221
|
-
width: margin(2);
|
|
222
|
-
height: margin(2);
|
|
223
|
-
border-radius: 50%;
|
|
224
|
-
background: theme(content);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
213
|
.title {
|
|
228
214
|
font-size: margin(0.875);
|
|
229
215
|
word-break: break-word;
|
|
@@ -8,16 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
10
|
title: TitleData
|
|
11
|
-
compact?: boolean
|
|
12
11
|
onclick?: (event: MouseEvent) => void
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
const { title,
|
|
14
|
+
const { title, onclick = () => null }: Props = $props()
|
|
16
15
|
|
|
17
16
|
const noAffiliate = !!window.PlayPilotLinkInjections?.no_affiliate
|
|
18
17
|
</script>
|
|
19
18
|
|
|
20
|
-
<button class="title"
|
|
19
|
+
<button class="title" {onclick} data-testid="title">
|
|
21
20
|
<div class="poster">
|
|
22
21
|
<TitlePoster {title} width={30} height={43} lazy />
|
|
23
22
|
</div>
|
|
@@ -39,14 +38,12 @@
|
|
|
39
38
|
{/if}
|
|
40
39
|
</div>
|
|
41
40
|
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</div>
|
|
46
|
-
{/if}
|
|
41
|
+
<div class="description" class:large={noAffiliate}>
|
|
42
|
+
{title.description}
|
|
43
|
+
</div>
|
|
47
44
|
|
|
48
45
|
{#if !noAffiliate}
|
|
49
|
-
<PlaylinksCompact playlinks={title.providers}
|
|
46
|
+
<PlaylinksCompact playlinks={title.providers} />
|
|
50
47
|
{/if}
|
|
51
48
|
</div>
|
|
52
49
|
|
|
@@ -87,10 +84,6 @@
|
|
|
87
84
|
border-radius: theme(detail-image-border-radius, border-radius);
|
|
88
85
|
background: theme(detail-image-background, content);
|
|
89
86
|
overflow: hidden;
|
|
90
|
-
|
|
91
|
-
.compact & {
|
|
92
|
-
width: margin(3.5);
|
|
93
|
-
}
|
|
94
87
|
}
|
|
95
88
|
|
|
96
89
|
.content {
|
|
@@ -104,17 +97,11 @@
|
|
|
104
97
|
@include desktop() {
|
|
105
98
|
padding-right: margin(1);
|
|
106
99
|
}
|
|
107
|
-
|
|
108
|
-
.compact & {
|
|
109
|
-
padding-left: margin(0.75);
|
|
110
|
-
padding-right: 0;
|
|
111
|
-
}
|
|
112
100
|
}
|
|
113
101
|
|
|
114
102
|
.heading {
|
|
115
103
|
color: theme(list-item-title-text-color, text-color);
|
|
116
104
|
font-weight: theme(list-item-title-font-weight, font-bold);
|
|
117
|
-
line-height: 1.2;
|
|
118
105
|
}
|
|
119
106
|
|
|
120
107
|
.meta {
|
|
@@ -129,10 +116,6 @@
|
|
|
129
116
|
> div {
|
|
130
117
|
text-transform: capitalize;
|
|
131
118
|
}
|
|
132
|
-
|
|
133
|
-
.compact & {
|
|
134
|
-
margin: margin(0.125) 0 margin(0.25) 0;
|
|
135
|
-
}
|
|
136
119
|
}
|
|
137
120
|
|
|
138
121
|
.imdb {
|
|
@@ -169,9 +152,5 @@
|
|
|
169
152
|
&:active {
|
|
170
153
|
color: theme(list-item-action-hover-color, text-color);
|
|
171
154
|
}
|
|
172
|
-
|
|
173
|
-
.compact & {
|
|
174
|
-
display: none;
|
|
175
|
-
}
|
|
176
155
|
}
|
|
177
156
|
</style>
|
|
@@ -13,10 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
participant: ParticipantData
|
|
16
|
-
small?: boolean
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
const { participant
|
|
18
|
+
const { participant }: Props = $props()
|
|
20
19
|
|
|
21
20
|
const { name, birth_date, death_date } = $derived(participant)
|
|
22
21
|
|
|
@@ -53,8 +52,8 @@
|
|
|
53
52
|
}
|
|
54
53
|
</script>
|
|
55
54
|
|
|
56
|
-
<div class="header"
|
|
57
|
-
<div class="heading" use:heading={2} id="
|
|
55
|
+
<div class="header">
|
|
56
|
+
<div class="heading" use:heading={2} id="name">{name}</div>
|
|
58
57
|
|
|
59
58
|
{#if birth_date}
|
|
60
59
|
<p class="dates">
|
|
@@ -64,13 +63,11 @@
|
|
|
64
63
|
</div>
|
|
65
64
|
|
|
66
65
|
<div class="content">
|
|
67
|
-
|
|
68
|
-
<div class="heading subheading" use:heading={3} id="credits">{t('Credits')}</div>
|
|
69
|
-
{/if}
|
|
66
|
+
<div class="heading small" use:heading={3} id="credits">{t('Credits')}</div>
|
|
70
67
|
|
|
71
68
|
<div class="list">
|
|
72
69
|
{#each titles as title}
|
|
73
|
-
<ListTitle {title}
|
|
70
|
+
<ListTitle {title} onclick={(event) => openModal({ event, data: title })} />
|
|
74
71
|
{/each}
|
|
75
72
|
|
|
76
73
|
{#if loading}
|
|
@@ -91,10 +88,6 @@
|
|
|
91
88
|
font-size: theme(detail-font-size, font-size-base);
|
|
92
89
|
line-height: theme(participant-description-line-height, normal);
|
|
93
90
|
color: theme(detail-text-color, text-color);
|
|
94
|
-
|
|
95
|
-
&.small {
|
|
96
|
-
padding: margin(1);
|
|
97
|
-
}
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
.heading {
|
|
@@ -105,7 +98,7 @@
|
|
|
105
98
|
line-height: normal;
|
|
106
99
|
font-style: theme(detail-title-font-style, normal);
|
|
107
100
|
|
|
108
|
-
&.
|
|
101
|
+
&.small {
|
|
109
102
|
margin: 0 0 margin(0.5);
|
|
110
103
|
font-size: theme(detail-title-small-font-size, margin(1.25));
|
|
111
104
|
}
|
|
@@ -123,11 +116,6 @@
|
|
|
123
116
|
font-size: theme(detail-font-size, font-size-base);
|
|
124
117
|
line-height: normal;
|
|
125
118
|
font-style: normal;
|
|
126
|
-
|
|
127
|
-
::view-transition-old(content),
|
|
128
|
-
::view-transition-new(content) {
|
|
129
|
-
height: 100%;
|
|
130
|
-
}
|
|
131
119
|
}
|
|
132
120
|
|
|
133
121
|
.list {
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
const { playlinks, title }: Props = $props()
|
|
23
23
|
|
|
24
24
|
const isModal = getContext('scope') === 'modal'
|
|
25
|
-
const type = getContext('type')
|
|
26
25
|
const displayAd = getFirstAdOfType('card')
|
|
27
26
|
const categorize = !!window.PlayPilotLinkInjections?.config?.categorize_playlinks
|
|
28
27
|
|
|
@@ -38,7 +37,7 @@
|
|
|
38
37
|
const list = $derived(outerWidth < 500 || !!displayAd || mergedPlaylinks.length === 1)
|
|
39
38
|
|
|
40
39
|
function onclick(playlink: string): void {
|
|
41
|
-
track(isModal ? TrackingEvent.TitleModalPlaylinkClick : TrackingEvent.
|
|
40
|
+
track(isModal ? TrackingEvent.TitleModalPlaylinkClick : TrackingEvent.TitlePopoverPlaylinkClick, title, { playlink })
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
function categorizePlaylinks(playlinks: PlaylinkData[]): CategorizedPlaylinks {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<TitlePoster {title} onload={() => posterLoaded = true} lazy={false} />
|
|
56
56
|
</div>
|
|
57
57
|
|
|
58
|
-
<div class="heading" use:heading={2} class:truncate={small} id="
|
|
58
|
+
<div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
|
|
59
59
|
|
|
60
60
|
<div class="info">
|
|
61
61
|
<div class="imdb">
|