@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.
- package/dist/link-injections.js +2 -2
- package/eslint.config.js +20 -0
- package/package.json +5 -2
- package/src/lib/{api.js → api.ts} +26 -38
- package/src/lib/{array.js → array.ts} +1 -3
- package/src/lib/{auth.js → auth.ts} +8 -10
- package/src/lib/{fakeData.js → fakeData.ts} +7 -6
- package/src/lib/{hash.js → hash.ts} +2 -3
- package/src/lib/{html.js → html.ts} +2 -4
- package/src/lib/{linkInjection.js → linkInjection.ts} +41 -85
- package/src/lib/{localization.js → localization.ts} +10 -12
- package/src/lib/{meta.js → meta.ts} +8 -18
- package/src/lib/{playlink.js → playlink.ts} +4 -5
- package/src/lib/{search.js → search.ts} +2 -3
- package/src/lib/{text.js → text.ts} +7 -19
- package/src/lib/{tracking.js → tracking.ts} +6 -4
- package/src/lib/types/global.d.ts +13 -0
- package/src/lib/types/injection.d.ts +41 -0
- package/src/lib/types/language.d.ts +1 -0
- package/src/lib/types/participant.d.ts +13 -0
- package/src/lib/types/playlink.d.ts +11 -0
- package/src/lib/types/position.d.ts +6 -0
- package/src/lib/types/title.d.ts +23 -0
- package/src/lib/types/user.d.ts +7 -0
- package/src/lib/{url.js → url.ts} +1 -2
- package/src/routes/+layout.svelte +3 -3
- package/src/routes/+page.svelte +12 -17
- package/src/routes/components/AfterArticlePlaylinks.svelte +14 -13
- package/src/routes/components/ContextMenu.svelte +11 -7
- package/src/routes/components/Description.svelte +9 -4
- package/src/routes/components/Editorial/AIIndicator.svelte +10 -7
- package/src/routes/components/Editorial/Alert.svelte +8 -3
- package/src/routes/components/Editorial/DragHandle.svelte +18 -26
- package/src/routes/components/Editorial/Editor.svelte +24 -23
- package/src/routes/components/Editorial/EditorItem.svelte +20 -18
- package/src/routes/components/Editorial/ManualInjection.svelte +16 -17
- package/src/routes/components/Editorial/PlaylinkTypeSelect.svelte +8 -4
- package/src/routes/components/Editorial/Search/TitleSearch.svelte +13 -17
- package/src/routes/components/Editorial/Switch.svelte +14 -4
- package/src/routes/components/Editorial/TextInput.svelte +10 -3
- package/src/routes/components/Genres.svelte +8 -5
- package/src/routes/components/Icons/IconAlign.svelte +8 -3
- package/src/routes/components/Icons/IconChevron.svelte +6 -3
- package/src/routes/components/Icons/IconEnlarge.svelte +6 -3
- package/src/routes/components/Modal.svelte +11 -10
- package/src/routes/components/Participants.svelte +8 -3
- package/src/routes/components/Playlinks.svelte +11 -7
- package/src/routes/components/Popover.svelte +11 -10
- package/src/routes/components/RoundButton.svelte +11 -9
- package/src/routes/components/SkeletonText.svelte +8 -3
- package/src/routes/components/Title.svelte +9 -3
- package/src/routes/components/TitleModal.svelte +9 -4
- package/src/routes/components/TitlePopover.svelte +7 -3
- package/src/tests/routes/components/Genres.test.js +1 -1
- package/src/lib/index.js +0 -1
- package/src/typedefs.js +0 -95
- /package/src/lib/{constants.js → constants.ts} +0 -0
- /package/src/lib/{genres.json → data/genres.json} +0 -0
- /package/src/lib/data/{translations.js → translations.ts} +0 -0
- /package/src/lib/enums/{Language.js → Language.ts} +0 -0
- /package/src/lib/enums/{TrackingEvent.js → TrackingEvent.ts} +0 -0
- /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
|
-
|
|
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
|
|
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 =
|
|
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 & 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
|
|
7
|
-
* @param
|
|
8
|
-
* @param
|
|
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,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,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
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
|
|
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 = (
|
|
13
|
+
const noClass = (node: HTMLElement) => {
|
|
14
14
|
node.classList.forEach(e => {if (e.startsWith('s-')) node.classList.remove(e)})
|
|
15
15
|
}
|
|
16
16
|
|
package/src/routes/+page.svelte
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
19
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
10
|
-
let
|
|
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
|