@playpilot/tpi 3.3.0-beta.2 → 3.3.1
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 +11 -8
- package/package.json +1 -1
- package/src/lib/api.ts +10 -2
- package/src/lib/image.ts +2 -1
- package/src/lib/linkInjection.ts +29 -13
- package/src/lib/scss/_mixins.scss +13 -0
- package/src/routes/+page.svelte +15 -41
- package/src/routes/components/AfterArticlePlaylinks.svelte +5 -6
- package/src/routes/components/Editorial/AIIndicator.svelte +75 -35
- package/src/routes/components/Editorial/Editor.svelte +7 -4
- package/src/routes/components/Modal.svelte +1 -0
- package/src/routes/components/Popover.svelte +1 -0
- package/src/tests/lib/api.test.js +18 -0
- package/src/tests/lib/linkInjection.test.js +51 -56
- package/src/tests/routes/+page.test.js +2 -2
- package/src/tests/routes/components/Editorial/AiIndicator.test.js +33 -1
- package/src/tests/routes/components/Editorial/EditorItem.test.js +7 -7
- package/vite.config.js +4 -2
- package/dist/assets/Editor-vJiCq2Zj.js +0 -2
package/package.json
CHANGED
package/src/lib/api.ts
CHANGED
|
@@ -51,7 +51,12 @@ export async function fetchLinkInjections(url: string, html: string, { hash = st
|
|
|
51
51
|
* @param url URL of the given article
|
|
52
52
|
* @param html HTML to be crawled
|
|
53
53
|
*/
|
|
54
|
-
export async function pollLinkInjections(
|
|
54
|
+
export async function pollLinkInjections(
|
|
55
|
+
url: string,
|
|
56
|
+
html: string,
|
|
57
|
+
{ requireCompletedResult = false, pollInterval = 3000, maxTries = 600, onpoll = () => null }:
|
|
58
|
+
{ requireCompletedResult?: boolean, pollInterval?: number, maxTries?: number, onpoll?: (_response: LinkInjectionResponse) => void } = {}
|
|
59
|
+
): Promise<LinkInjectionResponse> {
|
|
55
60
|
let hash = stringToHash(html)
|
|
56
61
|
let currentTry = 0
|
|
57
62
|
|
|
@@ -65,8 +70,9 @@ export async function pollLinkInjections(url: string, html: string, { requireCom
|
|
|
65
70
|
* @param reject Injections are not yet ready
|
|
66
71
|
*/
|
|
67
72
|
const poll = async (resolve: Function, reject: Function): Promise<void> => {
|
|
73
|
+
let response
|
|
68
74
|
try {
|
|
69
|
-
|
|
75
|
+
response = await fetchLinkInjections(url, html, { hash })
|
|
70
76
|
|
|
71
77
|
if (requireCompletedResult && (response.automation_enabled && response.ai_running)) throw new Error
|
|
72
78
|
|
|
@@ -84,6 +90,8 @@ export async function pollLinkInjections(url: string, html: string, { requireCom
|
|
|
84
90
|
return
|
|
85
91
|
}
|
|
86
92
|
|
|
93
|
+
if (response) onpoll(response)
|
|
94
|
+
|
|
87
95
|
pollTimeout = setTimeout(() => poll(resolve, reject), pollInterval)
|
|
88
96
|
}
|
|
89
97
|
}
|
package/src/lib/image.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* NOTE: This is a temporary measure. Images url from the API use a previous format which is to be replaced,
|
|
3
3
|
* but that isn't live yet and requires some extra work. In the meantime we remove part of the URL ourselves.
|
|
4
4
|
*/
|
|
5
|
-
export function removeImageUrlPrefix(url: string): string {
|
|
5
|
+
export function removeImageUrlPrefix(url: string | null): string | null {
|
|
6
|
+
if (!url) return null
|
|
6
7
|
return url.replace('/src/img', '')
|
|
7
8
|
}
|
package/src/lib/linkInjection.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mount, unmount } from 'svelte'
|
|
2
|
+
import TitleModal from '../routes/components/TitleModal.svelte'
|
|
2
3
|
import TitlePopover from '../routes/components/TitlePopover.svelte'
|
|
3
4
|
import AfterArticlePlaylinks from '../routes/components/AfterArticlePlaylinks.svelte'
|
|
4
5
|
import { cleanPhrase, findTextNodeContaining, isNodeInLink, replaceStartingFrom } from './text'
|
|
@@ -12,6 +13,7 @@ const activePopovers: Record<string, { injection: LinkInjection; component: obje
|
|
|
12
13
|
|
|
13
14
|
let currentlyHoveredInjection: EventTarget | null = null
|
|
14
15
|
let afterArticlePlaylinkInsertedComponent: object | null = null
|
|
16
|
+
let activeModalInsertedComponent: object | null = null
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Return a list of all valid text containing elements that may get injected into.
|
|
@@ -28,7 +30,7 @@ export function getLinkInjectionElements(parentElement: HTMLElement): HTMLElemen
|
|
|
28
30
|
if (validElements.includes(element)) continue
|
|
29
31
|
|
|
30
32
|
// Ignore links, buttons, and headers
|
|
31
|
-
if (/^(A|BUTTON|SCRIPT|NOSCRIPT|STYLE|IFRAME|H[1-6])$/.test(element.tagName)) continue
|
|
33
|
+
if (/^(A|BUTTON|SCRIPT|NOSCRIPT|STYLE|IFRAME|FIGCAPTION|H[1-6])$/.test(element.tagName)) continue
|
|
32
34
|
|
|
33
35
|
// Check if this element has a direct text node
|
|
34
36
|
const hasTextNode = Array.from(element.childNodes).some(
|
|
@@ -90,9 +92,10 @@ export function getLinkInjectionsParentElement(): HTMLElement {
|
|
|
90
92
|
* Replace all found injections within all given elements on the page
|
|
91
93
|
* @returns Returns an array of injections with injections that failed to be inserted marked as `failed`.
|
|
92
94
|
*/
|
|
93
|
-
export function injectLinksInDocument(elements: HTMLElement[],
|
|
94
|
-
|
|
95
|
+
export function injectLinksInDocument(elements: HTMLElement[], injections: LinkInjectionTypes = { aiInjections: [], manualInjections: [] }): LinkInjection[] {
|
|
96
|
+
clearLinkInjections()
|
|
95
97
|
|
|
98
|
+
const mergedInjections = mergeInjectionTypes(injections)
|
|
96
99
|
if (!mergedInjections) return []
|
|
97
100
|
|
|
98
101
|
// Find injection in text content of all elements together, ignore potential HTML elements.
|
|
@@ -162,11 +165,11 @@ export function injectLinksInDocument(elements: HTMLElement[], onclick: (LinkInj
|
|
|
162
165
|
}
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
addLinkInjectionEventListeners(validInjections
|
|
168
|
+
addLinkInjectionEventListeners(validInjections)
|
|
166
169
|
addCSSVariablesToLinks()
|
|
167
170
|
|
|
168
171
|
const afterArticleInjections = filterInvalidAfterArticleInjections(mergedInjections)
|
|
169
|
-
if (afterArticleInjections.length) insertAfterArticlePlaylinks(elements, afterArticleInjections
|
|
172
|
+
if (afterArticleInjections.length) insertAfterArticlePlaylinks(elements, afterArticleInjections)
|
|
170
173
|
|
|
171
174
|
return mergedInjections.filter(i => i.title_details).map((injection, index) => {
|
|
172
175
|
// Favour manual injections over AI injections
|
|
@@ -219,7 +222,7 @@ function addCSSVariablesToLinks(): void {
|
|
|
219
222
|
/**
|
|
220
223
|
* Add event listeners to all injected links. These events are for both the popover and the modal.
|
|
221
224
|
*/
|
|
222
|
-
function addLinkInjectionEventListeners(injections: LinkInjection[]
|
|
225
|
+
function addLinkInjectionEventListeners(injections: LinkInjection[]): void {
|
|
223
226
|
// Open modal on click
|
|
224
227
|
window.addEventListener('click', (event) => {
|
|
225
228
|
const target = event.target as HTMLElement | null
|
|
@@ -231,7 +234,7 @@ function addLinkInjectionEventListeners(injections: LinkInjection[], onclick: (i
|
|
|
231
234
|
const injection = injections.find(injection => key === injection.key)
|
|
232
235
|
if (!injection) return
|
|
233
236
|
|
|
234
|
-
openLinkModal(event, injection
|
|
237
|
+
openLinkModal(event, injection)
|
|
235
238
|
})
|
|
236
239
|
|
|
237
240
|
const createdInjectionElements = Array.from(document.querySelectorAll(keySelector)) as HTMLElement[]
|
|
@@ -250,16 +253,27 @@ function addLinkInjectionEventListeners(injections: LinkInjection[], onclick: (i
|
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
/**
|
|
253
|
-
*
|
|
254
|
-
*
|
|
256
|
+
* Open modal for the corresponding injection by mounting the component and saving it to a variable.
|
|
257
|
+
* Ignore clicks that used modifier keys or that were not left click.
|
|
255
258
|
*/
|
|
256
|
-
function openLinkModal(event: MouseEvent, injection: LinkInjection
|
|
259
|
+
function openLinkModal(event: MouseEvent, injection: LinkInjection): void {
|
|
257
260
|
if (event.ctrlKey || event.metaKey || event.button !== 0) return
|
|
261
|
+
if (activeModalInsertedComponent) return
|
|
258
262
|
|
|
259
263
|
event.preventDefault()
|
|
260
264
|
|
|
261
|
-
onclick(injection)
|
|
262
265
|
destroyLinkPopover(injection)
|
|
266
|
+
activeModalInsertedComponent = mount(TitleModal, { target: document.body, props: { title: injection.title_details!, onclose: destroyLinkModal } })
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Unmount the modal, removing it from the dom
|
|
271
|
+
*/
|
|
272
|
+
function destroyLinkModal(outro: boolean = true): void {
|
|
273
|
+
if (!activeModalInsertedComponent) return
|
|
274
|
+
|
|
275
|
+
unmount(activeModalInsertedComponent, { outro })
|
|
276
|
+
activeModalInsertedComponent = null
|
|
263
277
|
}
|
|
264
278
|
|
|
265
279
|
/**
|
|
@@ -303,7 +317,8 @@ function destroyLinkPopover(injection: LinkInjection, outro: boolean = true) {
|
|
|
303
317
|
* The config object contains a selector option as well as a position. This way a selector can be given and you can
|
|
304
318
|
* choose to insert the after article before or after the given element.
|
|
305
319
|
*/
|
|
306
|
-
export function insertAfterArticlePlaylinks(elements: HTMLElement[], injections: LinkInjection[]
|
|
320
|
+
export function insertAfterArticlePlaylinks(elements: HTMLElement[], injections: LinkInjection[]): void {
|
|
321
|
+
if (afterArticlePlaylinkInsertedComponent) return
|
|
307
322
|
if (!injections.length) return
|
|
308
323
|
|
|
309
324
|
const target = document.createElement('div')
|
|
@@ -314,7 +329,7 @@ export function insertAfterArticlePlaylinks(elements: HTMLElement[], injections:
|
|
|
314
329
|
target.dataset.playpilotAfterArticlePlaylinks = 'true'
|
|
315
330
|
insertElement.insertAdjacentElement(insertPosition, target)
|
|
316
331
|
|
|
317
|
-
afterArticlePlaylinkInsertedComponent = mount(AfterArticlePlaylinks, { target, props: { linkInjections: injections, onclickmodal } })
|
|
332
|
+
afterArticlePlaylinkInsertedComponent = mount(AfterArticlePlaylinks, { target, props: { linkInjections: injections, onclickmodal: (event, injection) => openLinkModal(event, injection) } })
|
|
318
333
|
}
|
|
319
334
|
|
|
320
335
|
function clearAfterArticlePlaylinks(): void {
|
|
@@ -337,6 +352,7 @@ export function clearLinkInjections(): void {
|
|
|
337
352
|
Object.values(activePopovers).forEach(({ injection }) => destroyLinkPopover(injection, false))
|
|
338
353
|
|
|
339
354
|
clearAfterArticlePlaylinks()
|
|
355
|
+
destroyLinkModal()
|
|
340
356
|
}
|
|
341
357
|
|
|
342
358
|
/**
|
package/src/routes/+page.svelte
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { isCrawler } from '$lib/crawler'
|
|
8
8
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
9
9
|
import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/auth'
|
|
10
|
-
import
|
|
10
|
+
import Editor from './components/Editorial/Editor.svelte'
|
|
11
11
|
import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
|
|
12
12
|
import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
|
|
13
13
|
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
const htmlString = elements.map(p => p.outerHTML).join('')
|
|
17
17
|
|
|
18
18
|
let response: LinkInjectionResponse | null = $state(null)
|
|
19
|
-
let activeInjection: LinkInjection | null = $state(null)
|
|
20
19
|
let isEditorialMode = $state(isEditorialModeEnabled())
|
|
21
20
|
let hasAuthToken = $state(!!getAuthToken())
|
|
22
21
|
let authorized = $state(false)
|
|
@@ -85,20 +84,18 @@
|
|
|
85
84
|
// so as not to suddenly insert new links while a user is reading the article.
|
|
86
85
|
if (!isEditorialMode) return
|
|
87
86
|
|
|
88
|
-
response = await pollLinkInjections(url, htmlString, { requireCompletedResult: true })
|
|
89
|
-
|
|
87
|
+
response = await pollLinkInjections(url, htmlString, { requireCompletedResult: true, onpoll: (update) => response = update })
|
|
90
88
|
inject({ aiInjections, manualInjections })
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
function rerender(): void {
|
|
94
|
-
clearLinkInjections()
|
|
95
92
|
inject(separateLinkInjectionTypes(linkInjections))
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
function inject(injections: LinkInjectionTypes = { aiInjections, manualInjections }): void {
|
|
99
96
|
// Get filtered injections as they are shown on the page.
|
|
100
97
|
// Only update state if it they are different from current injections.
|
|
101
|
-
const filteredInjections = injectLinksInDocument(elements,
|
|
98
|
+
const filteredInjections = injectLinksInDocument(elements, injections)
|
|
102
99
|
if (JSON.stringify(filteredInjections) !== JSON.stringify(linkInjections)) linkInjections = filteredInjections
|
|
103
100
|
|
|
104
101
|
const successfulInjections = filteredInjections.filter(i => !i.failed)
|
|
@@ -112,10 +109,6 @@
|
|
|
112
109
|
})
|
|
113
110
|
}
|
|
114
111
|
|
|
115
|
-
function setTarget(injection: LinkInjection): void {
|
|
116
|
-
activeInjection = injection
|
|
117
|
-
}
|
|
118
|
-
|
|
119
112
|
function openEditorialMode() {
|
|
120
113
|
isEditorialMode = true
|
|
121
114
|
setEditorialParamInUrl()
|
|
@@ -132,26 +125,18 @@
|
|
|
132
125
|
{/if}
|
|
133
126
|
|
|
134
127
|
{#if isEditorialMode && authorized}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
message: response?.ai_progress_message,
|
|
148
|
-
percentage: response?.ai_progress_percentage,
|
|
149
|
-
}} />
|
|
150
|
-
{/await}
|
|
151
|
-
{/if}
|
|
152
|
-
|
|
153
|
-
{#if activeInjection && activeInjection.title_details}
|
|
154
|
-
<TitleModal title={activeInjection.title_details} onclose={() => activeInjection = null} />
|
|
128
|
+
<Editor
|
|
129
|
+
bind:linkInjections
|
|
130
|
+
bind:this={editor}
|
|
131
|
+
{htmlString}
|
|
132
|
+
{loading}
|
|
133
|
+
injectionsEnabled={response?.injections_enabled}
|
|
134
|
+
aiStatus={{
|
|
135
|
+
automationEnabled: response?.automation_enabled,
|
|
136
|
+
aiRunning: response?.ai_running && response?.automation_enabled,
|
|
137
|
+
message: response?.ai_progress_message,
|
|
138
|
+
percentage: response?.ai_progress_percentage,
|
|
139
|
+
}} />
|
|
155
140
|
{/if}
|
|
156
141
|
</div>
|
|
157
142
|
|
|
@@ -164,16 +149,5 @@
|
|
|
164
149
|
:global(*) {
|
|
165
150
|
box-sizing: border-box;
|
|
166
151
|
}
|
|
167
|
-
|
|
168
|
-
:global(.playpilot-link-injections button),
|
|
169
|
-
:global(.playpilot-link-injections input) {
|
|
170
|
-
transition: outline-offset 100ms;
|
|
171
|
-
|
|
172
|
-
&:focus-visible,
|
|
173
|
-
&:focus-visible {
|
|
174
|
-
outline: 2px solid white;
|
|
175
|
-
outline-offset: 2px;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
152
|
}
|
|
179
153
|
</style>
|
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
interface Props {
|
|
9
9
|
linkInjections: LinkInjection[],
|
|
10
10
|
// eslint-disable-next-line no-unused-vars
|
|
11
|
-
onclickmodal?: (linkInjection: LinkInjection) => void
|
|
11
|
+
onclickmodal?: (event: MouseEvent, linkInjection: LinkInjection) => void
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
const { linkInjections, onclickmodal = (linkInjection) => null }: Props = $props()
|
|
14
|
+
const { linkInjections, onclickmodal = () => null }: Props = $props()
|
|
16
15
|
|
|
17
16
|
function onclick(title: TitleData, playlink: string): void {
|
|
18
17
|
track(TrackingEvent.AfterArticlePlaylinkClick, title, { playlink })
|
|
@@ -21,9 +20,9 @@
|
|
|
21
20
|
/**
|
|
22
21
|
* Open a modal for the given injection and track the click
|
|
23
22
|
*/
|
|
24
|
-
function openModal(title: TitleData, linkInjection: LinkInjection): void {
|
|
23
|
+
function openModal(event: MouseEvent, title: TitleData, linkInjection: LinkInjection): void {
|
|
25
24
|
track(TrackingEvent.AfterArticleModalButtonClick, title)
|
|
26
|
-
onclickmodal(linkInjection)
|
|
25
|
+
onclickmodal(event, linkInjection)
|
|
27
26
|
}
|
|
28
27
|
</script>
|
|
29
28
|
|
|
@@ -39,7 +38,7 @@
|
|
|
39
38
|
"{title}" {t('Is Available To Stream')}
|
|
40
39
|
|
|
41
40
|
<span>
|
|
42
|
-
<button onclick={() => openModal(title_details as TitleData, linkInjection)}>
|
|
41
|
+
<button onclick={(event) => openModal(event, title_details as TitleData, linkInjection)}>
|
|
43
42
|
{t('View Streaming Options')}
|
|
44
43
|
</button>
|
|
45
44
|
</span>
|
|
@@ -6,57 +6,78 @@
|
|
|
6
6
|
automationEnabled?: boolean,
|
|
7
7
|
message?: string,
|
|
8
8
|
percentage?: number,
|
|
9
|
+
aiInjectionsCount?: number,
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
const { aiRunning = false, automationEnabled = false, message = '', percentage = 0 }: Props = $props()
|
|
12
|
+
const { aiRunning = false, automationEnabled = false, message = '', percentage = 0, aiInjectionsCount = 0 }: Props = $props()
|
|
13
|
+
|
|
14
|
+
const initialAIInjectionsCount = aiInjectionsCount
|
|
15
|
+
|
|
16
|
+
let dismissed = $state(false)
|
|
12
17
|
</script>
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
<div class="
|
|
16
|
-
<div class="
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
+
{#if !dismissed}
|
|
20
|
+
<div class="ai-indicator" class:running={aiRunning} data-testid="ai-indicator">
|
|
21
|
+
<div class="content">
|
|
22
|
+
<div class="icon">
|
|
23
|
+
<IconAi />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div>
|
|
27
|
+
{#if !automationEnabled}
|
|
28
|
+
<strong>AI processing is disabled.</strong> Enable AI from the <a href="https://partner.playpilot.net">Partner Portal</a>
|
|
29
|
+
{:else if aiRunning}
|
|
30
|
+
<strong>AI links are currently processing.</strong> This can take several minutes. We'll insert all found injections once ready.
|
|
31
|
+
|
|
32
|
+
<div class="message">
|
|
33
|
+
{message}
|
|
34
|
+
|
|
35
|
+
<span class="ellipses">
|
|
36
|
+
{#each { length: 3 }}
|
|
37
|
+
<span>.</span>
|
|
38
|
+
{/each}
|
|
39
|
+
</span>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="loading-bar">
|
|
43
|
+
<div class="loading-bar-progress">
|
|
44
|
+
<div class="loading-bar-fill" data-testid="loading-bar" style:width="{Math.max(percentage, 3)}%"></div>
|
|
45
|
+
</div>
|
|
19
46
|
|
|
20
|
-
|
|
21
|
-
{#if !automationEnabled}
|
|
22
|
-
<strong>AI processing is disabled.</strong> Enable AI from the <a href="https://partner.playpilot.net">Partner Portal</a>
|
|
23
|
-
{:else if aiRunning}
|
|
24
|
-
AI links are currently processing. This can take several minutes. We'll automatically insert all found injections once ready.
|
|
25
|
-
|
|
26
|
-
<div class="message">
|
|
27
|
-
{message}
|
|
28
|
-
|
|
29
|
-
<span class="ellipses">
|
|
30
|
-
{#each { length: 3 }}
|
|
31
|
-
<span>.</span>
|
|
32
|
-
{/each}
|
|
33
|
-
</span>
|
|
34
|
-
</div>
|
|
35
|
-
|
|
36
|
-
<div class="loading-bar">
|
|
37
|
-
<div class="loading-bar-progress">
|
|
38
|
-
<div class="loading-bar-fill" data-testid="loading-bar" style:width="{Math.max(percentage, 3)}%"></div>
|
|
47
|
+
<div class="loading-bar-label">{percentage}%</div>
|
|
39
48
|
</div>
|
|
49
|
+
{:else}
|
|
50
|
+
<p>
|
|
51
|
+
<strong>AI has finished processing.</strong> <br>
|
|
52
|
+
{#if initialAIInjectionsCount === aiInjectionsCount}
|
|
53
|
+
No new injections where found.
|
|
54
|
+
{:else}
|
|
55
|
+
{aiInjectionsCount} Injections where found and added to the page.
|
|
56
|
+
{/if}
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<button class="dismiss" onclick={() => dismissed = true}>Dismiss</button>
|
|
60
|
+
{/if}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
40
63
|
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
<div class="border">
|
|
65
|
+
{#if aiRunning}
|
|
66
|
+
<div class="animator" data-test-id="animator"></div>
|
|
43
67
|
{/if}
|
|
44
68
|
</div>
|
|
45
|
-
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<div class="border">
|
|
49
|
-
{#if aiRunning}
|
|
50
|
-
<div class="animator" data-test-id="animator"></div>
|
|
51
|
-
{/if}
|
|
52
69
|
</div>
|
|
53
|
-
|
|
70
|
+
{/if}
|
|
54
71
|
|
|
55
72
|
<style lang="scss">
|
|
56
73
|
a {
|
|
57
74
|
color: currentColor;
|
|
58
75
|
}
|
|
59
76
|
|
|
77
|
+
p {
|
|
78
|
+
margin: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
.ai-indicator {
|
|
61
82
|
position: relative;
|
|
62
83
|
margin: 0 margin(0.5);
|
|
@@ -168,4 +189,23 @@
|
|
|
168
189
|
}
|
|
169
190
|
}
|
|
170
191
|
}
|
|
192
|
+
|
|
193
|
+
.dismiss {
|
|
194
|
+
appearance: none;
|
|
195
|
+
margin-top: margin(0.25);
|
|
196
|
+
padding: margin(0.25);
|
|
197
|
+
border: 2px solid var(--playpilot-content-light);
|
|
198
|
+
border-radius: margin(0.25);
|
|
199
|
+
background: var(--playpilot-light);
|
|
200
|
+
font-family: inherit;
|
|
201
|
+
color: var(--playpilot-text-color-alt);
|
|
202
|
+
font-size: margin(0.75);
|
|
203
|
+
line-height: 1;
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
|
|
206
|
+
&:hover {
|
|
207
|
+
background: var(--playpilot-content-light);
|
|
208
|
+
color: var(--playpilot-text-color);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
171
211
|
</style>
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { track } from '$lib/tracking'
|
|
15
15
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
16
16
|
import { heading } from '$lib/actions/heading'
|
|
17
|
+
import { separateLinkInjectionTypes } from '$lib/linkInjection'
|
|
17
18
|
|
|
18
19
|
interface Props {
|
|
19
20
|
linkInjections: LinkInjection[],
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
const filteredInjections = $derived(linkInjections.filter((i) => i.title_details && !i.removed && !i.duplicate && (i.manual || (!i.manual && !i.failed))))
|
|
55
56
|
const sortedInjections = $derived(sortInjections(filteredInjections))
|
|
56
57
|
const { automationEnabled = false, aiRunning = false } = $derived(aiStatus)
|
|
58
|
+
const initialAiRunning = $derived(!loading && untrack(() => aiStatus.aiRunning))
|
|
57
59
|
|
|
58
60
|
$effect(() => {
|
|
59
61
|
if (loading) return
|
|
@@ -173,8 +175,8 @@
|
|
|
173
175
|
</div>
|
|
174
176
|
{/if}
|
|
175
177
|
|
|
176
|
-
{#if
|
|
177
|
-
<AIIndicator {...aiStatus} />
|
|
178
|
+
{#if initialAiRunning || !automationEnabled}
|
|
179
|
+
<AIIndicator {...aiStatus} aiInjectionsCount={separateLinkInjectionTypes(linkInjections).aiInjections.length} />
|
|
178
180
|
{/if}
|
|
179
181
|
|
|
180
182
|
{#if hasError}
|
|
@@ -227,7 +229,7 @@
|
|
|
227
229
|
width: 100%;
|
|
228
230
|
max-width: margin(22);
|
|
229
231
|
height: min(70vh, margin(40));
|
|
230
|
-
min-height:
|
|
232
|
+
min-height: min(25rem, 80vh);
|
|
231
233
|
margin: 0;
|
|
232
234
|
padding: margin(1);
|
|
233
235
|
border-radius: margin(1.5);
|
|
@@ -241,6 +243,7 @@
|
|
|
241
243
|
line-height: normal;
|
|
242
244
|
|
|
243
245
|
@include reset-svg();
|
|
246
|
+
@include global-outlines();
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
.panel-open {
|
|
@@ -338,7 +341,7 @@
|
|
|
338
341
|
transition: opacity 100ms;
|
|
339
342
|
font-family: inherit;
|
|
340
343
|
color: var(--playpilot-text-color-alt);
|
|
341
|
-
font-size: 0.
|
|
344
|
+
font-size: margin(0.85);
|
|
342
345
|
cursor: pointer;
|
|
343
346
|
|
|
344
347
|
&:hover {
|
|
@@ -3,6 +3,7 @@ import { fetchConfig, fetchLinkInjections, getApiToken, pollLinkInjections } fro
|
|
|
3
3
|
import { fakeFetch } from '../helpers'
|
|
4
4
|
import { authorize, isEditorialModeEnabled } from '$lib/auth'
|
|
5
5
|
import { Language } from '$lib/enums/Language'
|
|
6
|
+
import { waitFor } from '@testing-library/svelte'
|
|
6
7
|
|
|
7
8
|
vi.mock('$lib/auth', async importActual => {
|
|
8
9
|
const actual = await importActual()
|
|
@@ -140,6 +141,23 @@ describe('$lib/api', () => {
|
|
|
140
141
|
expect(global.fetch).toHaveBeenCalledTimes(3)
|
|
141
142
|
})
|
|
142
143
|
|
|
144
|
+
it('Should call given onpoll function when polling', async () => {
|
|
145
|
+
fakeFetch({ response: { automation_enabled: true, ai_running: true } })
|
|
146
|
+
|
|
147
|
+
const onpoll = vi.fn()
|
|
148
|
+
pollLinkInjections('https://some-url', 'some-html', { requireCompletedResult: true, pollInterval: 500, onpoll })
|
|
149
|
+
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
expect(onpoll).toHaveBeenCalledTimes(1)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
await new Promise(res => setTimeout(res, 600)) // Wait for a polling
|
|
155
|
+
expect(onpoll).toHaveBeenCalledTimes(2)
|
|
156
|
+
|
|
157
|
+
await new Promise(res => setTimeout(res, 600)) // Wait for a polling
|
|
158
|
+
expect(onpoll).toHaveBeenCalledTimes(3)
|
|
159
|
+
})
|
|
160
|
+
|
|
143
161
|
it('Should stop polling if replacements are ready', async () => {
|
|
144
162
|
const response = { automation_enabled: true, ai_running: false, link_injections: [{ title: 'value' }] }
|
|
145
163
|
fakeFetch({ response })
|