@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,10 +1,10 @@
1
1
  import { getLinkInjectionsParentElement } from './linkInjection'
2
+ import type { ArticleMetaData } from './types/injection'
2
3
 
3
4
  /**
4
5
  * Returns meta data to be included with injections.
5
- * @returns {ArticleMetaData}
6
6
  */
7
- export function getPageMetaData() {
7
+ export function getPageMetaData(): ArticleMetaData {
8
8
  const parent = getLinkInjectionsParentElement()
9
9
 
10
10
  return {
@@ -17,10 +17,8 @@ export function getPageMetaData() {
17
17
  /**
18
18
  * Returns the most relevant `h1` content on the page.
19
19
  * A page should only ever contain exactly 1 `h1` tag, but we don't control these things.
20
- * @param {HTMLElement} parent
21
- * @returns {string | null}
22
20
  */
23
- export function getPageHeading(parent) {
21
+ export function getPageHeading(parent: HTMLElement): string | null {
24
22
  const heading = parent.querySelector('h1') || document.querySelector('h1')
25
23
 
26
24
  return heading?.innerText.trim() || null
@@ -28,15 +26,12 @@ export function getPageHeading(parent) {
28
26
 
29
27
  /**
30
28
  * Returns the datetime for the article, intended to be the last time the article was modified.
31
- * @param {HTMLElement} parent
32
- * @returns {string | null}
33
29
  */
34
- export function getPageModifiedTime(parent) {
30
+ export function getPageModifiedTime(parent: HTMLElement): string | null {
35
31
  const element =
36
- /** @type {HTMLElement | null} */
37
32
  (document.querySelector('meta[property*="modified_time"]') ||
38
33
  parent.querySelector('[itemprop="dateModified"]') ||
39
- document.querySelector('[itemprop="dateModified"]') || null)
34
+ document.querySelector('[itemprop="dateModified"]') || null) as HTMLElement | null
40
35
 
41
36
  const datetime = element?.getAttribute('content') || element?.getAttribute('datetime') || element?.innerText
42
37
 
@@ -47,19 +42,16 @@ export function getPageModifiedTime(parent) {
47
42
 
48
43
  /**
49
44
  * Returns the datetime for the article, intended to be the date the article was first published
50
- * @param {HTMLElement} parent
51
- * @returns {string | null}
52
45
  */
53
- export function getPagePublishedTime(parent) {
46
+ export function getPagePublishedTime(parent: HTMLElement): string | null {
54
47
  const element =
55
- /** @type {HTMLElement | null} */
56
48
  (document.querySelector('meta[property*="published_time"]') ||
57
49
  parent.querySelector('[itemprop="datePublished"]') ||
58
50
  document.querySelector('[itemprop="datePublished"]') ||
59
51
  parent.querySelector('time') ||
60
52
  document.querySelector('time') ||
61
53
  parent.querySelector('[datetime]') ||
62
- document.querySelector('[datetime]') || null)
54
+ document.querySelector('[datetime]') || null) as HTMLElement | null
63
55
 
64
56
  const datetime = element?.getAttribute('content') || element?.getAttribute('datetime') || element?.innerText
65
57
 
@@ -70,10 +62,8 @@ export function getPagePublishedTime(parent) {
70
62
 
71
63
  /**
72
64
  * Get datetime string as ISO datetime string
73
- * @param {string} datetime
74
- * @returns {string | null}
75
65
  */
76
- function getDatetime(datetime) {
66
+ function getDatetime(datetime: string): string | null {
77
67
  try {
78
68
  return new Date(datetime).toISOString()
79
69
  } catch {
@@ -1,11 +1,10 @@
1
+ import type { PlaylinkData } from "./types/playlink"
2
+
1
3
  /**
2
4
  * Merge playlinks of the same provider of BUY and RENT categories into a shared TVOD category.
3
- * @param {PlaylinkData[]} playlinks
4
- * @returns {PlaylinkData[]}
5
5
  */
6
- export function mergePlaylinks(playlinks) {
7
- /** @type {PlaylinkData[]} */
8
- let merged = []
6
+ export function mergePlaylinks(playlinks: PlaylinkData[]): PlaylinkData[] {
7
+ let merged: PlaylinkData[] = []
9
8
 
10
9
  for (const playlink of playlinks) {
11
10
  let newPlaylink = playlink
@@ -1,12 +1,11 @@
1
1
  import { getApiToken } from './api'
2
2
  import { apiBaseUrl } from './constants'
3
+ import type { TitleData } from './types/title'
3
4
 
4
5
  /**
5
6
  * Search for movies & shows. Requires valid API token.
6
- * @param {*} query
7
- * @returns {Promise<TitleData[]>}
8
7
  */
9
- export async function searchTitles(query) {
8
+ export async function searchTitles(query: string): Promise<TitleData[]> {
10
9
  const headers = new Headers({ 'Content-Type': 'application/json' })
11
10
  const apiToken = getApiToken()
12
11
 
@@ -4,13 +4,10 @@ import { decodeHtmlEntities, encodeHtmlEntities } from './html'
4
4
  * Find the node that a particular string is in. For example:
5
5
  * <p>Some sentence <strong>with words</strong> in it</p>
6
6
  * findTextNodeContaining('words') would return the <strong> node.
7
- *
8
- * @param {string} text
9
- * @param {HTMLElement} element The parent element from which to search for the given text
10
- * @param {string[]} ignoredParentNodes Filtered out parent nodes
11
- * @returns {Node | null}
7
+ * @param element The parent element from which to search for the given text
8
+ * @param ignoredParentNodes Filtered out parent nodes
12
9
  */
13
- export function findTextNodeContaining(text, element, ignoredParentNodes = []) {
10
+ export function findTextNodeContaining(text: string, element: HTMLElement, ignoredParentNodes: string[] = []): Node | null {
14
11
  const walker = document.createTreeWalker(element)
15
12
 
16
13
  let node
@@ -23,11 +20,9 @@ export function findTextNodeContaining(text, element, ignoredParentNodes = []) {
23
20
 
24
21
  /**
25
22
  * Returns whether or not the node itself is a link or is inside of a link tag.
26
- * @param {Node} node
27
- * @returns {boolean}
28
23
  */
29
- export function isNodeInLink(node) {
30
- const parentNode = /** @type {HTMLElement} */ (node.parentNode)
24
+ export function isNodeInLink(node: Node): boolean {
25
+ const parentNode = node.parentNode as HTMLElement | null
31
26
 
32
27
  if (!parentNode) return false
33
28
 
@@ -38,13 +33,8 @@ export function isNodeInLink(node) {
38
33
  * Replace a string in a bit of text, but only if it's after the given startIndex.
39
34
  * This replace method matches html entities against their decoded and encoded variant.
40
35
  * For instance &amp; and & are treated as one and the same character.
41
- * @param {string} text
42
- * @param {string} search
43
- * @param {string} replacement
44
- * @param {number} startIndex
45
- * @return {string}
46
36
  */
47
- export function replaceStartingFrom(text, search, replacement, startIndex) {
37
+ export function replaceStartingFrom(text: string, search: string, replacement: string, startIndex: number): string {
48
38
  const encodedSearch = encodeHtmlEntities(search)
49
39
  const decodedSearch = decodeHtmlEntities(search)
50
40
 
@@ -61,9 +51,7 @@ export function replaceStartingFrom(text, search, replacement, startIndex) {
61
51
 
62
52
  /**
63
53
  * Returns a string for most consistent string comparisons, decoding Html symbols and removing spaces.
64
- * @param {string} phrase
65
- * @returns {string}
66
54
  */
67
- export function cleanPhrase(phrase) {
55
+ export function cleanPhrase(phrase: string): string {
68
56
  return decodeHtmlEntities(phrase).toLowerCase().replace(/\s+/g, '')
69
57
  }
@@ -1,13 +1,15 @@
1
+ import type { TitleData } from "./types/title"
2
+
1
3
  const baseUrl = 'https://insights.playpilot.net'
2
4
 
3
5
  /**
4
6
  * Track events via PlayPilot insights tracking. Inserts data about the title when a title is passed.
5
7
  * Other data can be passed via the `payload` param.
6
- * @param {string} event Name of the event
7
- * @param {TitleData | null} [title] Title related to the event
8
- * @param {Record<string, string>} [payload] Any data that will be included with the event
8
+ * @param event Name of the event
9
+ * @param [title] Title related to the event
10
+ * @param [payload] Any data that will be included with the event
9
11
  */
10
- export async function track(event, title = null, payload = {}) {
12
+ export async function track(event: string, title: TitleData | null = null, payload: Record<string, string> = {}): Promise<void> {
11
13
  const headers = new Headers({ 'Content-Type': 'application/json' })
12
14
 
13
15
  if (title) {
@@ -0,0 +1,13 @@
1
+ declare global {
2
+ interface Window {
3
+ PlayPilotLinkInjections: {
4
+ token: string
5
+ editorial_token: string
6
+ selector: string
7
+ language: string | null
8
+ }
9
+ }
10
+ }
11
+
12
+ export { }
13
+
@@ -0,0 +1,41 @@
1
+ import type { TitleData } from "./title"
2
+
3
+ export type LinkInjection = {
4
+ sid: string
5
+ title: string
6
+ sentence: string
7
+ playpilot_url: string
8
+ key: string
9
+ title_details?: TitleData
10
+ inactive?: boolean
11
+ failed?: boolean
12
+ in_text?: boolean
13
+ after_article?: boolean
14
+ after_article_style?: 'modal_button' | 'playlinks' | null
15
+ manual?: boolean
16
+ removed?: boolean
17
+ duplicate?: boolean
18
+ }
19
+
20
+ export type LinkInjectionRanges = Record<string, { elementIndex: number, from: number, to: number }>
21
+
22
+ export type LinkInjectionResponse = {
23
+ injections_ready: boolean
24
+ page_updated: boolean
25
+ automation_enabled: boolean
26
+ injections_enabled: boolean
27
+ ai_running: boolean
28
+ ai_injections: LinkInjection[] | null
29
+ link_injections: LinkInjection[] | null
30
+ }
31
+
32
+ export type LinkInjectionTypes = {
33
+ aiInjections: LinkInjection[]
34
+ manualInjections: LinkInjection[]
35
+ }
36
+
37
+ export type ArticleMetaData = {
38
+ heading: string | null
39
+ modified_time: string | null
40
+ published_time: string | null
41
+ }
@@ -0,0 +1 @@
1
+ export type LanguageCode = 'en-US' | 'sv-SE' | 'dk-DK'
@@ -0,0 +1,13 @@
1
+ export type Participant = {
2
+ sid: string
3
+ name: string
4
+ birth_date: string
5
+ death_date: string | null
6
+ jobs: Job[]
7
+ image: string | null
8
+ image_uuid: string | null
9
+ gender: string
10
+ character: string | null
11
+ }
12
+
13
+ export type Job = 'actor' | 'writer' | 'director'
@@ -0,0 +1,11 @@
1
+ export type PlaylinkData = {
2
+ sid: string
3
+ name: string
4
+ url: string
5
+ logo_url: string
6
+ extra_info: {
7
+ category: PlaylinkCategory
8
+ }
9
+ }
10
+
11
+ export type PlaylinkCategory = 'SVOD' | 'BUY' | 'RENT' | 'TVOD'
@@ -0,0 +1,6 @@
1
+ export type Position = {
2
+ x: number
3
+ y: number
4
+ }
5
+
6
+ export type Alignment = 'bottom' | 'center'
@@ -0,0 +1,23 @@
1
+ import type { Participant } from "./participant"
2
+ import type { PlaylinkData } from "./playlink"
3
+
4
+ export type TitleData = {
5
+ sid: string
6
+ slug: string
7
+ poster_uuid: string
8
+ standing_poster_uuid: string
9
+ genres: string[]
10
+ year: number
11
+ imdb_score: number
12
+ type: string
13
+ providers: PlaylinkData[]
14
+ description: string
15
+ small_poster: string
16
+ medium_poster: string
17
+ standing_poster: string
18
+ title: string
19
+ original_title: string
20
+ length?: number
21
+ participants?: Participant[]
22
+ blurb?: string
23
+ }
@@ -0,0 +1,7 @@
1
+ export type User = {
2
+ username: string
3
+ profile: {
4
+ display_name: string
5
+ profile_photo: string
6
+ }
7
+ }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Get the full path, including host and protocol, but excluding url params
3
- * @returns {string}
4
3
  */
5
- export function getFullUrlPath() {
4
+ export function getFullUrlPath(): string {
6
5
  return location.protocol + '//' + location.host + location.pathname
7
6
  }
@@ -1,5 +1,5 @@
1
- <script>
2
- import { browser } from '$app/environment'
1
+ <script lang="ts">
2
+ import { browser } from '$app/environment'
3
3
 
4
4
  /**
5
5
  * This layout file is for development purposes only and will not be compiled with the final script.
@@ -10,7 +10,7 @@
10
10
  // This is used to remove the scoped classes in Svelte in order to prevent articles
11
11
  // from having to be re-generated between different HMR builds.
12
12
  // This is purely for development.
13
- const noClass = (/** @type {HTMLElement} */ node) => {
13
+ const noClass = (node: HTMLElement) => {
14
14
  node.classList.forEach(e => {if (e.startsWith('s-')) node.classList.remove(e)})
15
15
  }
16
16
 
@@ -1,4 +1,4 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { onMount } from 'svelte'
3
3
  import { pollLinkInjections } from '$lib/api'
4
4
  import { clearLinkInjections, getLinkInjectionElements, getLinkInjectionsParentElement, injectLinksInDocument, separateLinkInjectionTypes } from '$lib/linkInjection'
@@ -8,21 +8,18 @@
8
8
  import { authorize, isEditorialModeEnabled } from '$lib/auth'
9
9
  import TitleModal from './components/TitleModal.svelte'
10
10
  import Editor from './components/Editorial/Editor.svelte'
11
+ import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
11
12
 
12
13
  const parentElement = getLinkInjectionsParentElement()
13
- /** @type {HTMLElement[]} */
14
- const elements = getLinkInjectionElements(parentElement)
14
+ const elements: HTMLElement[] = getLinkInjectionElements(parentElement)
15
15
  const htmlString = elements.map(p => p.outerHTML).join('')
16
16
  const isEditorialMode = isEditorialModeEnabled()
17
17
 
18
- /** @type {LinkInjectionResponse | null} */
19
- let response = $state(null)
20
- /** @type {LinkInjection | null} */
21
- let activeInjection = $state(null)
18
+ let response: LinkInjectionResponse | null = $state(null)
19
+ let activeInjection: LinkInjection | null = $state(null)
22
20
  let authorized = $state(false)
23
21
  let loading = $state(true)
24
- /** @type {LinkInjection[]} */
25
- let linkInjections = $state([])
22
+ let linkInjections: LinkInjection[] = $state([])
26
23
  let editor = $state()
27
24
 
28
25
  // @ts-ignore It's ok if the response is empty
@@ -41,7 +38,7 @@
41
38
  return () => clearLinkInjections()
42
39
  })
43
40
 
44
- async function initialize() {
41
+ async function initialize(): Promise<void> {
45
42
  if (isEditorialMode) authorized = await authorize()
46
43
 
47
44
  // Only trying once when not in editorial mode to prevent late injections (as well as a ton of requests)
@@ -60,18 +57,17 @@
60
57
  if (!isEditorialMode) return
61
58
 
62
59
  const continuedResponse = await pollLinkInjections(getFullUrlPath(), htmlString, { requireCompletedResult: true })
60
+
61
+ // @ts-ignore
63
62
  editor.requestNewAIInjections(continuedResponse?.ai_injections || [])
64
63
  }
65
64
 
66
- function rerender() {
65
+ function rerender(): void {
67
66
  clearLinkInjections()
68
67
  inject(separateLinkInjectionTypes(linkInjections))
69
68
  }
70
69
 
71
- /**
72
- * @param {LinkInjectionTypes} injections
73
- */
74
- function inject(injections = { aiInjections, manualInjections }) {
70
+ function inject(injections: LinkInjectionTypes = { aiInjections, manualInjections }): void {
75
71
  if (!aiInjections.length && !manualInjections.length) return
76
72
 
77
73
  // Get filtered injections as they are shown on the page.
@@ -80,8 +76,7 @@
80
76
  if (JSON.stringify(filteredInjections) !== JSON.stringify(linkInjections)) linkInjections = filteredInjections
81
77
  }
82
78
 
83
- /** @param {LinkInjection} injection */
84
- function setTarget(injection) {
79
+ function setTarget(injection: LinkInjection): void {
85
80
  activeInjection = injection
86
81
  }
87
82
  </script>
@@ -1,26 +1,27 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
3
3
  import { t } from '$lib/localization'
4
4
  import { track } from '$lib/tracking'
5
+ import type { LinkInjection } from '$lib/types/injection'
6
+ import type { TitleData } from '$lib/types/title'
7
+
8
+ interface Props {
9
+ linkInjections: LinkInjection[],
10
+ // eslint-disable-next-line no-unused-vars
11
+ onclickmodal?: (linkInjection: LinkInjection) => void
12
+ }
5
13
 
6
- /** @type {{ linkInjections: LinkInjection[], onclickmodal?: (linkInjection: LinkInjection) => void }} */
7
14
  // eslint-disable-next-line no-unused-vars
8
- const { linkInjections, onclickmodal = (linkInjection) => null } = $props()
15
+ const { linkInjections, onclickmodal = (linkInjection) => null }: Props = $props()
9
16
 
10
- /**
11
- * @param {TitleData} title The full title data of the clicked playlink
12
- * @param {string} playlink Name of the clicked playlink
13
- */
14
- function onclick(title, playlink) {
17
+ function onclick(title: TitleData, playlink: string): void {
15
18
  track(TrackingEvent.AfterArticlePlaylinkClick, title, { playlink })
16
19
  }
17
20
 
18
21
  /**
19
22
  * Open a modal for the given injection and track the click
20
- * @param {TitleData} title The full title data of the title the modal button was clicked for
21
- * @param {LinkInjection} linkInjection
22
23
  */
23
- function openModal(title, linkInjection) {
24
+ function openModal(title: TitleData, linkInjection: LinkInjection): void {
24
25
  track(TrackingEvent.AfterArticleModalButtonClick, title)
25
26
  onclickmodal(linkInjection)
26
27
  }
@@ -38,7 +39,7 @@
38
39
  "{title}" {t('Is Available To Stream')}
39
40
 
40
41
  <span>
41
- <button onclick={() => openModal(/** @type {TitleData} */ (title_details), linkInjection)}>
42
+ <button onclick={() => openModal(title_details as TitleData, linkInjection)}>
42
43
  {t('View Streaming Options')}
43
44
  </button>
44
45
  </span>
@@ -49,7 +50,7 @@
49
50
  {i === playlinks.length - 1 ? `, ${t('And')}` : ','}
50
51
  {/if}
51
52
 
52
- <a onclick={() => onclick(/** @type {TitleData} */ (title_details), name)} href={url} target="_blank">
53
+ <a onclick={() => onclick(title_details as TitleData, name)} href={url} target="_blank">
53
54
  {name}
54
55
  </a>
55
56
  {/each}.
@@ -1,20 +1,24 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { fly } from 'svelte/transition'
3
3
  import IconDots from './Icons/IconDots.svelte'
4
+ import type { Snippet } from 'svelte'
4
5
 
5
- /** @type {{ ariaLabel: string, children: import('svelte').Snippet }} */
6
- const { ariaLabel, children } = $props()
6
+ interface Props {
7
+ ariaLabel: string
8
+ children: Snippet
9
+ }
10
+
11
+ const { ariaLabel, children }: Props = $props()
7
12
 
8
13
  let active = $state(false)
9
- /** @type {HTMLElement | null} */
10
- let buttonElement = $state(null)
14
+ let buttonElement: HTMLElement | null = $state(null)
11
15
 
12
16
  /**
13
17
  * Close the context menu when clicking anything but the toggle button.
14
18
  * @param {MouseEvent} event
15
19
  */
16
- function closeOnOutsideClick(event) {
17
- const target = /** @type {HTMLElement} */ (event.target)
20
+ function closeOnOutsideClick(event: MouseEvent): void {
21
+ const target = event.target as HTMLElement
18
22
 
19
23
  if (target === buttonElement || buttonElement?.contains(target)) return
20
24
 
@@ -1,9 +1,14 @@
1
- <script>
2
- /** @type {{ text: string, blurb?: string, limit?: number }} */
3
- const { text, blurb = '', limit = 200 } = $props()
1
+ <script lang="ts">
2
+ interface Props {
3
+ text: string
4
+ blurb?: string
5
+ limit?: number
6
+ }
7
+
8
+ const { text, blurb = '', limit = 200 }: Props = $props()
4
9
 
5
10
  let expanded = $state(false)
6
- let limitedText = $derived(text.slice(0, limit))
11
+ const limitedText = $derived(text.slice(0, limit))
7
12
  </script>
8
13
 
9
14
  <div>
@@ -1,21 +1,24 @@
1
- <script>
1
+ <script lang="ts">
2
+ import type { LinkInjection } from '$lib/types/injection'
2
3
  import IconAi from '../Icons/IconAi.svelte'
3
4
 
4
- /** @type {{ onadd: (injections: LinkInjection[]) => void }} */
5
- let { onadd } = $props()
5
+ interface Props {
6
+ // eslint-disable-next-line no-unused-vars
7
+ onadd: (injections: LinkInjection[]) => void
8
+ }
9
+
10
+ let { onadd }: Props = $props()
6
11
 
7
12
  let running = $state(true)
8
- /** @type {LinkInjection[]} */
9
- let injectionsToBeInserted = $state([])
13
+ let injectionsToBeInserted: LinkInjection[] = $state([])
10
14
  let dismissed = $state(false)
11
15
 
12
16
  /**
13
17
  * This is called from the Editor component when AI links are ready.
14
18
  * From here we determine what to show the user. Either we show them an option
15
19
  * to inject new links, or we tell them no new injections were found.
16
- * @param {LinkInjection[]} injections
17
20
  */
18
- export function notifyUserOfNewState(injections) {
21
+ export function notifyUserOfNewState(injections: LinkInjection[]) {
19
22
  running = false
20
23
  injectionsToBeInserted = injections
21
24
  }
@@ -1,6 +1,11 @@
1
- <script>
2
- /** @type {{ children: import('svelte').Snippet }} */
3
- const { children } = $props()
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface Props {
5
+ children: Snippet
6
+ }
7
+
8
+ const { children }: Props = $props()
4
9
  </script>
5
10
 
6
11
  <div class="alert">
@@ -1,25 +1,27 @@
1
- <script>
1
+ <script lang="ts">
2
+ import type { Position } from '$lib/types/position'
2
3
  import { onMount } from 'svelte'
3
4
 
4
- /** @type {{ element: HTMLElement, position: Position, limit?: Position, onchange?: (position: Position) => void }} */
5
- let { element, position = { x: 0, y: 0 }, limit = { x: 0, y: 0 }, onchange = () => null } = $props()
5
+ interface Props {
6
+ element: HTMLElement,
7
+ position: Position,
8
+ limit?: Position,
9
+ // eslint-disable-next-line no-unused-vars
10
+ onchange?: (position: Position) => void
11
+ }
12
+
13
+ let { element, position = { x: 0, y: 0 }, limit = { x: 0, y: 0 }, onchange = () => null }: Props = $props()
6
14
 
7
15
  let isDragging = false
8
16
 
9
- /** @type {Position} */
10
- let dragStartPosition = { x: 0, y: 0 }
11
- /** @type {Position} */
12
- let elementStartPosition = position
17
+ let dragStartPosition: Position = { x: 0, y: 0 }
18
+ let elementStartPosition: Position = position
13
19
 
14
20
  onMount(() => {
15
21
  setPosition(position.x, position.y)
16
22
  })
17
23
 
18
- /**
19
- * @param {number} x
20
- * @param {number} y
21
- */
22
- function setPosition(x, y) {
24
+ function setPosition(x: number, y: number): void {
23
25
  const windowWidth = window.innerWidth
24
26
  const windowHeight = window.innerHeight
25
27
  const elementWidth = element.clientWidth
@@ -36,11 +38,7 @@
36
38
  onchange(position)
37
39
  }
38
40
 
39
- /**
40
- * @param {MouseEvent | TouchEvent} event
41
- * @returns {Position}
42
- */
43
- function getPositionFromEvent(event) {
41
+ function getPositionFromEvent(event: MouseEvent | TouchEvent): Position {
44
42
  return {
45
43
  // @ts-ignore
46
44
  x: event.pageX || event.touches?.[0].pageX || 0,
@@ -49,20 +47,14 @@
49
47
  }
50
48
  }
51
49
 
52
- /**
53
- * @param {MouseEvent | TouchEvent} event
54
- */
55
- function start(event) {
50
+ function start(event: MouseEvent | TouchEvent): void {
56
51
  isDragging = true
57
52
 
58
53
  dragStartPosition = getPositionFromEvent(event)
59
54
  elementStartPosition = position
60
55
  }
61
56
 
62
- /**
63
- * @param {MouseEvent | TouchEvent} event
64
- */
65
- function move(event) {
57
+ function move(event: MouseEvent | TouchEvent): void {
66
58
  if (!isDragging) return
67
59
 
68
60
  const dragCurrentPosition = getPositionFromEvent(event)
@@ -75,7 +67,7 @@
75
67
  setPosition(newX, newY)
76
68
  }
77
69
 
78
- function end() {
70
+ function end(): void {
79
71
  if (!isDragging) return
80
72
 
81
73
  isDragging = false