@playpilot/tpi 3.3.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -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[], onclick: (LinkInjection: LinkInjection) => void, injections: LinkInjectionTypes = { aiInjections: [], manualInjections: [] }): LinkInjection[] {
94
- const mergedInjections = mergeInjectionTypes(injections)
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, onclick)
168
+ addLinkInjectionEventListeners(validInjections)
166
169
  addCSSVariablesToLinks()
167
170
 
168
171
  const afterArticleInjections = filterInvalidAfterArticleInjections(mergedInjections)
169
- if (afterArticleInjections.length) insertAfterArticlePlaylinks(elements, afterArticleInjections, onclick)
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[], onclick: (injection: LinkInjection) => void): void {
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, onclick)
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
- * Prevent default click and run onclick from parent. Ignore clicks that used modifier keys or that were not left click.
254
- * The event is not fired when the click happens from inside a popover.
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, onclick: (injection: LinkInjection) => void): void {
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[], onclickmodal: (linkInjection: LinkInjection) => void) {
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
  /**
@@ -6,3 +6,16 @@
6
6
  fill: none;
7
7
  }
8
8
  }
9
+
10
+ @mixin global-outlines() {
11
+ :global(a),
12
+ :global(button),
13
+ :global(input) {
14
+ transition: outline-offset 100ms;
15
+
16
+ &:focus-visible {
17
+ outline: 2px solid white;
18
+ outline-offset: 2px;
19
+ }
20
+ }
21
+ }
@@ -7,7 +7,6 @@
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 TitleModal from './components/TitleModal.svelte'
11
10
  import Editor from './components/Editorial/Editor.svelte'
12
11
  import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
13
12
  import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
@@ -17,7 +16,6 @@
17
16
  const htmlString = elements.map(p => p.outerHTML).join('')
18
17
 
19
18
  let response: LinkInjectionResponse | null = $state(null)
20
- let activeInjection: LinkInjection | null = $state(null)
21
19
  let isEditorialMode = $state(isEditorialModeEnabled())
22
20
  let hasAuthToken = $state(!!getAuthToken())
23
21
  let authorized = $state(false)
@@ -91,14 +89,13 @@
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, setTarget, injections)
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()
@@ -145,10 +138,6 @@
145
138
  percentage: response?.ai_progress_percentage,
146
139
  }} />
147
140
  {/if}
148
-
149
- {#if activeInjection && activeInjection.title_details}
150
- <TitleModal title={activeInjection.title_details} onclose={() => activeInjection = null} />
151
- {/if}
152
141
  </div>
153
142
 
154
143
  <style lang="scss">
@@ -160,16 +149,5 @@
160
149
  :global(*) {
161
150
  box-sizing: border-box;
162
151
  }
163
-
164
- :global(.playpilot-link-injections button),
165
- :global(.playpilot-link-injections input) {
166
- transition: outline-offset 100ms;
167
-
168
- &:focus-visible,
169
- &:focus-visible {
170
- outline: 2px solid white;
171
- outline-offset: 2px;
172
- }
173
- }
174
152
  }
175
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
- // eslint-disable-next-line no-unused-vars
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>
@@ -243,6 +243,7 @@
243
243
  line-height: normal;
244
244
 
245
245
  @include reset-svg();
246
+ @include global-outlines();
246
247
  }
247
248
 
248
249
  .panel-open {
@@ -65,6 +65,7 @@
65
65
  }
66
66
 
67
67
  @include reset-svg();
68
+ @include global-outlines();
68
69
  }
69
70
 
70
71
 
@@ -77,6 +77,7 @@
77
77
  z-index: 2147483647; // As high as she goes
78
78
 
79
79
  @include reset-svg();
80
+ @include global-outlines();
80
81
 
81
82
  &.flip {
82
83
  top: auto;