@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playpilot/tpi",
|
|
3
|
-
"version": "
|
|
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": "^
|
|
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
|
-
|
|
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
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @param
|
|
17
|
-
* @param
|
|
18
|
-
* @param
|
|
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
|
|
53
|
-
* @param
|
|
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
|
|
67
|
-
* @param
|
|
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((
|
|
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
|
|
151
|
-
* @returns
|
|
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
|
|
10
|
-
* @returns
|
|
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
|
|
45
|
-
* @returns
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 & -> &
|
|
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 & -> &
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
* @
|
|
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 =
|
|
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 =
|
|
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
|
-
* @
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
290
|
+
* @param key Given of the injection to be removed from the page
|
|
312
291
|
*/
|
|
313
|
-
export function clearLinkInjection(key) {
|
|
314
|
-
|
|
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 '
|
|
2
|
-
import { Language } from '
|
|
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
|
|
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 =
|
|
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
|
|
30
|
+
return documentLanguage
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
return Language.English
|