@playpilot/tpi 6.11.0 → 7.0.0-beta.2
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/.env +1 -0
- package/build.html +18 -0
- package/dist/link-injections.js +2 -11
- package/dist/mount.js +12 -0
- package/eslint.config.js +1 -0
- package/package.json +3 -2
- package/src/lib/api/auth.ts +1 -5
- package/src/lib/api/externalPages.ts +1 -1
- package/src/lib/api/titles.ts +1 -1
- package/src/lib/injection.ts +0 -91
- package/src/lib/injectionElements.ts +90 -0
- package/src/lib/language.ts +25 -0
- package/src/lib/localization.ts +1 -24
- package/src/lib/meta.ts +1 -1
- package/src/lib/tracking.ts +0 -1
- package/src/lib/types/global.d.ts +6 -3
- package/src/lib/types/script.d.ts +3 -1
- package/src/main.ts +21 -85
- package/src/mount.ts +29 -0
- package/src/routes/+page.svelte +10 -7
- package/src/routes/components/Editorial/EditorItem.svelte +2 -1
- package/src/routes/components/Editorial/ManualInjection.svelte +1 -1
- package/src/routes/components/Rails/ParticipantsRail.svelte +1 -1
- package/src/tests/lib/{injections.test.js → injection.test.js} +3 -207
- package/src/tests/lib/injectionElements.test.js +235 -0
- package/src/tests/lib/language.test.js +56 -0
- package/src/tests/lib/localization.test.js +3 -55
- package/vite._main.config.js +27 -0
- package/vite._mount.config.js +54 -0
- package/vite.config.js +2 -34
package/eslint.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playpilot/tpi",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
7
|
-
"build": "
|
|
7
|
+
"dev-build": "npx http-server . /build.html -p 3000",
|
|
8
|
+
"build": "vite build --config vite._main.config.js && vite build --config vite._mount.config.js",
|
|
8
9
|
"preview": "vite preview",
|
|
9
10
|
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
|
10
11
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
package/src/lib/api/auth.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { getApiToken } from '$lib/token'
|
|
2
|
-
import { TrackingEvent } from '../enums/TrackingEvent'
|
|
3
|
-
import { track } from '../tracking'
|
|
4
2
|
import { api } from './api'
|
|
5
3
|
|
|
6
4
|
const cookieName = 'EncryptedToken'
|
|
@@ -28,12 +26,10 @@ export async function authorize(href: string = window.location.href): Promise<bo
|
|
|
28
26
|
return true
|
|
29
27
|
} catch(error: any) {
|
|
30
28
|
console.error(error)
|
|
31
|
-
|
|
29
|
+
throw error
|
|
32
30
|
} finally {
|
|
33
31
|
removeAuthParamFromUrl()
|
|
34
32
|
}
|
|
35
|
-
|
|
36
|
-
return false
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
export function getAuthToken(href: string = ''): string {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { authorize, getAuthToken, isEditorialModeEnabled } from './auth'
|
|
2
2
|
import { generateRandomHash, stringToHash } from '../hash'
|
|
3
|
-
import { getLanguage } from '../
|
|
3
|
+
import { getLanguage } from '../language'
|
|
4
4
|
import { getDatetime, getPageMetaData } from '../meta'
|
|
5
5
|
import type { LinkInjectionResponse, LinkInjection } from '../types/injection'
|
|
6
6
|
import { getFullUrlPath } from '../url'
|
package/src/lib/api/titles.ts
CHANGED
package/src/lib/injection.ts
CHANGED
|
@@ -9,97 +9,6 @@ import { clearInTextDisclaimer, insertInTextDisclaimer } from './disclaimer'
|
|
|
9
9
|
export const keyDataAttribute = 'data-playpilot-injection-key'
|
|
10
10
|
export const keySelector = `[${keyDataAttribute}]`
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Return a list of all valid text containing elements that may get injected into.
|
|
14
|
-
* This excludes duplicates, empty elements, links, buttons, and header tags.
|
|
15
|
-
*
|
|
16
|
-
* Elements can additionally be excluded via the excludeElementsSelector attribute.
|
|
17
|
-
* This will exclude any element that matches or is in that selector.
|
|
18
|
-
*/
|
|
19
|
-
export function getLinkInjectionElements(parentElement: HTMLElement, excludeElementsSelector: string = ''): HTMLElement[] {
|
|
20
|
-
const validElements: HTMLElement[] = []
|
|
21
|
-
const remainingChildren = [parentElement]
|
|
22
|
-
|
|
23
|
-
while (remainingChildren.length > 0) {
|
|
24
|
-
const element = remainingChildren.pop() as HTMLElement
|
|
25
|
-
|
|
26
|
-
if (validElements.includes(element)) continue
|
|
27
|
-
if (excludeElementsSelector && element.matches(excludeElementsSelector)) continue
|
|
28
|
-
|
|
29
|
-
// Ignore links, buttons, and headers
|
|
30
|
-
if (/^(A|BUTTON|SCRIPT|NOSCRIPT|STYLE|IFRAME|FIGCAPTION|TIME|LABEL|BLOCKQUOTE|H1)$/.test(element.tagName)) continue
|
|
31
|
-
|
|
32
|
-
// Ignore elements that are visibly hidden as they are likely only for screen readers.
|
|
33
|
-
// These elements can be hidden via display: none or via a tiny width or perhaps even a clip path.
|
|
34
|
-
// Checking by their offsetWidth seems like the surest way to ignore these elements.
|
|
35
|
-
// We continue regardless of whether this is true or not, as we'd otherwise loop through children
|
|
36
|
-
// which are also hidden because of their parent.
|
|
37
|
-
// We always do it when running tests, as offsetWidth can't be reliably tested.
|
|
38
|
-
if (process.env.NODE_ENV !== 'test' && element.offsetWidth <= 1) continue
|
|
39
|
-
|
|
40
|
-
const hasTextNode = Array.from(element.childNodes).some(
|
|
41
|
-
node => node.nodeType === Node.TEXT_NODE && node.nodeValue?.trim() !== '',
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
// Treat list items as a single element, via their list element parent.
|
|
45
|
-
// This is to prevent missing out on list items with very short titles, such as lists
|
|
46
|
-
// of movie titles with no other content in the list item. Sentences often contain the full
|
|
47
|
-
// list, but breaking them up in separate list item elements means they can never get
|
|
48
|
-
// injected into properly.
|
|
49
|
-
if (hasTextNode && element.tagName === 'LI') {
|
|
50
|
-
const listContainer = element.closest('ul, ol') as HTMLElement | null
|
|
51
|
-
|
|
52
|
-
if (listContainer && !validElements.includes(listContainer)) {
|
|
53
|
-
validElements.push(listContainer)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Some wysiwyg editors wrap contents of a paragraph in `span` elements when they contain styling.
|
|
60
|
-
// This leads to broken up sentences which don't match correctly against the given sentence.
|
|
61
|
-
// `<p><span>Some text</span><span> <strong>with broken up</strong></span><span>elements</span></p>`
|
|
62
|
-
const isParagraphWithText = element.tagName === 'P' && !!element.textContent
|
|
63
|
-
|
|
64
|
-
// If this element has a text node we add it to the valid elements and stop there
|
|
65
|
-
if (hasTextNode || isParagraphWithText) {
|
|
66
|
-
validElements.push(element)
|
|
67
|
-
continue
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Add all children of the current element to be checked in this same loop.
|
|
71
|
-
const children = Array.from(element.children) as HTMLElement[]
|
|
72
|
-
for (let i = children.length - 1; i >= 0; i--) {
|
|
73
|
-
remainingChildren.push(children[i] as HTMLElement)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return validElements
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get the parent selector that will be used to find the link injections in.
|
|
82
|
-
* This selector is passed through the api config, or when the script is initialized.
|
|
83
|
-
* If no selector is passed a default is returned instead.
|
|
84
|
-
*/
|
|
85
|
-
export function getLinkInjectionsParentElement(): HTMLElement {
|
|
86
|
-
const selector = window.PlayPilotLinkInjections?.selector
|
|
87
|
-
|
|
88
|
-
if (selector) {
|
|
89
|
-
// Replace : with \\: because js selectors need some characters escaped. We only care about : at the moment.
|
|
90
|
-
const escaped = selector.replace(/(:)/g, '\\$1')
|
|
91
|
-
const element = document.querySelector(escaped)
|
|
92
|
-
|
|
93
|
-
if (element) return element as HTMLElement
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return document.querySelector('article') || document.querySelector('main') || document.body
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function getPageText(elements: HTMLElement[]): string {
|
|
100
|
-
return elements.map(element => element.innerText).join('\n\n')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
12
|
/**
|
|
104
13
|
* Replace all found injections within all given elements on the page
|
|
105
14
|
* @returns Returns an array of injections with injections that failed to be inserted marked as `failed`.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return a list of all valid text containing elements that may get injected into.
|
|
3
|
+
* This excludes duplicates, empty elements, links, buttons, and header tags.
|
|
4
|
+
*
|
|
5
|
+
* Elements can additionally be excluded via the excludeElementsSelector attribute.
|
|
6
|
+
* This will exclude any element that matches or is in that selector.
|
|
7
|
+
*/
|
|
8
|
+
export function getLinkInjectionElements(parentElement: HTMLElement, excludeElementsSelector: string = ''): HTMLElement[] {
|
|
9
|
+
const validElements: HTMLElement[] = []
|
|
10
|
+
const remainingChildren = [parentElement]
|
|
11
|
+
|
|
12
|
+
while (remainingChildren.length > 0) {
|
|
13
|
+
const element = remainingChildren.pop() as HTMLElement
|
|
14
|
+
|
|
15
|
+
if (validElements.includes(element)) continue
|
|
16
|
+
if (excludeElementsSelector && element.matches(excludeElementsSelector)) continue
|
|
17
|
+
|
|
18
|
+
// Ignore links, buttons, and headers
|
|
19
|
+
if (/^(A|BUTTON|SCRIPT|NOSCRIPT|STYLE|IFRAME|FIGCAPTION|TIME|LABEL|BLOCKQUOTE|H1)$/.test(element.tagName)) continue
|
|
20
|
+
|
|
21
|
+
// Ignore elements that are visibly hidden as they are likely only for screen readers.
|
|
22
|
+
// These elements can be hidden via display: none or via a tiny width or perhaps even a clip path.
|
|
23
|
+
// Checking by their offsetWidth seems like the surest way to ignore these elements.
|
|
24
|
+
// We continue regardless of whether this is true or not, as we'd otherwise loop through children
|
|
25
|
+
// which are also hidden because of their parent.
|
|
26
|
+
// We always do it when running tests, as offsetWidth can't be reliably tested.
|
|
27
|
+
if (process.env.NODE_ENV !== 'test' && element.offsetWidth <= 1) continue
|
|
28
|
+
|
|
29
|
+
const hasTextNode = Array.from(element.childNodes).some(
|
|
30
|
+
node => node.nodeType === Node.TEXT_NODE && node.nodeValue?.trim() !== '',
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// Treat list items as a single element, via their list element parent.
|
|
34
|
+
// This is to prevent missing out on list items with very short titles, such as lists
|
|
35
|
+
// of movie titles with no other content in the list item. Sentences often contain the full
|
|
36
|
+
// list, but breaking them up in separate list item elements means they can never get
|
|
37
|
+
// injected into properly.
|
|
38
|
+
if (hasTextNode && element.tagName === 'LI') {
|
|
39
|
+
const listContainer = element.closest('ul, ol') as HTMLElement | null
|
|
40
|
+
|
|
41
|
+
if (listContainer && !validElements.includes(listContainer)) {
|
|
42
|
+
validElements.push(listContainer)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Some wysiwyg editors wrap contents of a paragraph in `span` elements when they contain styling.
|
|
49
|
+
// This leads to broken up sentences which don't match correctly against the given sentence.
|
|
50
|
+
// `<p><span>Some text</span><span> <strong>with broken up</strong></span><span>elements</span></p>`
|
|
51
|
+
const isParagraphWithText = element.tagName === 'P' && !!element.textContent
|
|
52
|
+
|
|
53
|
+
// If this element has a text node we add it to the valid elements and stop there
|
|
54
|
+
if (hasTextNode || isParagraphWithText) {
|
|
55
|
+
validElements.push(element)
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Add all children of the current element to be checked in this same loop.
|
|
60
|
+
const children = Array.from(element.children) as HTMLElement[]
|
|
61
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
62
|
+
remainingChildren.push(children[i] as HTMLElement)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return validElements
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the parent selector that will be used to find the link injections in.
|
|
71
|
+
* This selector is passed through the api config, or when the script is initialized.
|
|
72
|
+
* If no selector is passed a default is returned instead.
|
|
73
|
+
*/
|
|
74
|
+
export function getLinkInjectionsParentElement(): HTMLElement {
|
|
75
|
+
const selector = window.PlayPilotLinkInjections?.selector
|
|
76
|
+
|
|
77
|
+
if (selector) {
|
|
78
|
+
// Replace : with \\: because js selectors need some characters escaped. We only care about : at the moment.
|
|
79
|
+
const escaped = selector.replace(/(:)/g, '\\$1')
|
|
80
|
+
const element = document.querySelector(escaped)
|
|
81
|
+
|
|
82
|
+
if (element) return element as HTMLElement
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return document.querySelector('article') || document.querySelector('main') || document.body
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getPageText(elements: HTMLElement[]): string {
|
|
89
|
+
return elements.map(element => element.innerText).join('\n\n')
|
|
90
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Language } from './enums/Language'
|
|
2
|
+
import type { LanguageCode } from './types/language'
|
|
3
|
+
|
|
4
|
+
export function getLanguage(): LanguageCode {
|
|
5
|
+
const configLanguage = window.PlayPilotLinkInjections?.language as LanguageCode | undefined
|
|
6
|
+
const languageCodes = Object.values(Language) as LanguageCode[]
|
|
7
|
+
|
|
8
|
+
if (configLanguage) {
|
|
9
|
+
if (languageCodes.includes(configLanguage)) return configLanguage
|
|
10
|
+
|
|
11
|
+
console.warn(`PlayPilot Link Injections: ${configLanguage} is not an accepted language`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const documentLanguage = document.querySelector('html')?.getAttribute('lang')
|
|
15
|
+
|
|
16
|
+
if (documentLanguage) {
|
|
17
|
+
if (languageCodes.includes(documentLanguage as LanguageCode)) return documentLanguage as LanguageCode
|
|
18
|
+
|
|
19
|
+
// Match against shorthand language codes like da -> da-DK
|
|
20
|
+
const matched = languageCodes.find(code => code.toLowerCase().startsWith(documentLanguage + '-'))
|
|
21
|
+
if (matched) return matched
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Language.English
|
|
25
|
+
}
|
package/src/lib/localization.ts
CHANGED
|
@@ -1,31 +1,8 @@
|
|
|
1
1
|
import { translations } from './data/translations'
|
|
2
|
-
import {
|
|
2
|
+
import { getLanguage } from './language'
|
|
3
3
|
import type { LanguageCode } from './types/language'
|
|
4
4
|
|
|
5
5
|
export function t(key: string, language: LanguageCode = getLanguage()): string {
|
|
6
6
|
// @ts-ignore It's fine if it's undefined
|
|
7
7
|
return translations[key]?.[language] || key
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
export function getLanguage(): LanguageCode {
|
|
11
|
-
const configLanguage = window.PlayPilotLinkInjections?.language as LanguageCode | undefined
|
|
12
|
-
const languageCodes = Object.values(Language) as LanguageCode[]
|
|
13
|
-
|
|
14
|
-
if (configLanguage) {
|
|
15
|
-
if (languageCodes.includes(configLanguage)) return configLanguage
|
|
16
|
-
|
|
17
|
-
console.warn(`PlayPilot Link Injections: ${configLanguage} is not an accepted language`)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const documentLanguage = document.querySelector('html')?.getAttribute('lang')
|
|
21
|
-
|
|
22
|
-
if (documentLanguage) {
|
|
23
|
-
if (languageCodes.includes(documentLanguage as LanguageCode)) return documentLanguage as LanguageCode
|
|
24
|
-
|
|
25
|
-
// Match against shorthand language codes like da -> da-DK
|
|
26
|
-
const matched = languageCodes.find(code => code.toLowerCase().startsWith(documentLanguage + '-'))
|
|
27
|
-
if (matched) return matched
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return Language.English
|
|
31
|
-
}
|
package/src/lib/meta.ts
CHANGED
package/src/lib/tracking.ts
CHANGED
|
@@ -36,7 +36,6 @@ export async function track(event: string, title: TitleData | null = null, paylo
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// eslint-disable-next-line no-undef
|
|
40
39
|
payload.version = __SCRIPT_VERSION__
|
|
41
40
|
payload.url = getFullUrlPath()
|
|
42
41
|
payload.organization_sid = window.PlayPilotLinkInjections?.organization_sid || 'undefined'
|
|
@@ -4,10 +4,13 @@ declare global {
|
|
|
4
4
|
interface Window {
|
|
5
5
|
PlayPilotLinkInjections: ScriptConfig & {
|
|
6
6
|
app: any | null
|
|
7
|
-
initialize(config: ScriptConfig): void
|
|
7
|
+
initialize(config: ScriptConfig): Promise<void>
|
|
8
|
+
destroy(): void
|
|
9
|
+
mount(): void
|
|
10
|
+
}
|
|
11
|
+
PlayPilotMount: {
|
|
12
|
+
mount(): void
|
|
8
13
|
destroy(): void
|
|
9
|
-
debug(): void
|
|
10
|
-
mockAd(override?: Record<any, any> = {}): void
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Campaign } from './campaign'
|
|
2
2
|
import type { ConsentOptions } from './consent'
|
|
3
|
-
import type { LinkInjection } from './injection'
|
|
3
|
+
import type { LinkInjection, LinkInjectionResponse } from './injection'
|
|
4
4
|
import type { TitleData } from './title'
|
|
5
5
|
|
|
6
6
|
export type ScriptConfig = {
|
|
@@ -30,6 +30,8 @@ export type ScriptConfig = {
|
|
|
30
30
|
split_test_identifiers?: Record<string, number>
|
|
31
31
|
// All link injections as returned from external-pages, with AI and manual injections merged.
|
|
32
32
|
evaluated_link_injections?: LinkInjection[]
|
|
33
|
+
// Initial link injections as fetched from the main script.
|
|
34
|
+
initial_link_injections_promise?: Promise<LinkInjectionResponse> | null
|
|
33
35
|
// By default the script requires consent from the user through tcfapi. Can be disabled using this setting.
|
|
34
36
|
require_consent?: boolean
|
|
35
37
|
// Used to check if a user has consented via tcfapi to various consent categories.
|
package/src/main.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { clearLinkInjections, getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/injection'
|
|
4
|
-
import { getPageMetaData } from '$lib/meta'
|
|
1
|
+
import { fetchConfig } from '$lib/api/config'
|
|
2
|
+
import { pollLinkInjections } from '$lib/api/externalPages'
|
|
5
3
|
import { setConsent } from '$lib/consent'
|
|
6
|
-
import
|
|
4
|
+
import { isCrawler } from '$lib/crawler'
|
|
5
|
+
import { getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/injectionElements'
|
|
7
6
|
|
|
8
7
|
window.PlayPilotLinkInjections = {
|
|
9
8
|
token: '',
|
|
@@ -20,6 +19,7 @@ window.PlayPilotLinkInjections = {
|
|
|
20
19
|
queued_tracking_events: [],
|
|
21
20
|
split_test_identifiers: {},
|
|
22
21
|
evaluated_link_injections: [],
|
|
22
|
+
initial_link_injections_promise: null,
|
|
23
23
|
ads: [],
|
|
24
24
|
require_consent: true,
|
|
25
25
|
consents: {
|
|
@@ -32,7 +32,7 @@ window.PlayPilotLinkInjections = {
|
|
|
32
32
|
config: {},
|
|
33
33
|
app: null,
|
|
34
34
|
|
|
35
|
-
initialize(options = { token: '', selector: '', after_article_selector: '', after_article_insert_position: '', language: null, region: null, organization_sid: null, domain_sid: null, editorial_token: '', require_consent: true }): void {
|
|
35
|
+
async initialize(options = { token: '', selector: '', after_article_selector: '', after_article_insert_position: '', language: null, region: null, organization_sid: null, domain_sid: null, editorial_token: '', require_consent: true }): Promise<void> {
|
|
36
36
|
if (!options.token) {
|
|
37
37
|
console.error('An API token is required.')
|
|
38
38
|
return
|
|
@@ -60,96 +60,32 @@ window.PlayPilotLinkInjections = {
|
|
|
60
60
|
})
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
this.config = await fetchConfig() || {}
|
|
64
64
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
document.body.insertAdjacentElement('beforeend', target)
|
|
65
|
+
const parentElement = getLinkInjectionsParentElement()
|
|
66
|
+
const elements = getLinkInjectionElements(parentElement, this.config.exclude_elements_selector || '')
|
|
67
|
+
const pageText = getPageText(elements)
|
|
69
68
|
|
|
70
|
-
this.
|
|
69
|
+
this.initial_link_injections_promise = pollLinkInjections(pageText, { maxTries: 1, runAiWhenRelevant: !isCrawler() })
|
|
70
|
+
this.mount()
|
|
71
71
|
},
|
|
72
72
|
|
|
73
73
|
destroy(): void {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
this.app = null
|
|
77
|
-
|
|
78
|
-
const target = document.getElementById('playpilot-link-injection')
|
|
79
|
-
if (target) target.remove()
|
|
80
|
-
|
|
81
|
-
clearLinkInjections()
|
|
74
|
+
window.PlayPilotMount?.destroy()
|
|
82
75
|
},
|
|
83
76
|
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const elements = getLinkInjectionElements(parentElement)
|
|
87
|
-
|
|
88
|
-
console.groupCollapsed('Config')
|
|
89
|
-
console.table(Object.entries(this))
|
|
90
|
-
console.groupEnd()
|
|
91
|
-
|
|
92
|
-
console.groupCollapsed('Elements')
|
|
93
|
-
console.log('Parent element', parentElement)
|
|
94
|
-
console.log('Valid elements', elements)
|
|
95
|
-
console.groupEnd()
|
|
77
|
+
mount(): void {
|
|
78
|
+
const script = document.createElement('script')
|
|
96
79
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
console.groupEnd()
|
|
100
|
-
|
|
101
|
-
console.groupCollapsed('Meta')
|
|
102
|
-
console.log(getPageMetaData())
|
|
103
|
-
console.groupEnd()
|
|
104
|
-
|
|
105
|
-
console.groupCollapsed('Page text')
|
|
106
|
-
console.log(getPageText(elements))
|
|
107
|
-
console.groupEnd()
|
|
108
|
-
|
|
109
|
-
console.groupCollapsed('Evaluated injections')
|
|
110
|
-
console.log(this.evaluated_link_injections)
|
|
111
|
-
console.groupEnd()
|
|
112
|
-
|
|
113
|
-
console.groupCollapsed('Tracked events')
|
|
114
|
-
console.log(this.tracked_events)
|
|
115
|
-
console.groupEnd()
|
|
116
|
-
|
|
117
|
-
console.groupCollapsed('Queued tracking events')
|
|
118
|
-
console.log(this.queued_tracking_events)
|
|
119
|
-
console.groupEnd()
|
|
120
|
-
|
|
121
|
-
console.groupCollapsed('Split tests')
|
|
122
|
-
console.log(this.split_test_identifiers)
|
|
123
|
-
console.groupEnd()
|
|
124
|
-
},
|
|
80
|
+
script.src = `https://cdn.jsdelivr.net/npm/@playpilot/tpi@${__SCRIPT_VERSION__}/dist/mount.js`
|
|
81
|
+
// script.src = './dist/mount.js' // Use me during development of this script
|
|
125
82
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
campaign_format: 'card',
|
|
130
|
-
campaign_type: 'image',
|
|
131
|
-
campaign_name: 'some_campaign',
|
|
132
|
-
content: {
|
|
133
|
-
header: 'Some content header',
|
|
134
|
-
header_logo: 'https://picsum.photos/seed/picsum/40/40',
|
|
135
|
-
header_logo_uuid: '',
|
|
136
|
-
subheader: 'Some content subheader',
|
|
137
|
-
image: 'https://picsum.photos/seed/picsum/640/360',
|
|
138
|
-
image_uuid: '',
|
|
139
|
-
video: null,
|
|
140
|
-
format: null,
|
|
141
|
-
},
|
|
142
|
-
cta: {
|
|
143
|
-
header: 'Some cta header',
|
|
144
|
-
subheader: 'Some cta subheader',
|
|
145
|
-
url: 'https://google.com/',
|
|
146
|
-
image: null,
|
|
147
|
-
image_uuid: null,
|
|
148
|
-
},
|
|
149
|
-
disclaimer: 'Some disclaimer',
|
|
83
|
+
script.onload = () => {
|
|
84
|
+
console.log('on load')
|
|
85
|
+
window.PlayPilotMount?.mount()
|
|
150
86
|
}
|
|
151
87
|
|
|
152
|
-
|
|
88
|
+
document.body.insertAdjacentElement('beforeend', script)
|
|
153
89
|
},
|
|
154
90
|
}
|
|
155
91
|
|
package/src/mount.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { mount } from 'svelte'
|
|
2
|
+
import App from './routes/+page.svelte'
|
|
3
|
+
import { clearLinkInjections } from '$lib/injection'
|
|
4
|
+
|
|
5
|
+
window.PlayPilotMount = {
|
|
6
|
+
mount(): void {
|
|
7
|
+
if (window.PlayPilotLinkInjections.app) this.destroy()
|
|
8
|
+
|
|
9
|
+
const target = document.createElement('div')
|
|
10
|
+
target.id = 'playpilot-link-injection'
|
|
11
|
+
|
|
12
|
+
document.body.insertAdjacentElement('beforeend', target)
|
|
13
|
+
|
|
14
|
+
window.PlayPilotLinkInjections.app = mount(App, { target })
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
destroy(): void {
|
|
18
|
+
if (!window.PlayPilotLinkInjections) return
|
|
19
|
+
|
|
20
|
+
window.PlayPilotLinkInjections.app = null
|
|
21
|
+
|
|
22
|
+
const target = document.getElementById('playpilot-link-injection')
|
|
23
|
+
if (target) target.remove()
|
|
24
|
+
|
|
25
|
+
clearLinkInjections()
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default window.PlayPilotLinkInjections
|
package/src/routes/+page.svelte
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy } from 'svelte'
|
|
3
3
|
import { pollLinkInjections } from '$lib/api/externalPages'
|
|
4
|
-
import { clearLinkInjections,
|
|
4
|
+
import { clearLinkInjections, injectLinksInDocument, separateLinkInjectionTypes } from '$lib/injection'
|
|
5
|
+
import { getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/injectionElements'
|
|
5
6
|
import { fireQueuedTrackingEvents, setTrackingSids, track } from '$lib/tracking'
|
|
6
7
|
import { getFullUrlPath } from '$lib/url'
|
|
7
8
|
import { isCrawler } from '$lib/crawler'
|
|
@@ -65,13 +66,16 @@
|
|
|
65
66
|
async function initialize(): Promise<void> {
|
|
66
67
|
loading = true
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
try {
|
|
70
|
+
if (isEditorialMode) authorized = await authorize()
|
|
71
|
+
} catch {
|
|
72
|
+
track(TrackingEvent.AuthFailed)
|
|
73
|
+
}
|
|
69
74
|
|
|
70
75
|
try {
|
|
71
|
-
const config = await fetchConfig()
|
|
72
76
|
const url = getFullUrlPath()
|
|
73
77
|
|
|
74
|
-
window.PlayPilotLinkInjections.config
|
|
78
|
+
const config = window.PlayPilotLinkInjections.config || await fetchConfig()
|
|
75
79
|
|
|
76
80
|
if (config?.custom_style) insertCustomStyle(config.custom_style || '')
|
|
77
81
|
if (config?.explore_navigation_selector) insertExploreIntoNavigation()
|
|
@@ -89,9 +93,8 @@
|
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
response = await pollLinkInjections(pageText, { maxTries: 1, runAiWhenRelevant: !isCrawler() })
|
|
96
|
+
response = await window.PlayPilotLinkInjections.initial_link_injections_promise || null
|
|
97
|
+
response ||= await pollLinkInjections(pageText, { maxTries: 1, runAiWhenRelevant: !isCrawler() })
|
|
95
98
|
|
|
96
99
|
loading = false
|
|
97
100
|
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import type { LinkInjection } from '$lib/types/injection'
|
|
14
14
|
import type { TitleData } from '$lib/types/title'
|
|
15
15
|
import { cleanPhrase, truncateAroundPhrase } from '$lib/text'
|
|
16
|
-
import {
|
|
16
|
+
import { isValidPlaylinkType } from '$lib/injection'
|
|
17
|
+
import { getLinkInjectionElements, getLinkInjectionsParentElement } from '$lib/injectionElements'
|
|
17
18
|
import { imagePlaceholderDataUrl } from '$lib/constants'
|
|
18
19
|
import { removeImageUrlPrefix } from '$lib/image'
|
|
19
20
|
import ReportIssueModal from './ReportIssueModal.svelte'
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { onMount } from 'svelte'
|
|
5
5
|
import { playPilotBaseUrl } from '$lib/constants'
|
|
6
6
|
import { generateInjectionKey } from '$lib/api/externalPages'
|
|
7
|
-
import { getLinkInjectionsParentElement } from '$lib/
|
|
7
|
+
import { getLinkInjectionsParentElement } from '$lib/injectionElements'
|
|
8
8
|
import { heading } from '$lib/actions/heading'
|
|
9
9
|
import { findSurroundingPhrases, cleanPhrase } from '$lib/text'
|
|
10
10
|
import { getIndexOfSelection } from '$lib/selection'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { fetchParticipantsForTitle } from '$lib/api/participants'
|
|
3
3
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
4
|
-
import { getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/
|
|
4
|
+
import { getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/injectionElements'
|
|
5
5
|
import { t } from '$lib/localization'
|
|
6
6
|
import { openModal } from '$lib/modal'
|
|
7
7
|
import { cleanPhrase, findNumberOfMatchesInString } from '$lib/text'
|