@playpilot/tpi 5.19.2 → 5.19.3

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": "5.19.2",
3
+ "version": "5.19.3",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -209,6 +209,13 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
209
209
  const valueIndex = element.innerHTML.indexOf(nodeContainingText.nodeValue)
210
210
  const sentenceIndex = element.innerHTML.indexOf(injection.sentence)
211
211
 
212
+ // Sentences are often broken up by HTML elements. When that happens the index of the sentence can't be found in the HTML.
213
+ // In that case we can attempt to search for part of the sentence instead, and hope for the best. We grab the first 20 characters
214
+ // of the sentence and look for that instead. This will not work if sentence is broken up within those first characters,
215
+ // if the injections is right at the start of the sentence, or the element contains multiple matches for that same slice.
216
+ // But it's something to fall back on regardless.
217
+ const startOfSentenceIndex = sentenceIndex === -1 ? element.innerHTML.indexOf(injection.sentence.slice(0, 20)) : -1
218
+
212
219
  // Starting from occurence happens when a sentence might include multiple occurences of the same phrase.
213
220
  // We might still prefer the valueIndex if that is higher than this value.
214
221
 
@@ -217,7 +224,7 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
217
224
  const indexOfOccurrence = getNumberOfOccurrencesInArray<LinkInjection>(foundInjections.slice(0, injectionIndex + 1), injection, ['title', 'sentence']) - 1
218
225
  const indexInSentence = getIndexOfPhraseInElement(nodeContainingText.nodeValue, element, indexOfOccurrence)
219
226
 
220
- replacementIndex = Math.max(valueIndex, indexInSentence, sentenceIndex, 0)
227
+ replacementIndex = Math.max(valueIndex, startOfSentenceIndex, indexInSentence, sentenceIndex, 0)
221
228
  }
222
229
 
223
230
  if (replacementIndex === -1) continue
@@ -1,12 +1,17 @@
1
1
  <script lang="ts">
2
2
  interface Props {
3
3
  direction?: 'left' | 'right'
4
+ title?: string
4
5
  }
5
6
 
6
- const { direction = 'right' }: Props = $props()
7
+ const { direction = 'right', title = '' }: Props = $props()
7
8
  </script>
8
9
 
9
10
  <svg class="{direction}" height="16px" viewBox="0 -960 960 960" width="16px">
11
+ {#if title}
12
+ <title>{title}</title>
13
+ {/if}
14
+
10
15
  <path d="m288-96-68-68 316-316-316-316 68-68 384 384L288-96Z" fill="currentColor" />
11
16
  </svg>
12
17
 
@@ -7,5 +7,6 @@
7
7
  </script>
8
8
 
9
9
  <svg width={size} height={size} viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
10
- <path d="M7 0.351929L9.163 4.72803L14 5.43408L10.5 8.8385L11.326 13.648L7 11.3761L2.674 13.648L3.5 8.8385L0 5.43408L4.837 4.72803L7 0.351929Z" fill="#F6C045"/>
10
+ <title>IMDb rating</title>
11
+ <path d="M7 0.351929L9.163 4.72803L14 5.43408L10.5 8.8385L11.326 13.648L7 11.3761L2.674 13.648L3.5 8.8385L0 5.43408L4.837 4.72803L7 0.351929Z" fill="#F6C045"/>
11
12
  </svg>
@@ -19,6 +19,7 @@
19
19
  closeButtonStyle?: 'shadow' | 'flat'
20
20
  initialScrollPosition?: number
21
21
  onscroll?: () => void
22
+ onclose?: () => void
22
23
  }
23
24
 
24
25
  const {
@@ -29,6 +30,7 @@
29
30
  closeButtonStyle = 'shadow',
30
31
  initialScrollPosition = 0,
31
32
  onscroll = () => null,
33
+ onclose = () => null,
32
34
  }: Props = $props()
33
35
 
34
36
  const inlineBubble = isInSplitTestVariant(SplitTest.TopScrollFormat, 1)
@@ -80,11 +82,16 @@
80
82
  if (isMobile) return fly(node, { duration: 250, ...options })
81
83
  return scale(node, { duration: 150, start: 0.85 })
82
84
  }
85
+
86
+ function close() {
87
+ onclose()
88
+ destroyAllModals()
89
+ }
83
90
  </script>
84
91
 
85
92
  <svelte:window
86
- onkeydown={({ key }) => { if (key === 'Escape') destroyAllModals() }}
87
- onhashchange={() => destroyAllModals()}
93
+ onkeydown={({ key }) => { if (key === 'Escape') close() }}
94
+ onhashchange={close}
88
95
  bind:innerWidth={windowWidth} />
89
96
 
90
97
  <div class="modal" style:--dialog-offset="{dialogOffset}px" transition:fade|global={{ duration: 150 }} bind:this={modalElement} class:has-bubble={!!bubble && inlineBubble} class:has-prepend={!!prepend}>
@@ -102,7 +109,7 @@
102
109
 
103
110
  {#if isMobile}
104
111
  <div class="swipe-handle" transition:scaleOrFly|global>
105
- <SwipeHandle target={dialogElement!} onpassed={() => destroyAllModals()} />
112
+ <SwipeHandle target={dialogElement!} onpassed={close} />
106
113
  </div>
107
114
  {/if}
108
115
 
@@ -116,7 +123,7 @@
116
123
  {/if}
117
124
 
118
125
  <div class="close {closeButtonStyle}">
119
- <RoundButton onclick={() => destroyAllModals()} aria-label="Close">
126
+ <RoundButton onclick={close} aria-label="Close">
120
127
  <IconClose />
121
128
  </RoundButton>
122
129
  </div>
@@ -126,7 +133,7 @@
126
133
 
127
134
  <!-- svelte-ignore a11y_click_events_have_key_events -->
128
135
  <!-- svelte-ignore a11y_no_static_element_interactions -->
129
- <div class="backdrop" onclick={() => destroyAllModals()}></div>
136
+ <div class="backdrop" onclick={close}></div>
130
137
  </div>
131
138
 
132
139
  <style lang="scss">
@@ -22,11 +22,15 @@
22
22
 
23
23
  {#snippet controls({ setIndex, currentIndex, reachedEnd })}
24
24
  {#if currentIndex > 0}
25
- <button class="arrow left" onclick={() => setIndex(currentIndex - 1)}><IconArrow direction="left" /></button>
25
+ <button class="arrow left" onclick={() => setIndex(currentIndex - 1)} aria-label="Previous" aria-live="polite">
26
+ <IconArrow direction="left" title="Previous" />
27
+ </button>
26
28
  {/if}
27
29
 
28
30
  {#if !reachedEnd}
29
- <button class="arrow right" onclick={() => setIndex(currentIndex + 1)}><IconArrow /></button>
31
+ <button class="arrow right" onclick={() => setIndex(currentIndex + 1)} aria-label="Next" aria-live="polite">
32
+ <IconArrow title="Next" />
33
+ </button>
30
34
  {/if}
31
35
  {/snippet}
32
36
  </TinySlider>
@@ -57,10 +57,7 @@
57
57
 
58
58
  <!-- Temporarily not available on production as there is not yet an API endpoint for either -->
59
59
  {#if process.env.NODE_ENV !== 'production'}
60
- {#if true}
61
- <ParticipantsRail />
62
- {/if}
63
-
60
+ <ParticipantsRail />
64
61
  <SimilarRail {title} />
65
62
  {/if}
66
63
  </div>
@@ -296,6 +296,22 @@ describe('linkInjection.js', () => {
296
296
  expect(document.querySelector('[data-playpilot-injection-key]')).toBeTruthy()
297
297
  })
298
298
 
299
+ it('Should inject correctly when sentence is broken up by html and element contains multiple matches', () => {
300
+ document.body.innerHTML = '<p>This is some match. Some other sentence with a match. But this is yet another <em>sentence</em> with a match.</p>'
301
+
302
+ const elements = Array.from(document.body.querySelectorAll('p'))
303
+
304
+ const injections = [
305
+ generateInjection('This is some match.', 'some match'),
306
+ generateInjection('But this is yet another sentence with a match.', 'a match'),
307
+ ]
308
+
309
+ injectLinksInDocument(elements, { aiInjections: injections, manualInjections: [] })
310
+
311
+ expect(document.body.innerHTML).toContain('Some other sentence with a match')
312
+ expect(document.querySelectorAll('[data-playpilot-injection-key]')).toHaveLength(2)
313
+ })
314
+
299
315
  it('Should leave the text intact if no injections were found', () => {
300
316
  document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
301
317
 
@@ -16,47 +16,53 @@ describe('Modal.svelte', () => {
16
16
  vi.resetAllMocks()
17
17
  })
18
18
 
19
+ const onclose = vi.fn()
19
20
  const children = createRawSnippet(() => ({ render: () => '<div></div>' }))
20
21
 
21
- it('Should fire given destroyAllModals function when backdrop is clicked', async () => {
22
- const { container } = render(Modal, { children })
22
+ it('Should fire given onclose and destroyAllModals functions when backdrop is clicked', async () => {
23
+ const { container } = render(Modal, { children, onclose })
23
24
 
24
25
  await fireEvent.click(/** @type {Node} */ (container.querySelector('.backdrop')))
25
26
 
27
+ expect(onclose).toHaveBeenCalled()
26
28
  expect(destroyAllModals).toHaveBeenCalled()
27
29
  })
28
30
 
29
- it('Should fire destroyAllModals function when close button is clicked', async () => {
30
- const { container } = render(Modal, { children })
31
+ it('Should fire given onclose and destroyAllModals functions when close button is clicked', async () => {
32
+ const { container } = render(Modal, { children, onclose })
31
33
 
32
34
  await fireEvent.click(/** @type {Node} */ (container.querySelector('button')))
33
35
 
36
+ expect(onclose).toHaveBeenCalled()
34
37
  expect(destroyAllModals).toHaveBeenCalled()
35
38
  })
36
39
 
37
- it('Should not fire given onclose function when dialog is clicked', async () => {
38
- const { container } = render(Modal, { children })
40
+ it('Should not fire given onclose or destroyAllModals functions when dialog is clicked', async () => {
41
+ const { container } = render(Modal, { children, onclose })
39
42
 
40
43
  await fireEvent.click(/** @type {Node} */ (container.querySelector('.dialog')))
41
44
 
45
+ expect(onclose).not.toHaveBeenCalled()
42
46
  expect(destroyAllModals).not.toHaveBeenCalled()
43
47
  })
44
48
 
45
- it('Should fire given destroyAllModals function when escape key is pressed', async () => {
46
- render(Modal, { children })
49
+ it('Should fire given onclose and destroyAllModals functions when escape key is pressed', async () => {
50
+ render(Modal, { children, onclose })
47
51
 
48
52
  fireEvent.keyDown(window, { key: 'Escape' })
49
53
 
54
+ expect(onclose).toHaveBeenCalled()
50
55
  expect(destroyAllModals).toHaveBeenCalled()
51
56
  })
52
57
 
53
- it('Should not fire given onclose function when key other than escape key is pressed', async () => {
54
- render(Modal, { children })
58
+ it('Should not fire given onclose or destroyAllModals function when key other than escape key is pressed', async () => {
59
+ render(Modal, { children, onclose })
55
60
 
56
61
  fireEvent.keyDown(window, { key: 'a' })
57
62
  fireEvent.keyDown(window, { key: 'Enter' })
58
63
  fireEvent.keyDown(window, { key: 'Space' })
59
64
 
65
+ expect(onclose).not.toHaveBeenCalled()
60
66
  expect(destroyAllModals).not.toHaveBeenCalled()
61
67
  })
62
68