@playpilot/tpi 2.0.5 → 3.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.
Files changed (62) hide show
  1. package/dist/link-injections.js +2 -2
  2. package/eslint.config.js +20 -0
  3. package/package.json +5 -2
  4. package/src/lib/{api.js → api.ts} +26 -38
  5. package/src/lib/{array.js → array.ts} +1 -3
  6. package/src/lib/{auth.js → auth.ts} +8 -10
  7. package/src/lib/{fakeData.js → fakeData.ts} +7 -6
  8. package/src/lib/{hash.js → hash.ts} +2 -3
  9. package/src/lib/{html.js → html.ts} +2 -4
  10. package/src/lib/{linkInjection.js → linkInjection.ts} +41 -85
  11. package/src/lib/{localization.js → localization.ts} +10 -12
  12. package/src/lib/{meta.js → meta.ts} +8 -18
  13. package/src/lib/{playlink.js → playlink.ts} +4 -5
  14. package/src/lib/{search.js → search.ts} +2 -3
  15. package/src/lib/{text.js → text.ts} +7 -19
  16. package/src/lib/{tracking.js → tracking.ts} +6 -4
  17. package/src/lib/types/global.d.ts +13 -0
  18. package/src/lib/types/injection.d.ts +41 -0
  19. package/src/lib/types/language.d.ts +1 -0
  20. package/src/lib/types/participant.d.ts +13 -0
  21. package/src/lib/types/playlink.d.ts +11 -0
  22. package/src/lib/types/position.d.ts +6 -0
  23. package/src/lib/types/title.d.ts +23 -0
  24. package/src/lib/types/user.d.ts +7 -0
  25. package/src/lib/{url.js → url.ts} +1 -2
  26. package/src/routes/+layout.svelte +3 -3
  27. package/src/routes/+page.svelte +12 -17
  28. package/src/routes/components/AfterArticlePlaylinks.svelte +14 -13
  29. package/src/routes/components/ContextMenu.svelte +11 -7
  30. package/src/routes/components/Description.svelte +9 -4
  31. package/src/routes/components/Editorial/AIIndicator.svelte +10 -7
  32. package/src/routes/components/Editorial/Alert.svelte +8 -3
  33. package/src/routes/components/Editorial/DragHandle.svelte +18 -26
  34. package/src/routes/components/Editorial/Editor.svelte +24 -23
  35. package/src/routes/components/Editorial/EditorItem.svelte +20 -18
  36. package/src/routes/components/Editorial/ManualInjection.svelte +16 -17
  37. package/src/routes/components/Editorial/PlaylinkTypeSelect.svelte +8 -4
  38. package/src/routes/components/Editorial/Search/TitleSearch.svelte +13 -17
  39. package/src/routes/components/Editorial/Switch.svelte +14 -4
  40. package/src/routes/components/Editorial/TextInput.svelte +10 -3
  41. package/src/routes/components/Genres.svelte +8 -5
  42. package/src/routes/components/Icons/IconAlign.svelte +8 -3
  43. package/src/routes/components/Icons/IconChevron.svelte +6 -3
  44. package/src/routes/components/Icons/IconEnlarge.svelte +6 -3
  45. package/src/routes/components/Modal.svelte +11 -10
  46. package/src/routes/components/Participants.svelte +8 -3
  47. package/src/routes/components/Playlinks.svelte +11 -7
  48. package/src/routes/components/Popover.svelte +11 -10
  49. package/src/routes/components/RoundButton.svelte +11 -9
  50. package/src/routes/components/SkeletonText.svelte +8 -3
  51. package/src/routes/components/Title.svelte +9 -3
  52. package/src/routes/components/TitleModal.svelte +9 -4
  53. package/src/routes/components/TitlePopover.svelte +7 -3
  54. package/src/tests/routes/components/Genres.test.js +1 -1
  55. package/src/lib/index.js +0 -1
  56. package/src/typedefs.js +0 -95
  57. /package/src/lib/{constants.js → constants.ts} +0 -0
  58. /package/src/lib/{genres.json → data/genres.json} +0 -0
  59. /package/src/lib/data/{translations.js → translations.ts} +0 -0
  60. /package/src/lib/enums/{Language.js → Language.ts} +0 -0
  61. /package/src/lib/enums/{TrackingEvent.js → TrackingEvent.ts} +0 -0
  62. /package/{jsconfig.json → tsconfig.json} +0 -0
@@ -1,4 +1,4 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { fade, fly, slide } from 'svelte/transition'
3
3
  import { flip } from 'svelte/animate'
4
4
  import EditorItem from './EditorItem.svelte'
@@ -10,16 +10,22 @@
10
10
  import { untrack } from 'svelte'
11
11
  import AIIndicator from './AIIndicator.svelte'
12
12
  import { isEquivalentInjection, isValidInjection } from '$lib/linkInjection'
13
+ import type { Position } from '$lib/types/position'
14
+ import type { LinkInjection } from '$lib/types/injection'
15
+
16
+ interface Props {
17
+ linkInjections: LinkInjection[],
18
+ htmlString?: string,
19
+ loading?: boolean,
20
+ aiRunning?: boolean
21
+ }
13
22
 
14
- /** @type {{ linkInjections: LinkInjection[], htmlString?: string, loading?: boolean, aiRunning?: boolean }} */
15
- let { linkInjections = $bindable(), htmlString = '', loading = false, aiRunning = false } = $props()
23
+ let { linkInjections = $bindable(), htmlString = '', loading = false, aiRunning = false }: Props = $props()
16
24
 
17
25
  const editorPositionKey = 'editor-position'
18
26
 
19
- /** @type {HTMLElement | null} */
20
- let editorElement = $state(null)
21
- /** @type {Position} */
22
- let position = $state(JSON.parse(localStorage.getItem(editorPositionKey) || '{ "x": 0, "y": 0 }'))
27
+ let editorElement: HTMLElement | null = $state(null)
28
+ let position: Position = $state(JSON.parse(localStorage.getItem(editorPositionKey) || '{ "x": 0, "y": 0 }'))
23
29
  let manualInjectionActive = $state(false)
24
30
  let saving = $state(false)
25
31
  let hasError = $state(false)
@@ -29,14 +35,14 @@
29
35
 
30
36
  const linkInjectionsString = $derived(JSON.stringify(linkInjections))
31
37
  const hasChanged = $derived(initialStateString !== linkInjectionsString)
32
- const filteredInjections = $derived(linkInjections.filter(i => i.title_details && !i.removed && !i.duplicate))
38
+ const filteredInjections = $derived(linkInjections.filter((i) => i.title_details && !i.removed && !i.duplicate))
33
39
 
34
40
  $effect(() => {
35
41
  if (!loading) untrack(() => initialStateString = linkInjectionsString)
36
42
  })
37
43
 
38
44
  /** Save the given injections in their current state and rewrite object to returned value */
39
- async function save() {
45
+ async function save(): Promise<void> {
40
46
  try {
41
47
  saving = true
42
48
  hasError = false
@@ -50,18 +56,12 @@
50
56
  }
51
57
  }
52
58
 
53
- /**
54
- * @param {Position} position
55
- */
56
- function savePosition(position) {
59
+ function savePosition(position: Position): void {
57
60
  localStorage.setItem(editorPositionKey, JSON.stringify(position))
58
61
  }
59
62
 
60
- /**
61
- * @param {string} key
62
- */
63
- function removeInjection(key) {
64
- const index = linkInjections.findIndex(i => i.key === key)
63
+ function removeInjection(key: string): void {
64
+ const index = linkInjections.findIndex((i) => i.key === key)
65
65
 
66
66
  linkInjections[index].manual = true
67
67
  linkInjections[index].removed = true
@@ -72,7 +72,7 @@
72
72
  /**
73
73
  * @param {HTMLElement} itemElement
74
74
  */
75
- function scrollToItem(itemElement) {
75
+ function scrollToItem(itemElement: HTMLElement): void {
76
76
  if (!editorElement) return
77
77
 
78
78
  const itemTop = itemElement.offsetTop
@@ -93,8 +93,8 @@
93
93
  /**
94
94
  * @param {Event} event
95
95
  */
96
- function onscroll(event) {
97
- const target = /** @type {HTMLElement} */ (event.target)
96
+ function onscroll(event: Event): void {
97
+ const target = event.target as HTMLElement
98
98
  scrollDistance = target.scrollTop
99
99
  }
100
100
 
@@ -103,12 +103,13 @@
103
103
  * We only pass on links that do not already exist.
104
104
  * @param {LinkInjection[]} injections
105
105
  */
106
- export function requestNewAIInjections(injections) {
106
+ export function requestNewAIInjections(injections: LinkInjection[]): void {
107
107
  const newInjections = injections.filter(injection => {
108
108
  if (!isValidInjection(injection)) return
109
109
  return !linkInjections.some((i) => isEquivalentInjection(injection, i))
110
110
  })
111
111
 
112
+ // @ts-ignore
112
113
  aIIndicator.notifyUserOfNewState(newInjections)
113
114
  }
114
115
  </script>
@@ -150,7 +151,7 @@
150
151
  <div class="items">
151
152
  {#each filteredInjections as linkInjection (linkInjection.key)}
152
153
  <!-- We want to bind to the original object, not the derived object, so we get the index of the injection in the original object by it's key -->
153
- {@const index = linkInjections.findIndex(i => i.key === linkInjection.key)}
154
+ {@const index = linkInjections.findIndex((i) => i.key === linkInjection.key)}
154
155
 
155
156
  <div animate:flip={{ duration: 300 }}>
156
157
  <EditorItem bind:linkInjection={linkInjections[index]} onremove={() => removeInjection(linkInjection.key)} onhighlight={scrollToItem} />
@@ -1,4 +1,4 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { slide } from 'svelte/transition'
3
3
  import IconChevron from '../Icons/IconChevron.svelte'
4
4
  import IconIMDb from '../Icons/IconIMDb.svelte'
@@ -10,20 +10,26 @@
10
10
  import { onMount } from 'svelte'
11
11
  import { track } from '$lib/tracking'
12
12
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
13
+ import type { LinkInjection } from '$lib/types/injection'
14
+ import type { TitleData } from '$lib/types/title'
15
+
16
+ interface Props {
17
+ linkInjection: LinkInjection,
18
+ onremove?: () => void,
19
+ // eslint-disable-next-line no-unused-vars
20
+ onhighlight?: (element: HTMLElement) => void
21
+ }
13
22
 
14
- /** @type {{ linkInjection: LinkInjection, onremove?: () => void, onhighlight?: (element: HTMLElement) => void }} */
15
- const { linkInjection = $bindable(), onremove = () => null, onhighlight = () => null } = $props()
23
+ const { linkInjection = $bindable(), onremove = () => null, onhighlight = () => null }: Props = $props()
16
24
 
17
25
  const { key, sentence, title_details, failed } = $derived(linkInjection || {})
18
26
 
19
- /** @type {TitleData} */
20
27
  // @ts-ignore Definitely not null
21
- const title = /** @type {TitleData} */ $derived(title_details)
28
+ const title: TitleData = $derived(title_details)
22
29
 
23
30
  let expanded = $state(false)
24
31
  let highlighted = $state(false)
25
- /** @type {HTMLElement | null} */
26
- let element = $state(null)
32
+ let element: HTMLElement | null = $state(null)
27
33
 
28
34
  onMount(() => {
29
35
  if (failed) track(TrackingEvent.InjectionFailed, title, { phrase: linkInjection.title, sentence })
@@ -31,9 +37,8 @@
31
37
 
32
38
  /**
33
39
  * Highlight links beloning to this item in the article itself.
34
- * @param {boolean} state
35
40
  */
36
- function toggleOnPageResultHighlight(state = true) {
41
+ function toggleOnPageResultHighlight(state: boolean = true): void {
37
42
  const matchingElements = getMatchingElements()
38
43
  matchingElements.forEach(element => {
39
44
  element.classList.toggle('injection-highlight', state)
@@ -42,14 +47,13 @@
42
47
 
43
48
  /**
44
49
  * Highlight this editor when hovering links in the article itself.
45
- * @param {MouseEvent} event
46
50
  */
47
- function setInEditorHighlight(event) {
48
- let target = /** @type {HTMLElement | null} */ (event.target)
51
+ function setInEditorHighlight(event: MouseEvent): void {
52
+ let target = event.target as HTMLElement | null
49
53
  let injectionKey = target?.dataset.playpilotInjectionKey
50
54
 
51
55
  if (target && !injectionKey) {
52
- target = /** @type {HTMLElement | null} */ (target.closest(`[data-playpilot-injection-key="${key}"]`))
56
+ target = target.closest(`[data-playpilot-injection-key="${key}"]`) as HTMLElement | null
53
57
  injectionKey = target?.dataset.playpilotInjectionKey
54
58
  }
55
59
 
@@ -58,11 +62,10 @@
58
62
  if (element && highlighted) onhighlight(element)
59
63
  }
60
64
 
61
- /** @param {MouseEvent} event */
62
- function scrollLinkIntoView(event) {
65
+ function scrollLinkIntoView(event: MouseEvent): void {
63
66
  requestAnimationFrame(() => toggleOnPageResultHighlight()) // Reset highlight in case the playlink type changed
64
67
 
65
- const target = /** @type {HTMLElement} */ (event.target)
68
+ const target = event.target as HTMLElement
66
69
  if (['BUTTON', 'INPUT'].includes(target.nodeName)) return
67
70
  if (target.closest('button') || target.closest('input')) return
68
71
 
@@ -70,8 +73,7 @@
70
73
  if (matchingElement) matchingElement.scrollIntoView({ behavior: 'smooth' })
71
74
  }
72
75
 
73
- /** @returns {Element[]} */
74
- function getMatchingElements() {
76
+ function getMatchingElements(): Element[] {
75
77
  return Array.from(document.querySelectorAll(`[data-playpilot-injection-key="${key}"]`))
76
78
  }
77
79
  </script>
@@ -1,4 +1,4 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { onMount } from 'svelte'
3
3
  import IconBack from '../Icons/IconBack.svelte'
4
4
  import RoundButton from '../RoundButton.svelte'
@@ -9,14 +9,21 @@
9
9
  import { generateInjectionKey } from '$lib/api'
10
10
  import { decodeHtmlEntities } from '$lib/html'
11
11
  import { getLinkInjectionsParentElement } from '$lib/linkInjection'
12
+ import type { LinkInjection } from '$lib/types/injection'
13
+ import type { TitleData } from '$lib/types/title'
14
+
15
+ interface Props {
16
+ htmlString: string
17
+ // eslint-disable-next-line no-unused-vars
18
+ onsave: (linkInjection: LinkInjection) => void
19
+ onclose?: () => void
20
+ }
12
21
 
13
- /** @type {{ htmlString: string, onsave: (linkInjection: LinkInjection) => void, onclose?: () => void }} */
14
- let { htmlString = '', onsave, onclose = () => null } = $props()
22
+ let { htmlString = '', onsave, onclose = () => null }: Props = $props()
15
23
 
16
24
  let currentSelection = $state('')
17
25
  let selectionSentence = $state('')
18
- /** @type {TitleData | null} */
19
- let selectedTitle = $state(null)
26
+ let selectedTitle: TitleData | null = $state(null)
20
27
  let error = $state('')
21
28
  let query = $state('')
22
29
 
@@ -25,9 +32,8 @@
25
32
  /**
26
33
  * Find the user selected content on the page, if any.
27
34
  * Results in a visual error if the selected content was not within the given HTML.
28
- * @returns {void}
29
35
  */
30
- function updateSelection() {
36
+ function updateSelection(): void {
31
37
  const selection = window.getSelection()
32
38
  if (!selection?.anchorNode) return
33
39
 
@@ -51,11 +57,8 @@
51
57
 
52
58
  /**
53
59
  * Find the sentence that the given selected phrase is in. This is limited by the node that the text is in.
54
- * @param {Selection} selection
55
- * @param {string} selectionText
56
- * @returns {string}
57
60
  */
58
- function findSentenceForSelection(selection, selectionText) {
61
+ function findSentenceForSelection(selection: Selection, selectionText: string): string {
59
62
  const range = selection.getRangeAt(0)
60
63
 
61
64
  // Get the node the text is in. If the content of the node is very short we use the parent node instead.
@@ -81,18 +84,14 @@
81
84
  return fullText.slice(sentenceStart, sentenceEnd).trim()
82
85
  }
83
86
 
84
- /**
85
- * @returns {void}
86
- */
87
- function save() {
87
+ function save(): void {
88
88
  if (!currentSelection) return
89
89
  if (!selectedTitle) return
90
90
 
91
91
  const typePath = selectedTitle.type === 'movie' ? 'movie' : 'show'
92
92
  const url = playPilotBaseUrl + `/${typePath}/${selectedTitle.slug}`
93
93
 
94
- /** @type {LinkInjection} */
95
- const linkInjection = {
94
+ const linkInjection: LinkInjection = {
96
95
  sid: selectedTitle.sid,
97
96
  title: currentSelection,
98
97
  sentence: selectionSentence,
@@ -1,12 +1,16 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { slide } from 'svelte/transition'
3
3
  import IconAlign from '../Icons/IconAlign.svelte'
4
4
  import Switch from './Switch.svelte'
5
+ import type { LinkInjection } from '$lib/types/injection'
5
6
 
6
- /** @type {{ linkInjection: LinkInjection }} */
7
- let { linkInjection } = $props()
7
+ interface Props {
8
+ linkInjection: LinkInjection
9
+ }
10
+
11
+ const { linkInjection }: Props = $props()
8
12
 
9
- let isAfterArticleButton = $derived(linkInjection.after_article_style === 'modal_button')
13
+ const isAfterArticleButton = $derived(linkInjection.after_article_style === 'modal_button')
10
14
  </script>
11
15
 
12
16
  <div class="switches">
@@ -1,28 +1,28 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { playPilotBaseUrl } from '$lib/constants'
3
3
  import { searchTitles } from '$lib/search'
4
+ import type { TitleData } from '$lib/types/title'
4
5
  import IconIMDb from '../../Icons/IconIMDb.svelte'
5
6
  import IconNewTab from '../../Icons/IconNewTab.svelte'
6
7
  import TextInput from '../TextInput.svelte'
7
8
 
8
- /** @type {{ onselect?: (title: TitleData) => void, query: string }} */
9
- let { onselect = () => null, query = $bindable() } = $props()
9
+ interface Props {
10
+ // eslint-disable-next-line no-unused-vars
11
+ onselect?: (title: TitleData) => void,
12
+ query: string
13
+ }
14
+
15
+ let { onselect = () => null, query = $bindable() }: Props = $props()
10
16
 
11
- /** @type {TitleData[]} */
12
- let results = $state([])
13
- /** @type {TitleData | null} */
14
- let selectedResult = $state(null)
17
+ let results: TitleData[] = $state([])
18
+ let selectedResult: TitleData | null = $state(null)
15
19
  let loading = $state(false)
16
20
 
17
21
  $effect(() => {
18
22
  if (query) search(query)
19
23
  })
20
24
 
21
- /**
22
- * @param {string} query
23
- * @returns {Promise<void>}
24
- */
25
- async function search(query) {
25
+ async function search(query: string): Promise<void> {
26
26
  loading = true
27
27
  selectedResult = null
28
28
 
@@ -33,11 +33,7 @@
33
33
  }
34
34
  }
35
35
 
36
- /**
37
- * @param {TitleData} title
38
- * @returns {void}
39
- */
40
- function select(title) {
36
+ function select(title: TitleData): void {
41
37
  query = title.title
42
38
  selectedResult = title
43
39
 
@@ -1,8 +1,18 @@
1
- <script>
2
- /** @type {{ active: boolean, fullwidth?: boolean, label?: string, onclick?: (active: boolean) => void, children: import('svelte').Snippet }} */
3
- let { active = $bindable(), fullwidth = false, label = '', onclick = () => null, children } = $props()
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
4
3
 
5
- function toggle() {
4
+ interface Props {
5
+ active: boolean,
6
+ fullwidth?: boolean,
7
+ label?: string,
8
+ // eslint-disable-next-line no-unused-vars
9
+ onclick?: (active: boolean) => void,
10
+ children: Snippet
11
+ }
12
+
13
+ let { active = $bindable(), fullwidth = false, label = '', onclick = () => null, children }: Props = $props()
14
+
15
+ function toggle(): void {
6
16
  active = !active
7
17
  onclick(active)
8
18
  }
@@ -1,6 +1,13 @@
1
- <script>
2
- /** @type {{ value: string, label?: string, name?: string, readonly?: boolean, oninput?: () => void }} */
3
- let { value = $bindable(), label = '', name = '', readonly = false, oninput = () => null } = $props()
1
+ <script lang="ts">
2
+ interface Props {
3
+ value: string,
4
+ label?: string,
5
+ name?: string,
6
+ readonly?: boolean,
7
+ oninput?: () => void
8
+ }
9
+
10
+ let { value = $bindable(), label = '', name = '', readonly = false, oninput = () => null }: Props = $props()
4
11
  </script>
5
12
 
6
13
  <input type="text" bind:value {name} aria-label={label} placeholder={label} {readonly} {oninput} />
@@ -1,12 +1,15 @@
1
- <script>
2
- import genreData from '$lib/genres.json'
1
+ <script lang="ts">
2
+ import genreData from '$lib/data/genres.json'
3
3
  import { t } from '$lib/localization'
4
4
 
5
- /** @type {{ genres: string[] }} */
6
- const { genres } = $props()
5
+ interface Props {
6
+ genres: string[]
7
+ }
8
+
9
+ const { genres }: Props = $props()
7
10
 
8
11
  let expanded = $state(false)
9
- let shownGenres = $derived(expanded ? genres : [genres[0]])
12
+ const shownGenres = $derived(expanded ? genres : [genres[0]])
10
13
  </script>
11
14
 
12
15
  {#each shownGenres as genre}
@@ -1,6 +1,11 @@
1
- <script>
2
- /** @type {{ align?: Alignment }} */
3
- let { align = 'bottom' } = $props()
1
+ <script lang="ts">
2
+ import type { Alignment } from '$lib/types/position'
3
+
4
+ interface Props {
5
+ align: Alignment
6
+ }
7
+
8
+ const { align = 'bottom' }: Props = $props()
4
9
  </script>
5
10
 
6
11
  <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
@@ -1,6 +1,9 @@
1
- <script>
2
- /** @type {{ expanded: boolean }} */
3
- const { expanded = false } = $props()
1
+ <script lang="ts">
2
+ interface Props {
3
+ expanded: boolean
4
+ }
5
+
6
+ const { expanded = false }: Props = $props()
4
7
  </script>
5
8
 
6
9
  <svg width="8" height="4" viewBox="0 0 8 4" fill="none" class:expanded>
@@ -1,6 +1,9 @@
1
- <script>
2
- /** @type {{ expanded: boolean }} */
3
- const { expanded = false } = $props()
1
+ <script lang="ts">
2
+ interface Props {
3
+ expanded: boolean
4
+ }
5
+
6
+ const { expanded = false }: Props = $props()
4
7
  </script>
5
8
 
6
9
  <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -1,11 +1,16 @@
1
- <script>
2
- import { fade, fly, scale } from 'svelte/transition'
1
+ <script lang="ts">
2
+ import { fade, fly, scale, type TransitionConfig } from 'svelte/transition'
3
3
  import IconClose from './Icons/IconClose.svelte'
4
4
  import RoundButton from './RoundButton.svelte'
5
- import { onMount, setContext } from 'svelte'
5
+ import { onMount, setContext, type Snippet } from 'svelte'
6
6
 
7
- /** @type {{ children: import('svelte').Snippet, onclose?: () => void, onscroll?: () => void }} */
8
- const { children, onclose = () => null, onscroll = () => null } = $props()
7
+ interface Props {
8
+ children: Snippet
9
+ onclose?: () => void
10
+ onscroll?: () => void
11
+ }
12
+
13
+ const { children, onclose = () => null, onscroll = () => null }: Props = $props()
9
14
 
10
15
  setContext('scope', 'modal')
11
16
 
@@ -16,11 +21,7 @@
16
21
  return () => document.body.style.overflowY = baseOverflowStyle || ''
17
22
  })
18
23
 
19
- /**
20
- * @param {Element} node
21
- * @returns {import('svelte/transition').TransitionConfig}
22
- */
23
- function scaleOrFly(node) {
24
+ function scaleOrFly(node: Element): TransitionConfig {
24
25
  const shouldFly = window.innerWidth < 600
25
26
 
26
27
  if (shouldFly) return fly(node, { duration: 250, y: window.innerHeight })
@@ -1,6 +1,11 @@
1
- <script>
2
- /** @type {{ participants: Participant[] }} */
3
- const { participants } = $props()
1
+ <script lang="ts">
2
+ import type { Participant } from '$lib/types/participant'
3
+
4
+ interface Props {
5
+ participants: Participant[]
6
+ }
7
+
8
+ const { participants }: Props = $props()
4
9
  </script>
5
10
 
6
11
  <h2>Cast</h2>
@@ -1,13 +1,20 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
3
3
  import { t } from '$lib/localization'
4
4
  import { mergePlaylinks } from '$lib/playlink'
5
5
  import { track } from '$lib/tracking'
6
+ import type { PlaylinkData } from '$lib/types/playlink'
7
+ import type { TitleData } from '$lib/types/title'
6
8
  import IconContinue from './Icons/IconContinue.svelte'
7
9
  import { getContext } from 'svelte'
8
10
 
9
- /** @type {{ playlinks: PlaylinkData[], title: TitleData, list?: boolean }} */
10
- const { playlinks, title, list = false } = $props()
11
+ interface Props {
12
+ playlinks: PlaylinkData[]
13
+ title: TitleData
14
+ list?: boolean
15
+ }
16
+
17
+ const { playlinks, title, list = false }: Props = $props()
11
18
 
12
19
  const isModal = getContext('scope') === 'modal'
13
20
 
@@ -22,10 +29,7 @@
22
29
  TVOD: t('Rent Or Buy'),
23
30
  }
24
31
 
25
- /**
26
- * @param {string} playlink Name of the clicked playlink
27
- */
28
- function onclick(playlink) {
32
+ function onclick(playlink: string): void {
29
33
  track(isModal ? TrackingEvent.TitleModalPlaylinkClick : TrackingEvent.TitlePopoverPlaylinkClick, title, { playlink })
30
34
  }
31
35
  </script>
@@ -1,14 +1,17 @@
1
- <script>
2
- import { onMount, setContext, tick } from 'svelte'
1
+ <script lang="ts">
2
+ import { onMount, setContext, tick, type Snippet } from 'svelte'
3
3
  import { fly } from 'svelte/transition'
4
4
 
5
- /** @type {{ children: import('svelte').Snippet, maxHeight?: number }} */
6
- let { children, maxHeight = $bindable() } = $props()
5
+ interface Props {
6
+ children: Snippet
7
+ maxHeight?: number
8
+ }
9
+
10
+ let { children, maxHeight = $bindable() }: Props = $props()
7
11
 
8
12
  setContext('scope', 'popover')
9
13
 
10
- /** @type {HTMLElement | null} */
11
- let element = $state(null)
14
+ let element: HTMLElement | null = $state(null)
12
15
  let flip = $state(false)
13
16
 
14
17
  onMount(async () => {
@@ -23,9 +26,8 @@
23
26
  * Flip the element vertically if it doesn't fit on screen above the link.
24
27
  * Move it to the left if it doesn't fit from the right side of the screen.
25
28
  * Vertically it is always above or below, but horizontally is a gradual.
26
- * @return {void}
27
29
  */
28
- function positionElement() {
30
+ function positionElement(): void {
29
31
  if (!element) return
30
32
 
31
33
  const { top, right, bottom, height } = element.getBoundingClientRect()
@@ -40,9 +42,8 @@
40
42
 
41
43
  /**
42
44
  * Set the max height of the dialog so that it always fits on screen, even after flipping.
43
- * @return {void}
44
45
  */
45
- function setMaxHeight() {
46
+ function setMaxHeight(): void {
46
47
  if (!element) return
47
48
 
48
49
  const { top, height } = element.getBoundingClientRect()
@@ -1,12 +1,14 @@
1
- <script>
2
- /**
3
- * @typedef {Object} Props
4
- * @property {string} [size]
5
- * @property {import('svelte').Snippet | null} [children]
6
- * @property {() => void} [onclick]
7
- */
8
- /** @type {Props & { [key: string]: any }} */
9
- const { children = null, size = 'margin(2)', onclick = () => null, ...rest } = $props()
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface Props {
5
+ size?: string;
6
+ children?: Snippet;
7
+ onclick?: () => void;
8
+ [key: string]: any;
9
+ }
10
+
11
+ const { children, size = 'margin(2)', onclick = () => null, ...rest }: Props = $props()
10
12
  </script>
11
13
 
12
14
  <svelte:element this={rest.href ? 'a' : 'button'} role={rest.href ? 'link' : 'button'} {onclick} class="button" style:--size={size} {...rest}>
@@ -1,6 +1,11 @@
1
- <script>
2
- /** @type {{ lines?: number, max?: number, min?: any }} */
3
- const { lines = 4, max = 100, min = (max / 100) * 70 } = $props()
1
+ <script lang="ts">
2
+ interface Props {
3
+ lines?: number
4
+ max?: number
5
+ min?: number
6
+ }
7
+
8
+ const { lines = 4, max = 100, min = (max / 100) * 70 }: Props = $props()
4
9
  </script>
5
10
 
6
11
  <!-- eslint-disable-next-line no-unused-vars -->
@@ -1,13 +1,19 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import Genres from './Genres.svelte'
3
3
  import Playlinks from './Playlinks.svelte'
4
4
  import Description from './Description.svelte'
5
5
  import Participants from './Participants.svelte'
6
6
  import IconIMDb from './Icons/IconIMDb.svelte'
7
7
  import { t } from '$lib/localization'
8
+ import type { TitleData } from '$lib/types/title'
8
9
 
9
- /** @type {{ title: TitleData, small?: boolean, compact?: boolean }} */
10
- const { title, small = false, compact = false } = $props()
10
+ interface Props {
11
+ title: TitleData
12
+ small?: boolean
13
+ compact?: boolean
14
+ }
15
+
16
+ const { title, small = false, compact = false }: Props = $props()
11
17
 
12
18
  let imageLoaded = $state(false)
13
19
  </script>