@playpilot/tpi 6.5.0 → 6.5.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.
Files changed (35) hide show
  1. package/dist/link-injections.js +11 -10
  2. package/eslint.config.js +22 -0
  3. package/package.json +2 -2
  4. package/src/lib/api/api.ts +1 -0
  5. package/src/lib/api/externalPages.ts +1 -0
  6. package/src/lib/clipboard.ts +4 -4
  7. package/src/lib/modal.ts +15 -15
  8. package/src/lib/popover.ts +3 -3
  9. package/src/lib/routes.ts +2 -2
  10. package/src/lib/tracking.ts +1 -0
  11. package/src/lib/trailer.ts +5 -5
  12. package/src/main.ts +1 -0
  13. package/src/routes/components/Button.svelte +0 -1
  14. package/src/routes/components/Editorial/DragHandle.svelte +0 -1
  15. package/src/routes/components/Editorial/EditorItem.svelte +0 -1
  16. package/src/routes/components/Editorial/ManualInjection.svelte +0 -1
  17. package/src/routes/components/Editorial/ResizeHandle.svelte +0 -1
  18. package/src/routes/components/Editorial/Search/TitleSearch.svelte +0 -1
  19. package/src/routes/components/Editorial/Session.svelte +0 -1
  20. package/src/routes/components/Editorial/Switch.svelte +0 -1
  21. package/src/routes/components/Explore/Filter/Dropdown.svelte +0 -1
  22. package/src/routes/components/Explore/Filter/FilterItem.svelte +3 -4
  23. package/src/routes/components/Explore/Filter/Range.svelte +0 -2
  24. package/src/routes/components/Explore/Filter/Search.svelte +0 -1
  25. package/src/routes/components/Explore/Filter/TogglesWithSearch.svelte +0 -1
  26. package/src/routes/components/GridTitle.svelte +0 -1
  27. package/src/routes/components/ListTitle.svelte +0 -1
  28. package/src/routes/components/Playlinks/AfterArticlePlaylinks.svelte +0 -1
  29. package/src/routes/components/Playlinks/PlaylinkIcon.svelte +0 -1
  30. package/src/routes/components/Playlinks/PlaylinkLabel.svelte +0 -1
  31. package/src/routes/components/PostersScrollReveal.svelte +3 -2
  32. package/src/routes/components/SkeletonText.svelte +1 -1
  33. package/src/routes/components/UserJourney.svelte +0 -2
  34. package/src/tests/lib/api/externalPages.test.js +10 -1
  35. package/src/tests/lib/trailer.test.js +1 -4
package/eslint.config.js CHANGED
@@ -81,4 +81,26 @@ export default [
81
81
  ],
82
82
  },
83
83
  },
84
+ {
85
+ ignores: [],
86
+
87
+ plugins: {
88
+ prettier,
89
+ },
90
+
91
+ rules: {
92
+ 'svelte/require-each-key': 'off',
93
+ 'svelte/no-navigation-without-resolve': 'off',
94
+ 'no-unused-vars': 'off',
95
+ '@typescript-eslint/no-unused-vars': [
96
+ 'error',
97
+ {
98
+ vars: 'all',
99
+ args: 'after-used',
100
+ ignoreRestSiblings: true,
101
+ argsIgnorePattern: '^_',
102
+ },
103
+ ],
104
+ },
105
+ },
84
106
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "6.5.0",
3
+ "version": "6.5.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -24,7 +24,7 @@
24
24
  "@typescript-eslint/parser": "^8.32.1",
25
25
  "eslint": "^9.27.0",
26
26
  "eslint-config-prettier": "^9.1.0",
27
- "eslint-plugin-svelte": "^2.36.0",
27
+ "eslint-plugin-svelte": "^3.15.0",
28
28
  "globals": "^15.0.0",
29
29
  "happy-dom": "^16.5.3",
30
30
  "prettier": "^3.3.2",
@@ -21,6 +21,7 @@ export async function api<T>(path: string, { headers = {}, method = 'GET', body
21
21
  const response = await fetch(apiBaseUrl + path, {
22
22
  method,
23
23
  headers: new Headers({ ...baseHeaders, ...headers }),
24
+ // eslint-disable-next-line no-undef
24
25
  body: body ? JSON.stringify(body as BodyInit) : null,
25
26
  })
26
27
 
@@ -129,6 +129,7 @@ export async function runAiBasedOnResponse(pageText: string, response: LinkInjec
129
129
  const isCurrentTimeEqualToResponseTime = currentModifiedTime && responseModifiedTime === currentModifiedTime
130
130
 
131
131
  if (response.ai_last_run && isCurrentTimeEqualToResponseTime) return response
132
+ if (new Date(currentModifiedTime || 0) <= new Date(responseModifiedTime || 0)) return response
132
133
 
133
134
  return await fetchLinkInjections(pageText, { params: { run_ai: true } })
134
135
  }
@@ -1,14 +1,14 @@
1
1
  export function copyToClipboard(text: string): void {
2
- const textarea = document.createElement("textarea")
2
+ const textarea = document.createElement('textarea')
3
3
 
4
4
  textarea.value = text
5
- textarea.style.position = "fixed"
6
- textarea.style.left = "-9999px"
5
+ textarea.style.position = 'fixed'
6
+ textarea.style.left = '-9999px'
7
7
 
8
8
  document.body.appendChild(textarea)
9
9
 
10
10
  textarea.select()
11
11
 
12
- document.execCommand("copy")
12
+ document.execCommand('copy')
13
13
  document.body.removeChild(textarea)
14
14
  }
package/src/lib/modal.ts CHANGED
@@ -1,16 +1,16 @@
1
- import { mount, unmount } from "svelte"
2
- import { isHoldingSpecialKey } from "./event"
3
- import type { LinkInjection } from "./types/injection"
4
- import type { TitleData } from "./types/title"
5
- import type { ParticipantData } from "./types/participant"
6
- import { mobileBreakpoint } from "./constants"
7
- import TitleModal from "../routes/components/TitleModal.svelte"
8
- import ParticipantModal from "../routes/components/ParticipantModal.svelte"
9
- import ExploreModal from "../routes/components/Explore/ExploreModal.svelte"
10
- import { getPlayPilotWrapperElement, keyDataAttribute, keySelector } from "./injection"
11
- import { playFallbackViewTransition } from "./viewTransition"
12
- import { destroyLinkPopover } from "./popover"
13
- import { prefersReducedMotion } from "svelte/motion"
1
+ import { mount, unmount } from 'svelte'
2
+ import { isHoldingSpecialKey } from './event'
3
+ import type { LinkInjection } from './types/injection'
4
+ import type { TitleData } from './types/title'
5
+ import type { ParticipantData } from './types/participant'
6
+ import { mobileBreakpoint } from './constants'
7
+ import TitleModal from '../routes/components/TitleModal.svelte'
8
+ import ParticipantModal from '../routes/components/ParticipantModal.svelte'
9
+ import ExploreModal from '../routes/components/Explore/ExploreModal.svelte'
10
+ import { getPlayPilotWrapperElement, keyDataAttribute, keySelector } from './injection'
11
+ import { playFallbackViewTransition } from './viewTransition'
12
+ import { destroyLinkPopover } from './popover'
13
+ import { prefersReducedMotion } from 'svelte/motion'
14
14
 
15
15
  type ModalType = 'title' | 'participant' | 'explore'
16
16
 
@@ -49,13 +49,13 @@ export function openModal(
49
49
  addModalToList({ type, injection, data, scrollPosition, component })
50
50
  }
51
51
 
52
- function getModalComponentByType({ type = 'title', target, data, props = {} }: { type: ModalType, target: Element, data: TitleData | ParticipantData | null, props?: Record<string, any> }) {
52
+ function getModalComponentByType({ type = 'title', target, data, props = {} }: { type: ModalType, target: Element, data: TitleData | ParticipantData | null, props?: Record<string, any> }): ReturnType<typeof mount> {
53
53
  if (type === 'participant') return mount(ParticipantModal, { target, props: { participant: data as ParticipantData, ...props } })
54
54
  if (type === 'explore') return mount(ExploreModal, { target, props: { ...props } })
55
55
  return mount(TitleModal, { target, props: { title: data as TitleData, ...props } })
56
56
  }
57
57
 
58
- function addModalToList({ type = 'title', injection = null, data, scrollPosition = 0, component }: Modal) {
58
+ function addModalToList({ type = 'title', injection = null, data, scrollPosition = 0, component }: Modal): void {
59
59
  modals.push({ type, injection, data, scrollPosition, component })
60
60
  }
61
61
 
@@ -27,7 +27,7 @@ export function openPopoverForInjectedLink(event: MouseEvent, injection: LinkInj
27
27
  }, 100)
28
28
  }
29
29
 
30
- export async function destroyLinkPopover(outro: boolean = true) {
30
+ export async function destroyLinkPopover(outro: boolean = true): Promise<void> {
31
31
  if (activePopoverInsertedComponent) {
32
32
  const promise = unmount(activePopoverInsertedComponent, { outro })
33
33
 
@@ -59,10 +59,10 @@ export function destroyLinkPopoverOnMouseleave(event: MouseEvent): void {
59
59
  destroyLinkPopover()
60
60
  }
61
61
 
62
- export function clearCurrentlyHoveredInjection() {
62
+ export function clearCurrentlyHoveredInjection(): void {
63
63
  currentlyHoveredInjection = null
64
64
  }
65
65
 
66
- export function isPopoverActive() {
66
+ export function isPopoverActive(): boolean {
67
67
  return !!activePopoverInsertedComponent
68
68
  }
package/src/lib/routes.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { playPilotBaseUrl } from "./constants"
2
- import type { TitleData } from "./types/title"
1
+ import { playPilotBaseUrl } from './constants'
2
+ import type { TitleData } from './types/title'
3
3
 
4
4
  export function titleUrl(title: TitleData): string {
5
5
  return `${playPilotBaseUrl}/${title.type}/${title.slug}/`
@@ -36,6 +36,7 @@ export async function track(event: string, title: TitleData | null = null, paylo
36
36
  }
37
37
  }
38
38
 
39
+ // eslint-disable-next-line no-undef
39
40
  payload.version = __SCRIPT_VERSION__
40
41
  payload.url = getFullUrlPath()
41
42
  payload.organization_sid = window.PlayPilotLinkInjections?.organization_sid || 'undefined'
@@ -1,11 +1,11 @@
1
- import { mount, unmount } from "svelte"
2
- import { getPlayPilotWrapperElement } from "./injection"
3
- import type { TitleData } from "./types/title"
4
- import YouTubeEmbedOverlay from "../routes/components/YouTubeEmbedOverlay.svelte"
1
+ import { mount, unmount } from 'svelte'
2
+ import { getPlayPilotWrapperElement } from './injection'
3
+ import type { TitleData } from './types/title'
4
+ import YouTubeEmbedOverlay from '../routes/components/YouTubeEmbedOverlay.svelte'
5
5
 
6
6
  let currentTrailerComponent: object | null = {}
7
7
 
8
- export function openTrailerOverlay(title: TitleData) {
8
+ export function openTrailerOverlay(title: TitleData): void {
9
9
  const target = getPlayPilotWrapperElement()
10
10
  const props = { onclose: closeTrailerOverlay, embeddable_url: title.embeddable_url || '' }
11
11
 
package/src/main.ts CHANGED
@@ -42,6 +42,7 @@ window.PlayPilotLinkInjections = {
42
42
  this.editorial_token = options.editorial_token
43
43
  this.selector = options.selector
44
44
  this.after_article_selector = options.after_article_selector
45
+ // eslint-disable-next-line no-undef
45
46
  this.after_article_insert_position = options.after_article_insert_position as InsertPosition
46
47
  this.language = options.language
47
48
  this.region = options.region
@@ -5,7 +5,6 @@
5
5
  variant?: 'filled' | 'border' | 'link'
6
6
  size?: 'base' | 'large'
7
7
  active?: boolean
8
- // eslint-disable-next-line no-unused-vars
9
8
  onclick?: (event: MouseEvent) => void
10
9
  children?: Snippet
11
10
  }
@@ -6,7 +6,6 @@
6
6
  element: HTMLElement,
7
7
  position: Position,
8
8
  limit?: Position,
9
- // eslint-disable-next-line no-unused-vars
10
9
  onchange?: (position: Position) => void
11
10
  }
12
11
 
@@ -21,7 +21,6 @@
21
21
  interface Props {
22
22
  linkInjection: LinkInjection,
23
23
  onremove?: () => void,
24
- // eslint-disable-next-line no-unused-vars
25
24
  onhighlight?: (element: HTMLElement) => void
26
25
  }
27
26
 
@@ -16,7 +16,6 @@
16
16
 
17
17
  interface Props {
18
18
  pageText: string
19
- // eslint-disable-next-line no-unused-vars
20
19
  onsave: (linkInjection: LinkInjection) => void
21
20
  onclose?: () => void
22
21
  }
@@ -4,7 +4,6 @@
4
4
  interface Props {
5
5
  element: HTMLElement,
6
6
  height: number,
7
- // eslint-disable-next-line no-unused-vars
8
7
  onchange?: (height: number) => void
9
8
  }
10
9
 
@@ -5,7 +5,6 @@
5
5
  import TitleSearchItem from './TitleSearchItem.svelte'
6
6
 
7
7
  interface Props {
8
- // eslint-disable-next-line no-unused-vars
9
8
  onselect?: (title: TitleData) => void
10
9
  query: string
11
10
  }
@@ -5,7 +5,6 @@
5
5
 
6
6
  interface Props {
7
7
  pageText?: string,
8
- // eslint-disable-next-line no-unused-vars
9
8
  onpoll?: ({ injectionsEnabled, aiEnabled }: { injectionsEnabled: boolean, aiEnabled: boolean }) => void,
10
9
  onallow?: () => void,
11
10
  ondisallow?: () => void,
@@ -5,7 +5,6 @@
5
5
  active?: boolean,
6
6
  fullwidth?: boolean,
7
7
  label?: string,
8
- // eslint-disable-next-line no-unused-vars
9
8
  onclick?: (active: boolean) => void,
10
9
  children: Snippet
11
10
  }
@@ -3,7 +3,6 @@
3
3
  import { tick, type Snippet } from 'svelte'
4
4
 
5
5
  interface Props {
6
- // eslint-disable-next-line no-unused-vars
7
6
  button: Snippet<[{ toggle: (event: MouseEvent) => void }]>,
8
7
  content: Snippet
9
8
  }
@@ -7,8 +7,7 @@
7
7
  import Range from './Range.svelte'
8
8
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
9
9
  import { track } from '$lib/tracking'
10
- import { onMount } from 'svelte';
11
- import { t } from '$lib/localization';
10
+ import { t } from '$lib/localization'
12
11
 
13
12
  type Item = { label: string, value: string }
14
13
 
@@ -31,7 +30,7 @@
31
30
  data = null,
32
31
  fetchData = null,
33
32
  range = null,
34
- valueAppend = ''
33
+ valueAppend = '',
35
34
  }: Props = $props()
36
35
 
37
36
  const active = $derived.by(() => {
@@ -97,7 +96,7 @@
97
96
  {@const selected = filter[param]?.value as string[] | undefined}
98
97
  <TogglesWithSearch options={data} {selected} onchange={(selected) => setFilter(selected, 'array')} />
99
98
  {:else if range}
100
- {@const value = (filter[param]?.value as unknown as [number, number])}
99
+ {@const value = filter[param]?.value as unknown as [number, number]}
101
100
  <Range {range} {value} label={label} {valueAppend} onchange={(value) => setFilter(value, 'range')} />
102
101
  {/if}
103
102
  </div>
@@ -4,9 +4,7 @@
4
4
  value?: [number, number]
5
5
  label?: string
6
6
  valueAppend?: string
7
- // eslint-disable-next-line no-unused-vars
8
7
  formatFunction?: (value: number) => string
9
- // eslint-disable-next-line no-unused-vars
10
8
  onchange?: (value: [number, number]) => void
11
9
  }
12
10
 
@@ -2,7 +2,6 @@
2
2
  import IconSearch from '../../Icons/IconSearch.svelte'
3
3
 
4
4
  interface Props {
5
- // eslint-disable-next-line no-unused-vars
6
5
  oninput: (query: string) => void
7
6
  }
8
7
 
@@ -11,7 +11,6 @@
11
11
  options?: Option[]
12
12
  selected?: string[]
13
13
  placeholder?: string
14
- // eslint-disable-next-line no-unused-vars
15
14
  onchange?: (selected: string[]) => void
16
15
  }
17
16
 
@@ -6,7 +6,6 @@
6
6
 
7
7
  interface Props {
8
8
  title: TitleData
9
- // eslint-disable-next-line no-unused-vars
10
9
  onclick?: (event: MouseEvent) => void
11
10
  }
12
11
 
@@ -7,7 +7,6 @@
7
7
 
8
8
  interface Props {
9
9
  title: TitleData
10
- // eslint-disable-next-line no-unused-vars
11
10
  onclick?: (event: MouseEvent) => void
12
11
  }
13
12
 
@@ -7,7 +7,6 @@
7
7
 
8
8
  interface Props {
9
9
  linkInjections: LinkInjection[],
10
- // eslint-disable-next-line no-unused-vars
11
10
  onclickmodal?: (event: MouseEvent, linkInjection: LinkInjection) => void
12
11
  }
13
12
 
@@ -6,7 +6,6 @@
6
6
  interface Props {
7
7
  playlink: PlaylinkData
8
8
  size?: number
9
- // eslint-disable-next-line no-unused-vars
10
9
  onclick?: (event: MouseEvent) => void
11
10
  }
12
11
 
@@ -4,7 +4,6 @@
4
4
  interface Props {
5
5
  playlink: PlaylinkData
6
6
  size?: number
7
- // eslint-disable-next-line no-unused-vars
8
7
  onclick?: (event: MouseEvent) => void
9
8
  }
10
9
 
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { mobileBreakpoint } from '$lib/constants'
3
3
  import { onDestroy } from 'svelte'
4
+ import { SvelteSet } from 'svelte/reactivity'
4
5
 
5
6
  const linksWithPosters = Array.from(document.querySelectorAll<HTMLElement>('[data-playpilot-poster-url]'))
6
- const linksWithCurrentlyVisiblePosters = new Set<HTMLElement>()
7
+ const linksWithCurrentlyVisiblePosters = new SvelteSet<HTMLElement>()
7
8
  const targetThreshold = 0.7
8
9
  const posterSelector = '[data-playpilot-poster]'
9
10
 
@@ -21,7 +22,7 @@
21
22
  setScrollTimeout()
22
23
 
23
24
  const windowHeight = window.innerHeight
24
- const linksWithinTargetViewport = new Set<HTMLElement>()
25
+ const linksWithinTargetViewport = new SvelteSet<HTMLElement>()
25
26
  const targetFromBottom = windowHeight * targetThreshold
26
27
  const targetFromTop = windowHeight - targetFromBottom
27
28
 
@@ -9,7 +9,7 @@
9
9
  </script>
10
10
 
11
11
  <!-- eslint-disable-next-line no-unused-vars -->
12
- {#each { length: lines } as _}
12
+ {#each { length: lines }}
13
13
  <div class="skeleton" style:width="{Math.floor(Math.random() * (max - min) + min)}%">&nbsp;</div>
14
14
  {/each}
15
15
 
@@ -1,9 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { isEditorialModeEnabled } from '$lib/api/auth'
3
- import { SplitTest } from '$lib/enums/SplitTest'
4
3
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
5
4
  import { keyDataAttribute, keySelector } from '$lib/injection'
6
- import { getSplitTestVariantName } from '$lib/splitTest'
7
5
  import { track } from '$lib/tracking'
8
6
  import { onMount } from 'svelte'
9
7
 
@@ -290,7 +290,7 @@ describe('$lib/api/externalPages', () => {
290
290
  expect(result).toEqual(response)
291
291
  })
292
292
 
293
- it('Should call api if ai_enabled is true and content_modified_time does not match document time, but no pageText is given', async () => {
293
+ it('Should not call api if ai_enabled is true and content_modified_time does not match document time, but no pageText is given', async () => {
294
294
  document.body.innerHTML = '<meta content="2025-04-20T00:00:00Z" property="article:modified_time">'
295
295
  const response = { ai_enabled: true, content_modified_time: '2025-01-01T00:00:00Z' }
296
296
 
@@ -308,6 +308,15 @@ describe('$lib/api/externalPages', () => {
308
308
  expect(api).not.toHaveBeenCalledTimes(1)
309
309
  })
310
310
 
311
+ it('Should not call api if ai_enabled is true but response content_modified_time is before document time', async () => {
312
+ document.body.innerHTML = '<meta content="2020-04-20T00:00:00Z" property="article:modified_time">'
313
+ const response = { ai_enabled: true, content_modified_time: '2025-01-01T00:00:00Z' }
314
+
315
+ // @ts-ignore
316
+ await runAiBasedOnResponse('Some text', response)
317
+ expect(api).not.toHaveBeenCalled()
318
+ })
319
+
311
320
  it('Should call api if ai_enabled is true and ai_last_run is null, regardless of content_modified_time', async () => {
312
321
  const response = { ai_enabled: true, ai_last_run: null }
313
322
 
@@ -1,9 +1,6 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest'
2
- import { closeCurrentModal, destroyAllModals, destroyCurrentModal, getAllModals, getPreviousModal, goBackToPreviousModal, openModal } from '$lib/modal'
3
- import { linkInjections, title } from '$lib/fakeData'
2
+ import { title } from '$lib/fakeData'
4
3
  import { mount, unmount } from 'svelte'
5
- import ParticipantModal from '../../routes/components/ParticipantModal.svelte'
6
- import TitleModal from '../../routes/components/TitleModal.svelte'
7
4
  import { closeTrailerOverlay, openTrailerOverlay } from '$lib/trailer'
8
5
 
9
6
  vi.mock('svelte', () => ({