@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "2.0.5",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -18,7 +18,9 @@
18
18
  "@sveltejs/kit": "^2.0.0",
19
19
  "@sveltejs/vite-plugin-svelte": "^4.0.0",
20
20
  "@testing-library/svelte": "^5.2.6",
21
- "eslint": "^9.7.0",
21
+ "@typescript-eslint/eslint-plugin": "^8.32.1",
22
+ "@typescript-eslint/parser": "^8.32.1",
23
+ "eslint": "^9.27.0",
22
24
  "eslint-config-prettier": "^9.1.0",
23
25
  "eslint-plugin-svelte": "^2.36.0",
24
26
  "globals": "^15.0.0",
@@ -30,6 +32,7 @@
30
32
  "svelte-check": "^4.0.0",
31
33
  "svelte-preprocess": "^6.0.3",
32
34
  "typescript": "^5.0.0",
35
+ "typescript-eslint": "^8.32.1",
33
36
  "vite": "^5.0.3",
34
37
  "vite-plugin-css-injected-by-js": "^3.5.2",
35
38
  "vitest": "^2.1.8"
@@ -3,22 +3,21 @@ import { apiBaseUrl } from './constants'
3
3
  import { stringToHash } from './hash'
4
4
  import { getLanguage } from './localization'
5
5
  import { getPageMetaData } from './meta'
6
+ import type { LinkInjectionResponse, LinkInjection } from './types/injection'
6
7
  import { getFullUrlPath } from './url'
7
8
 
8
- /** @type {NodeJS.Timeout | number | null} */
9
- let pollTimeout = null
9
+ let pollTimeout: ReturnType<typeof setTimeout> | null = null
10
10
 
11
11
  /**
12
12
  * Fetch link injections for a URL.
13
- * @param {string} url URL of the given article
14
- * @param {string} html HTML to be crawled
15
- * @param {object} options
16
- * @param {boolean} [options.automation] Enable automation, disable when inserting into editorial
17
- * @param {string} [options.hash] unique key to identify the HTML
18
- * @param {object} [options.params] Any rest params to include in the request body
19
- * @returns {Promise<LinkInjectionResponse>}
13
+ * @param url URL of the given article
14
+ * @param html HTML to be crawled
15
+ * @param options
16
+ * @param [options.automation] Enable automation, disable when inserting into editorial
17
+ * @param [options.hash] unique key to identify the HTML
18
+ * @param [options.params] Any rest params to include in the request body
20
19
  */
21
- export async function fetchLinkInjections(url, html, { hash = stringToHash(html), params = {} } = {}) {
20
+ export async function fetchLinkInjections(url: string, html: string, { hash = stringToHash(html), params = {} }: { automation?: boolean; hash?: string; params?: object } = {}): Promise<LinkInjectionResponse> {
22
21
  const headers = new Headers({ 'Content-Type': 'application/json' })
23
22
  const apiToken = getApiToken()
24
23
  const isEditorialMode = isEditorialModeEnabled() ? await authorize() : false
@@ -49,11 +48,10 @@ export async function fetchLinkInjections(url, html, { hash = stringToHash(html)
49
48
  /**
50
49
  * Link injections take a while to be ready. During this time we poll the endpoint until it returns the result we want.
51
50
  * The results return `injections_ready=false` while the injections are not yet ready.
52
- * @param {string} url URL of the given article
53
- * @param {string} html HTML to be crawled
54
- * @returns {Promise<LinkInjectionResponse>}
51
+ * @param url URL of the given article
52
+ * @param html HTML to be crawled
55
53
  */
56
- export async function pollLinkInjections(url, html, { requireCompletedResult = false, pollInterval = 3000, maxTries = 600 } = {}) {
54
+ export async function pollLinkInjections(url: string, html: string, { requireCompletedResult = false, pollInterval = 3000, maxTries = 600 } = {}): Promise<LinkInjectionResponse> {
57
55
  let hash = stringToHash(html)
58
56
  let currentTry = 0
59
57
 
@@ -63,11 +61,10 @@ export async function pollLinkInjections(url, html, { requireCompletedResult = f
63
61
 
64
62
  /**
65
63
  * Polls the endpoint recursively until it is ready.
66
- * @param {Function} resolve Injections are ready and returned as expected
67
- * @param {Function} reject Injections are not yet ready
68
- * @returns {Promise<void>}
64
+ * @param resolve Injections are ready and returned as expected
65
+ * @param reject Injections are not yet ready
69
66
  */
70
- const poll = async (resolve, reject) => {
67
+ const poll = async (resolve: Function, reject: Function): Promise<void> => {
71
68
  try {
72
69
  const response = await fetchLinkInjections(url, html, { hash })
73
70
 
@@ -94,19 +91,13 @@ export async function pollLinkInjections(url, html, { requireCompletedResult = f
94
91
  return new Promise(poll)
95
92
  }
96
93
 
97
- /**
98
- * @param {LinkInjection[]} linkInjections
99
- * @param {string} html HTML to be crawled
100
- * @returns {Promise<LinkInjection[]>}
101
- */
102
- export async function saveLinkInjections(linkInjections, html) {
103
- // @ts-ignore
94
+ export async function saveLinkInjections(linkInjections: LinkInjection[], html: string): Promise<LinkInjection[]> {
104
95
  const selector = window.PlayPilotLinkInjections?.selector
105
96
 
106
97
  // Only save manual injections, AI injections should be left intact.
107
- const filteredLinkInjections = linkInjections.filter(i => i.manual)
98
+ const filteredLinkInjections = linkInjections.filter((i: LinkInjection) => i.manual)
108
99
 
109
- const newLinkInjections = filteredLinkInjections.map((/** @type {any} */linkInjection) => ({
100
+ const newLinkInjections = filteredLinkInjections.map((linkInjection: LinkInjection) => ({
110
101
  sid: linkInjection.sid,
111
102
  title: linkInjection.title,
112
103
  sentence: linkInjection.sentence,
@@ -115,7 +106,7 @@ export async function saveLinkInjections(linkInjections, html) {
115
106
  after_article_style: linkInjection.after_article_style || null,
116
107
  in_text: linkInjection.in_text ?? true,
117
108
  inactive: !!linkInjection.inactive,
118
- removed: !!linkInjection.removed,
109
+ removed: !!linkInjection.removed
119
110
  }))
120
111
 
121
112
  const response = await fetchLinkInjections(getFullUrlPath(), html, {
@@ -135,26 +126,23 @@ export async function saveLinkInjections(linkInjections, html) {
135
126
  /**
136
127
  * Insert random keys into link injections. These are used to identify the links on the page.
137
128
  * We can't just use SIDs, as a page might include multiple links of the same title
138
- * @param {LinkInjection[]} linkInjections
139
- * @returns {LinkInjection[]}
140
129
  */
141
- function insertRandomKeys(linkInjections) {
142
- return linkInjections.map(linkInjection => ({
130
+ function insertRandomKeys(linkInjections: LinkInjection[]): LinkInjection[] {
131
+ return linkInjections.map((linkInjection: LinkInjection) => ({
143
132
  ...linkInjection,
144
- key: generateInjectionKey(linkInjection.sid),
145
- }))
133
+ key: generateInjectionKey(linkInjection.sid)
134
+ }));
146
135
  }
147
136
 
148
137
  /**
149
138
  * Generate a key for a linkInjection. This is used to match an injection to it's element on the page.
150
- * @param {string} sid Sid of the title linked to the injection
151
- * @returns {string} Random string preprending with title sid.
139
+ * @param sid Sid of the title linked to the injection
140
+ * @returns Random string preprending with title sid.
152
141
  */
153
- export function generateInjectionKey(sid) {
142
+ export function generateInjectionKey(sid: string): string {
154
143
  return sid + '-' + (Math.random() + 1).toString(36).substring(7)
155
144
  }
156
145
 
157
146
  export function getApiToken() {
158
- // @ts-ignore
159
147
  return window.PlayPilotLinkInjections?.token
160
148
  }
@@ -1,10 +1,8 @@
1
1
 
2
2
  /**
3
3
  * Returns the largest number in an array of numbers. Returns 0 if the array has no entries.
4
- * @param {number[]} array
5
- * @return {number}
6
4
  */
7
- export function getLargestValueInArray(array) {
5
+ export function getLargestValueInArray(array: number[]): number {
8
6
  let largest = 0
9
7
 
10
8
  for (let i = 0; i < array.length; i++) {
@@ -6,10 +6,10 @@ const urlParam = 'articleReplacementEditToken'
6
6
 
7
7
  /**
8
8
  * Authorize the user
9
- * @param {string} href The current window.location.href
10
- * @returns {Promise<boolean>} Whether the user is authorized or not
9
+ * @param href The current window.location.href
10
+ * @returns Whether the user is authorized or not
11
11
  */
12
- export async function authorize(href = window.location.href) {
12
+ export async function authorize(href: string = window.location.href): Promise<boolean> {
13
13
  const headers = new Headers({ 'Content-Type': 'application/json' })
14
14
 
15
15
  try {
@@ -41,10 +41,10 @@ export async function authorize(href = window.location.href) {
41
41
 
42
42
  /**
43
43
  * Get the auth token from the URL, a stored cookie, or from the window object
44
- * @param {string} [href] URL that the param is extracted from
45
- * @returns {string} Auth token
44
+ * @param [href] URL that the param is extracted from
45
+ * @returns Auth token
46
46
  */
47
- export function getAuthToken(href = '') {
47
+ export function getAuthToken(href: string = ''): string {
48
48
  // @ts-ignore
49
49
  const configToken = window?.PlayPilotLinkInjections?.editorial_token
50
50
  if (configToken) return configToken
@@ -62,7 +62,7 @@ export function getAuthToken(href = '') {
62
62
  * Set auth cookie equal to given value
63
63
  * @param {string} value The auth token value
64
64
  */
65
- function setAuthCookie(value) {
65
+ function setAuthCookie(value: string) {
66
66
  const time = new Date()
67
67
  const days = 30
68
68
 
@@ -75,10 +75,8 @@ function setAuthCookie(value) {
75
75
  /**
76
76
  * Returns whether or not the user has requested editorial mode to be enabled.
77
77
  * This won't enable editorial mode by itself, as that also requires authentication.
78
- * @returns {boolean}
79
78
  */
80
- export function isEditorialModeEnabled() {
81
- // @ts-ignore
79
+ export function isEditorialModeEnabled(): boolean {
82
80
  const windowToken = window?.PlayPilotLinkInjections?.editorial_token
83
81
  return new URLSearchParams(window.location.search).get('playpilot-editorial-mode') === 'true' || !!windowToken
84
82
  }
@@ -1,5 +1,8 @@
1
- /** @type {Participant[]} */
2
- export const participants = [
1
+ import type { LinkInjection } from "./types/injection"
2
+ import type { Participant } from "./types/participant"
3
+ import type { TitleData } from "./types/title"
4
+
5
+ export const participants: Participant[] = [
3
6
  {
4
7
  sid: 'pr5C5W',
5
8
  name: 'James Franco',
@@ -68,8 +71,7 @@ export const participants = [
68
71
  },
69
72
  ]
70
73
 
71
- /** @type {TitleData} */
72
- export const title = {
74
+ export const title: TitleData = {
73
75
  sid: 'tig9r9F',
74
76
  slug: 'dune-prophecy-2024-series',
75
77
  poster_uuid: '61feb4b0a58f11efb0b50a58a9feac02',
@@ -107,8 +109,7 @@ export const title = {
107
109
  participants,
108
110
  }
109
111
 
110
- /** @type {LinkInjection[]} */
111
- export const linkInjections = [{
112
+ export const linkInjections: LinkInjection[] = [{
112
113
  sid: '1',
113
114
  title: 'Quan',
114
115
  sentence: 'In an interview with Epire Magazine, Quan reveals he quested starring in Love Hurts',
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Turns any string into a very short hash. This is super basic and not super reliable, but it's good enough for our purpose.
3
- * @param {string} string
4
- * @returns {string}
3
+ * @param string
5
4
  */
6
- export function stringToHash(string) {
5
+ export function stringToHash(string: string): string {
7
6
  let hash = 0
8
7
 
9
8
  for (let i = 0; i < string.length; i++) {
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Returns a string with decoded html characters. For instance & -> &amp;
3
- * @param {string} string
4
3
  */
5
- export function encodeHtmlEntities(string) {
4
+ export function encodeHtmlEntities(string: string): string {
6
5
  const tempElement = document.createElement('div')
7
6
  tempElement.textContent = string
8
7
 
@@ -11,9 +10,8 @@ export function encodeHtmlEntities(string) {
11
10
 
12
11
  /**
13
12
  * Returns a string with encoded html characters. For instance &amp; -> &
14
- * @param {string} string
15
13
  */
16
- export function decodeHtmlEntities(string) {
14
+ export function decodeHtmlEntities(string: string): string {
17
15
  const tempElement = document.createElement('div')
18
16
  tempElement.innerHTML = string
19
17
 
@@ -3,28 +3,28 @@ import TitlePopover from '../routes/components/TitlePopover.svelte'
3
3
  import AfterArticlePlaylinks from '../routes/components/AfterArticlePlaylinks.svelte'
4
4
  import { cleanPhrase, findTextNodeContaining, isNodeInLink, replaceStartingFrom } from './text'
5
5
  import { getLargestValueInArray } from './array'
6
+ import { decodeHtmlEntities } from './html'
7
+ import type { LinkInjection, LinkInjectionTypes, LinkInjectionRanges } from './types/injection'
6
8
 
7
9
  const keyDataAttribute = 'data-playpilot-injection-key'
8
10
  const keySelector = `[${keyDataAttribute}]`
9
11
 
10
- /** @type {Record<string, { injection: LinkInjection, component: object }>} */
11
- const activePopovers = {}
12
- /** @type {object | null} */
13
- let afterArticlePlaylinkInsertedComponent = null
12
+ const activePopovers: Record<string, { injection: LinkInjection; component: object }> = {}
13
+
14
+ let afterArticlePlaylinkInsertedComponent: object | null = null
14
15
 
15
16
  /**
16
17
  * Return a list of all valid text containing elements that may get injected into.
17
18
  * This excludes duplicates, empty elements, links, buttons, and header tags.
18
- * @param {HTMLElement} parentElement
19
- * @returns {HTMLElement[]} A list of all HTMLElements that contain text, without repeating the same text in
19
+ * @returns A list of all HTMLElements that contain text, without repeating the same text in
20
20
  */
21
- export function getLinkInjectionElements(parentElement) {
21
+ export function getLinkInjectionElements(parentElement: HTMLElement): HTMLElement[] {
22
22
  let validElements = []
23
23
 
24
24
  let remainingChildren = [parentElement]
25
25
 
26
26
  while (remainingChildren.length > 0) {
27
- const element = /** @type {HTMLElement} */ (remainingChildren.pop())
27
+ const element = remainingChildren.pop() as HTMLElement
28
28
 
29
29
  // Ignore links, buttons, and headers
30
30
  if (/^(A|BUTTON|SCRIPT|NOSCRIPT|STYLE|IFRAME|H[1-6])$/.test(element.tagName)) continue
@@ -41,9 +41,9 @@ export function getLinkInjectionElements(parentElement) {
41
41
  continue
42
42
  }
43
43
 
44
- const children = /** @type {HTMLElement[]} */ (Array.from(element.children))
44
+ const children = Array.from(element.children) as HTMLElement[]
45
45
  for (let i = children.length - 1; i >= 0; i--) {
46
- remainingChildren.push(children[i])
46
+ remainingChildren.push(children[i] as HTMLElement)
47
47
  }
48
48
  }
49
49
 
@@ -54,9 +54,8 @@ export function getLinkInjectionElements(parentElement) {
54
54
  * Get the parent selector that will be used to find the link injections in.
55
55
  * This selector is passed when the script is initialized.
56
56
  * If no selector is passed a default is returned instead.
57
- * @returns {HTMLElement}
58
57
  */
59
- export function getLinkInjectionsParentElement() {
58
+ export function getLinkInjectionsParentElement(): HTMLElement {
60
59
  // @ts-ignore
61
60
  const selector = window.PlayPilotLinkInjections?.selector
62
61
 
@@ -65,7 +64,7 @@ export function getLinkInjectionsParentElement() {
65
64
  const escaped = selector.replace(/(:)/g, '\\$1')
66
65
  const element = document.querySelector(escaped)
67
66
 
68
- if (element) return element
67
+ if (element) return element as HTMLElement
69
68
  }
70
69
 
71
70
  return document.querySelector('article') || document.querySelector('main') || document.body
@@ -73,12 +72,9 @@ export function getLinkInjectionsParentElement() {
73
72
 
74
73
  /**
75
74
  * Replace all found injections within all given elements on the page
76
- * @param {HTMLElement[]} elements
77
- * @param {(LinkInjection: LinkInjection) => void} onclick
78
- * @param {LinkInjectionTypes} injections
79
- * @returns {LinkInjection[]} Returns an array of injections with injections that failed to be inserted marked as `failed`.
75
+ * @returns Returns an array of injections with injections that failed to be inserted marked as `failed`.
80
76
  */
81
- export function injectLinksInDocument(elements, onclick, injections = { aiInjections: [], manualInjections: [] }) {
77
+ export function injectLinksInDocument(elements: HTMLElement[], onclick: (LinkInjection: LinkInjection) => void, injections: LinkInjectionTypes = { aiInjections: [], manualInjections: [] }): LinkInjection[] {
82
78
  const mergedInjections = mergeInjectionTypes(injections)
83
79
 
84
80
  // Find injection in text content of all elements together, ignore potential HTML elements.
@@ -90,8 +86,7 @@ export function injectLinksInDocument(elements, onclick, injections = { aiInject
90
86
  return cleanPhrase(fullText).includes(cleanPhrase(i.sentence))
91
87
  })
92
88
 
93
- /** @type {LinkInjectionRanges} */
94
- const ranges = {}
89
+ const ranges: LinkInjectionRanges = {}
95
90
 
96
91
  for (const injection of foundInjections) {
97
92
  const elementIndex = elements.findIndex(element => cleanPhrase(element.innerText).includes(cleanPhrase(injection.sentence)))
@@ -157,10 +152,9 @@ export function injectLinksInDocument(elements, onclick, injections = { aiInject
157
152
  * Add all used CSS variables to a data attribute. This data attribute is then used for selectors that for each
158
153
  * individual CSS variable. This is done this way so that CSS variables are only set when they are used.
159
154
  * Using the variables straight up or with a fallback value would not allow them to use their default page styling.
160
- * @returns {void}
161
155
  */
162
- function addCSSVariablesToLinks() {
163
- const createdLinkElements = /** @type {HTMLElement[]} */ (Array.from(document.querySelectorAll(`${keySelector} a`)))
156
+ function addCSSVariablesToLinks(): void {
157
+ const createdLinkElements = Array.from(document.querySelectorAll(`${keySelector} a`)) as HTMLElement[]
164
158
 
165
159
  const variables = [
166
160
  '--playpilot-injection-text-color',
@@ -183,14 +177,11 @@ function addCSSVariablesToLinks() {
183
177
 
184
178
  /**
185
179
  * Add event listeners to all injected links. These events are for both the popover and the modal.
186
- * @param {LinkInjection[]} injections
187
- * @param {function} onclick
188
- * @returns {void}
189
180
  */
190
- function addLinkInjectionEventListeners(injections, onclick) {
181
+ function addLinkInjectionEventListeners(injections: LinkInjection[], onclick: (injection: LinkInjection) => void): void {
191
182
  // Open modal on click
192
183
  window.addEventListener('click', (event) => {
193
- const target = /** @type {HTMLElement | null} */ (event.target)
184
+ const target = event.target as HTMLElement | null
194
185
  if (!target?.parentElement) return
195
186
 
196
187
  const key = target.parentElement.getAttribute(keyDataAttribute)
@@ -202,11 +193,10 @@ function addLinkInjectionEventListeners(injections, onclick) {
202
193
  openLinkModal(event, injection, onclick)
203
194
  })
204
195
 
205
- const createdInjectionElements = document.querySelectorAll(keySelector)
196
+ const createdInjectionElements = Array.from(document.querySelectorAll(keySelector)) as HTMLElement[]
206
197
 
207
198
  // Open and close popover on mouseenter/mouseleave
208
199
  createdInjectionElements.forEach((injectionElement) => {
209
- // @ts-ignore
210
200
  const key = injectionElement.dataset.playpilotInjectionKey
211
201
  const injection = injections.find(injection => key === injection.key)
212
202
 
@@ -221,12 +211,8 @@ function addLinkInjectionEventListeners(injections, onclick) {
221
211
  /**
222
212
  * Prevent default click and run onclick from parent. Ignore clicks that used modifier keys or that were not left click.
223
213
  * The event is not fired when the click happens from inside a popover.
224
- * @param {MouseEvent} event
225
- * @param {LinkInjection} injection
226
- * @param {function} onclick
227
- * @returns {void}
228
214
  */
229
- function openLinkModal(event, injection, onclick) {
215
+ function openLinkModal(event: MouseEvent, injection: LinkInjection, onclick: (injection: LinkInjection) => void): void {
230
216
  if (event.ctrlKey || event.metaKey || event.button !== 0) return
231
217
 
232
218
  event.preventDefault()
@@ -238,28 +224,24 @@ function openLinkModal(event, injection, onclick) {
238
224
  /**
239
225
  * When a link is hovered, it is shown as a popover. The component is mounted when a mouse enters the link,
240
226
  * and removed when clicked or on mouseleave.
241
- * @param {MouseEvent} event
242
- * @param {LinkInjection} injection
243
227
  */
244
- function openLinkPopover(event, injection) {
228
+ function openLinkPopover(event: MouseEvent, injection: LinkInjection) {
245
229
  // Popover for this link was already open and was called again... for some reason
246
230
  if (activePopovers[injection.key]) return
247
231
 
248
232
  // Skip touch devices
249
233
  if (window.matchMedia('(pointer: coarse)').matches) return
250
234
 
251
- const target = /** @type {Element} */ (event.currentTarget)
252
- const popover = mount(TitlePopover, { target, props: { title: injection.title_details } })
235
+ const target = event.currentTarget as Element
236
+ const popover = mount(TitlePopover, { target, props: { title: injection.title_details! } })
253
237
 
254
238
  activePopovers[injection.key] = { injection, component: popover }
255
239
  }
256
240
 
257
241
  /**
258
242
  * Unmount the popover, removing it from the dom
259
- * @param {LinkInjection} injection
260
- * @param {boolean} outro
261
243
  */
262
- function destroyLinkPopover(injection, outro = true) {
244
+ function destroyLinkPopover(injection: LinkInjection, outro: boolean = true) {
263
245
  const popover = activePopovers[injection.key]
264
246
 
265
247
  if (!popover) return
@@ -270,11 +252,8 @@ function destroyLinkPopover(injection, outro = true) {
270
252
 
271
253
  /**
272
254
  * Insert AfterArticlePlaylinks after the last valid element.
273
- * @param {HTMLElement[]} elements
274
- * @param {LinkInjection[]} injections
275
- * @param {(linkInjection: LinkInjection) => void} onclickmodal
276
255
  */
277
- export function insertAfterArticlePlaylinks(elements, injections, onclickmodal) {
256
+ export function insertAfterArticlePlaylinks(elements: HTMLElement[], injections: LinkInjection[], onclickmodal: (linkInjection: LinkInjection) => void) {
278
257
  if (!injections.length) return
279
258
 
280
259
  const target = document.createElement('div')
@@ -284,7 +263,7 @@ export function insertAfterArticlePlaylinks(elements, injections, onclickmodal)
284
263
  afterArticlePlaylinkInsertedComponent = mount(AfterArticlePlaylinks, { target, props: { linkInjections: injections, onclickmodal } })
285
264
  }
286
265
 
287
- function clearAfterArticlePlaylinks() {
266
+ function clearAfterArticlePlaylinks(): void {
288
267
  if (!afterArticlePlaylinkInsertedComponent) return
289
268
 
290
269
  unmount(afterArticlePlaylinkInsertedComponent)
@@ -295,11 +274,11 @@ function clearAfterArticlePlaylinks() {
295
274
  /**
296
275
  * Clear link injections from the page
297
276
  */
298
- export function clearLinkInjections() {
277
+ export function clearLinkInjections(): void {
299
278
  Object.values(activePopovers).forEach(popover => destroyLinkPopover(popover.injection))
300
279
 
301
280
  const elements = document.querySelectorAll(keySelector)
302
- elements.forEach((element /** @type {HTMLAnchorElement} */) => element.outerHTML = element.textContent || '')
281
+ elements.forEach((element) => element.outerHTML = element.textContent || '')
303
282
 
304
283
  Object.values(activePopovers).forEach(({ injection }) => destroyLinkPopover(injection, false))
305
284
 
@@ -308,22 +287,18 @@ export function clearLinkInjections() {
308
287
 
309
288
  /**
310
289
  * Clear specific link injection from the page
311
- * @param {string} key Given of the injection to be removed from the page
290
+ * @param key Given of the injection to be removed from the page
312
291
  */
313
- export function clearLinkInjection(key) {
314
- /** @type {HTMLAnchorElement | null} */
315
- const element = document.querySelector(`[${keyDataAttribute}="${key}"]`)
292
+ export function clearLinkInjection(key: string): void {
293
+ const element: HTMLAnchorElement | null = document.querySelector(`[${keyDataAttribute}="${key}"]`)
316
294
  if (element) element.outerHTML = element.textContent || ''
317
295
  }
318
296
 
319
297
  /**
320
298
  * Sort injections by where they were inserted. First by their element index, second by where in the element the
321
299
  * injection was injected. Injections without range (after article injections or failed injection) go last.
322
- * @param {LinkInjection[]} injections
323
- * @param {LinkInjectionRanges} ranges
324
- * @returns {LinkInjection[]}
325
300
  */
326
- export function sortLinkInjectionsByRange(injections, ranges) {
301
+ export function sortLinkInjectionsByRange(injections: LinkInjection[], ranges: LinkInjectionRanges): LinkInjection[] {
327
302
  return injections.sort((a, b) => {
328
303
  const rangeA = ranges[a.key]
329
304
  const rangeB = ranges[b.key]
@@ -342,19 +317,15 @@ export function sortLinkInjectionsByRange(injections, ranges) {
342
317
 
343
318
  /**
344
319
  * Merge different injection types
345
- * @param {LinkInjectionTypes} injections
346
- * @returns {LinkInjection[]}
347
320
  */
348
- export function mergeInjectionTypes({ aiInjections, manualInjections }) {
321
+ export function mergeInjectionTypes({ aiInjections, manualInjections }: LinkInjectionTypes): LinkInjection[] {
349
322
  return [...aiInjections, ...manualInjections.map(i => ({ ...i, manual: true }))]
350
323
  }
351
324
 
352
325
  /**
353
326
  * Separate an array of flat injections into ai and manual arrays.
354
- * @param {LinkInjection[]} injections
355
- * @returns {LinkInjectionTypes}
356
327
  */
357
- export function separateLinkInjectionTypes(injections) {
328
+ export function separateLinkInjectionTypes(injections: LinkInjection[]): LinkInjectionTypes {
358
329
  return {
359
330
  aiInjections: injections.filter(i => !i.manual),
360
331
  manualInjections: injections.filter(i => i.manual),
@@ -363,37 +334,29 @@ export function separateLinkInjectionTypes(injections) {
363
334
 
364
335
  /**
365
336
  * Returns whether or not an injection would be valid for any sort of injection, text or after_article
366
- * @param {LinkInjection} injection
367
- * @returns {boolean}
368
337
  */
369
- export function isValidInjection(injection) {
338
+ export function isValidInjection(injection: LinkInjection): boolean {
370
339
  return !injection.inactive && !injection.removed && !injection.duplicate && !!injection.title_details
371
340
  }
372
341
 
373
342
  /**
374
343
  * Filter links for in-text injections, removing after article, inactive, removed, duplicate, and items without title_details
375
- * @param {LinkInjection[]} injections
376
- * @returns {LinkInjection[]}
377
344
  */
378
- export function filterInvalidInTextInjections(injections) {
345
+ export function filterInvalidInTextInjections(injections: LinkInjection[]): LinkInjection[] {
379
346
  return filterRemovedInjections(injections).filter(i => i.in_text !== false && isValidInjection(i))
380
347
  }
381
348
 
382
349
  /**
383
350
  * Filter links for after article injections, removing in-text only, inactive, removed, duplicate, and items without title_details
384
- * @param {LinkInjection[]} injections
385
- * @returns {LinkInjection[]}
386
351
  */
387
- export function filterInvalidAfterArticleInjections(injections) {
352
+ export function filterInvalidAfterArticleInjections(injections: LinkInjection[]): LinkInjection[] {
388
353
  return filterRemovedInjections(injections).filter(i => i.after_article === true && isValidInjection(i))
389
354
  }
390
355
 
391
356
  /**
392
357
  * Filter injections that were marked as removed or have an equivalent removed manual injections, soley based on the same sentence and title.
393
- * @param {LinkInjection[]} injections
394
- * @returns {LinkInjection[]}
395
358
  */
396
- export function filterRemovedInjections(injections) {
359
+ export function filterRemovedInjections(injections: LinkInjection[]): LinkInjection[] {
397
360
  return injections.filter(injection => {
398
361
  if (injection.removed) return false
399
362
  if (injection.manual && !injection.removed) return true
@@ -403,12 +366,8 @@ export function filterRemovedInjections(injections) {
403
366
 
404
367
  /**
405
368
  * Return whether or not an injection is also available as manual injection
406
- * @param {LinkInjection} injection
407
- * @param {number} injectionIndex
408
- * @param {LinkInjection[]} injections
409
- * @returns {boolean}
410
369
  */
411
- export function isAvailableAsManualInjection(injection, injectionIndex, injections) {
370
+ export function isAvailableAsManualInjection(injection: LinkInjection, injectionIndex: number, injections: LinkInjection[]): boolean {
412
371
  return injections.some((i, index) => {
413
372
  return injectionIndex !== index && i.manual && isEquivalentInjection(i, injection)
414
373
  })
@@ -416,10 +375,7 @@ export function isAvailableAsManualInjection(injection, injectionIndex, injectio
416
375
 
417
376
  /**
418
377
  * Returns whether or not 2 injections match in title and sentence
419
- * @param {LinkInjection} injection1
420
- * @param {LinkInjection} injection2
421
- * @returns {boolean}
422
378
  */
423
- export function isEquivalentInjection(injection1, injection2) {
379
+ export function isEquivalentInjection(injection1: LinkInjection, injection2: LinkInjection): boolean {
424
380
  return injection1.title === injection2.title && cleanPhrase(injection1.sentence) === cleanPhrase(injection2.sentence)
425
381
  }
@@ -1,24 +1,22 @@
1
- import { translations } from '$lib/data/translations'
2
- import { Language } from '$lib/enums/Language'
1
+ import { translations } from './data/translations'
2
+ import { Language } from './enums/Language'
3
+ import type { LanguageCode } from './types/language'
3
4
 
4
5
  /**
5
6
  * Super basic implementation to get a string for the given key and language
6
- * @param {string} key Key of the wanted translation
7
- * @param {LanguageCode} language Language code
8
- * @returns
9
7
  */
10
- export function t(key, language = getLanguage()) {
11
- // @ts-ignore It's ok if a key is not found.
8
+ export function t(key: string, language: LanguageCode = getLanguage()): string {
9
+ // @ts-ignore It's fine if it's undefined
12
10
  return translations[key]?.[language] || key
13
11
  }
14
12
 
15
13
  /**
16
14
  * @returns {LanguageCode}
17
15
  */
18
- export function getLanguage() {
16
+ export function getLanguage(): LanguageCode {
19
17
  // @ts-ignore
20
- const configLanguage = window.PlayPilotLinkInjections?.language
21
- const languageCodes = /** @type {string[]} */ (Object.values(Language))
18
+ const configLanguage = window.PlayPilotLinkInjections?.language as LanguageCode | undefined
19
+ const languageCodes = Object.values(Language) as LanguageCode[]
22
20
 
23
21
  if (configLanguage) {
24
22
  if (languageCodes.includes(configLanguage)) return configLanguage
@@ -26,10 +24,10 @@ export function getLanguage() {
26
24
  console.warn(`PlayPilot Link Injections: ${configLanguage} is not an accepted language`)
27
25
  }
28
26
 
29
- const documentLanguage = document.querySelector('html')?.getAttribute('lang')
27
+ const documentLanguage = document.querySelector('html')?.getAttribute('lang') as LanguageCode | undefined
30
28
 
31
29
  if (documentLanguage && languageCodes.includes(documentLanguage)) {
32
- return /** @type {LanguageCode} */ (documentLanguage)
30
+ return documentLanguage
33
31
  }
34
32
 
35
33
  return Language.English