@playpilot/tpi 7.0.1 → 7.0.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": "7.0.1",
3
+ "version": "7.0.3",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -1,10 +1,21 @@
1
+ import { getLanguage } from '$lib/language'
1
2
  import { getApiToken } from '$lib/token'
2
3
  import type { ParticipantData } from '$lib/types/participant'
4
+ import { paramsToString } from '$lib/url'
3
5
  import type { TitleData } from '../types/title'
4
6
  import { api } from './api'
7
+ import { getRegionBasedOnIp } from './region'
5
8
 
6
9
  export async function fetchTitlesForParticipant(participant: ParticipantData, { page = 1 }: { page?: number } = {}): Promise<TitleData[]> {
7
- const response = await api<{ results: TitleData[] }>(`/titles/browse?api-token=${getApiToken()}&participant_sid=${participant.sid}&page=${page}`)
10
+ const params = {
11
+ language: getLanguage(),
12
+ region: await getRegionBasedOnIp(),
13
+ include_count: 'false',
14
+ participant_sid: participant.sid,
15
+ page,
16
+ }
17
+
18
+ const response = await api<{ results: TitleData[] }>(`/titles/browse?api-token=${getApiToken()}&${paramsToString(params)}`)
8
19
 
9
20
  return response.results
10
21
  }
@@ -236,6 +236,21 @@ export const translations = {
236
236
  [Language.Swedish]: 'Sök',
237
237
  [Language.Danish]: 'Søg',
238
238
  },
239
+ 'Born On': {
240
+ [Language.English]: 'Born on',
241
+ [Language.Swedish]: 'Född den',
242
+ [Language.Danish]: 'Født den',
243
+ },
244
+ 'Died On': {
245
+ [Language.English]: 'Died on',
246
+ [Language.Swedish]: 'Död den',
247
+ [Language.Danish]: 'Død den',
248
+ },
249
+ 'Credits': {
250
+ [Language.English]: 'Credits',
251
+ [Language.Swedish]: 'Medverkande',
252
+ [Language.Danish]: 'Medvirkende',
253
+ },
239
254
 
240
255
  // Genres
241
256
  'All': {
@@ -97,6 +97,12 @@
97
97
  <li>Anatomy of a Fall</li>
98
98
  <li>Killers of the Flower Moon</li>
99
99
  </ul>
100
+
101
+ <h2>Example of short manual match</h2>
102
+ <div>
103
+ <p><em>The Testaments</em> er en spændende fortsættelse i universet fra <em>The Handmaid’s Tale</em> og bygger på Margaret Atwoods populære bog. Serien dykker endnu dybere ned i det dystopiske samfund Gilead og følger nye karakterer, der kæmper imod regimet.</p><p><strong>Star Wars: Maul – Shadow Lord</strong></p>
104
+ <p>Denne serie handler om at Maul forsøger at genopbygge sit kriminelle syndikat på en planet, der endnu ikke er under Imperiets kontrol. Her møder han en desillusioneret ung jedi-padawan, som måske kan blive den lærling, han har brug for i sin jagt på hævn.</p>
105
+ </div>
100
106
  </article>
101
107
  {/key}
102
108
  {/if}
@@ -54,7 +54,7 @@
54
54
 
55
55
  &:hover {
56
56
  cursor: pointer;
57
- color: white;
57
+ color: theme(text-color);
58
58
  }
59
59
  }
60
60
 
@@ -183,6 +183,7 @@
183
183
  }
184
184
 
185
185
  hr {
186
+ margin: margin(0.5) 0;
186
187
  border-color: theme(primary);
187
188
  }
188
189
 
@@ -367,9 +367,8 @@
367
367
  }
368
368
 
369
369
  &[disabled] {
370
- opacity: 0.75;
370
+ opacity: 0.65;
371
371
  background: theme(lighter);
372
- color: theme(content-light);
373
372
  }
374
373
  }
375
374
 
@@ -310,6 +310,7 @@
310
310
 
311
311
  &:hover {
312
312
  filter: brightness(1.2);
313
+ outline: 1px solid currentColor;
313
314
  }
314
315
  }
315
316
 
@@ -87,8 +87,9 @@
87
87
  // Get the node the text is in. If the content of the node is very short we use the parent node instead.
88
88
  // This is meant for content that is inside of other elements such as <p>Some <strong>word</strong> in a sentence</p>
89
89
  // If we selected "word", we'd still want the full sentence, rather than just the "word".
90
+ // We don't do this if the node is a paragraph, since that implies it is a full sentence by itself at least.
90
91
  let node = range.startContainer
91
- while((node.textContent || '').length <= selectionText.length * 3 && node.parentNode) {
92
+ while((node.textContent || '').length <= selectionText.length * 3 && node.parentNode && node.nodeName !== 'P') {
92
93
  // Stop when reaching the parent or any child of the parent that doesn't contain any extra content.
93
94
  // This prevents matching on text outside of elements that are top level already.
94
95
  if (node.parentNode.textContent === injectionsParent.textContent) break
@@ -103,9 +104,8 @@
103
104
  const absoluteStart = getAbsoluteOffset(node, range.startContainer, range.startOffset)
104
105
  const absoluteEnd = getAbsoluteOffset(node, range.endContainer, range.endOffset)
105
106
 
106
- // Find sentence boundaries
107
107
  const before = fullText.slice(0, absoluteStart).lastIndexOf('.')
108
- const afterMatch = fullText.slice(absoluteEnd).match(/[.!?]/)
108
+ const afterMatch = fullText.slice(absoluteEnd).match(/[.!?\n]/)
109
109
  const after = afterMatch ? absoluteEnd + afterMatch.index! : fullText.length
110
110
 
111
111
  const sentenceStart = before === -1 ? 0 : before + 1
@@ -4,6 +4,7 @@
4
4
  import IconArrow from './Icons/IconArrow.svelte'
5
5
  import IconIMDb from './Icons/IconIMDb.svelte'
6
6
  import TitlePoster from './TitlePoster.svelte'
7
+ import { t } from '$lib/localization'
7
8
 
8
9
  interface Props {
9
10
  title: TitleData
@@ -30,7 +31,7 @@
30
31
  </div>
31
32
 
32
33
  <div>{title.year}</div>
33
- <div>{title.type}</div>
34
+ <div>{t(`Type: ${title.type}`)}</div>
34
35
 
35
36
  {#if title.length}
36
37
  <div>{title.length} min</div>
@@ -57,13 +57,13 @@
57
57
 
58
58
  {#if birth_date}
59
59
  <p class="dates">
60
- Born on <strong>{formatDate(birth_date)}</strong>{#if death_date}, died on <strong>{formatDate(death_date)}</strong>{/if}
60
+ {t('Born On')} <strong>{formatDate(birth_date)}</strong>{#if death_date}, {t('Died On')} <strong>{formatDate(death_date)}</strong>{/if}
61
61
  </p>
62
62
  {/if}
63
63
  </div>
64
64
 
65
65
  <div class="content">
66
- <div class="heading small" use:heading={3} id="credits">Credits</div>
66
+ <div class="heading small" use:heading={3} id="credits">{t('Credits')}</div>
67
67
 
68
68
  <div class="list">
69
69
  {#each titles as title}
@@ -35,6 +35,7 @@
35
35
  &:hover {
36
36
  filter: brightness(1.2);
37
37
  transform: scale(1.05);
38
+ outline: 1px solid theme(button-hover-border-color, text-color-alt);
38
39
  }
39
40
  }
40
41
  </style>
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+
3
+ import { api } from '$lib/api/api'
4
+ import { fetchTitlesForParticipant } from '$lib/api/participants'
5
+ import { participants, title } from '$lib/fakeData'
6
+ import { getApiToken } from '$lib/token'
7
+ import { fakeFetch } from '../../helpers'
8
+
9
+ vi.mock('$lib/token', () => ({
10
+ getApiToken: vi.fn(),
11
+ }))
12
+
13
+ vi.mock('$lib/api/api', () => ({
14
+ api: vi.fn(),
15
+ }))
16
+
17
+ describe('$lib/api/participants', () => {
18
+ beforeEach(() => {
19
+ // @ts-ignore
20
+ window.PlayPilotLinkInjections = {}
21
+
22
+ vi.resetAllMocks()
23
+ vi.mocked(getApiToken).mockReturnValue('some-token')
24
+ fakeFetch()
25
+ })
26
+
27
+ describe('fetchTitlesForParticipant', () => {
28
+ it('Should call api with given parameters and always include include_count=false and return titles', async () => {
29
+ vi.mocked(api).mockResolvedValueOnce({ results: [title] })
30
+
31
+ const response = await fetchTitlesForParticipant(participants[0])
32
+
33
+ expect(api).toHaveBeenCalledWith(`/titles/browse?api-token=some-token&language=en-US&include_count=false&participant_sid=${participants[0].sid}&page=1`)
34
+ expect(response).toEqual([title])
35
+ })
36
+
37
+ it('Should use given page number', async () => {
38
+ vi.mocked(api).mockResolvedValueOnce({ results: [title] })
39
+
40
+ await fetchTitlesForParticipant(participants[0], { page: 5 })
41
+
42
+ expect(api).toHaveBeenCalledWith(expect.stringContaining('page=5'))
43
+ })
44
+
45
+ it('Should throw when api returns error', async () => {
46
+ vi.mocked(api).mockRejectedValueOnce({ error: 'message' })
47
+
48
+ await expect(async () => await fetchTitlesForParticipant(participants[0])).rejects.toThrow()
49
+ })
50
+
51
+ it('Should include region if set', async () => {
52
+ vi.mocked(api).mockResolvedValueOnce({ results: [] })
53
+
54
+ window.PlayPilotLinkInjections.region = 'nl'
55
+
56
+ await fetchTitlesForParticipant(participants[0])
57
+
58
+ expect(api).toHaveBeenCalledWith(`/titles/browse?api-token=some-token&language=en-US&region=nl&include_count=false&participant_sid=${participants[0].sid}&page=1`)
59
+ })
60
+ })
61
+ })
@@ -4,6 +4,7 @@ import { api } from '$lib/api/api'
4
4
  import { fetchSimilarTitles, fetchTitles } from '$lib/api/titles'
5
5
  import { title } from '$lib/fakeData'
6
6
  import { getApiToken } from '$lib/token'
7
+ import { fakeFetch } from '../../helpers'
7
8
 
8
9
  vi.mock('$lib/token', () => ({
9
10
  getApiToken: vi.fn(),
@@ -13,10 +14,11 @@ vi.mock('$lib/api/api', () => ({
13
14
  api: vi.fn(),
14
15
  }))
15
16
 
16
- describe('$lib/api/ads', () => {
17
+ describe('$lib/api/titles', () => {
17
18
  beforeEach(() => {
18
19
  vi.resetAllMocks()
19
20
  vi.mocked(getApiToken).mockReturnValue('some-token')
21
+ fakeFetch()
20
22
  })
21
23
 
22
24
  describe('fetchTitles', () => {
@@ -186,6 +186,48 @@ describe('ManualInjection.svelte', () => {
186
186
  })
187
187
  })
188
188
 
189
+ it('Should not select parent if content is too short but is in a paragraph', async () => {
190
+ vi.mocked(searchTitles).mockResolvedValueOnce([title])
191
+ vi.mocked(getIndexOfSelection).mockReturnValueOnce({ start: 0, end: 0 })
192
+
193
+ document.body.innerHTML = '<main><p>Some paragraph</p> <p>Some other paragraph</p></main>'
194
+
195
+ const container = document.querySelectorAll('p')[1]
196
+
197
+ // @ts-ignore
198
+ window.getSelection = vi.fn(() => ({
199
+ toString: () => 'Some other paragraph',
200
+ getRangeAt: () => ({
201
+ commonAncestorContainer: container,
202
+ startContainer: container,
203
+ startOffset: 0,
204
+ endOffset: 0,
205
+ cloneContents: () => ({ childNodes: [] }),
206
+ }),
207
+ anchorNode: container,
208
+ focusNode: container,
209
+ }))
210
+
211
+ const onsave = vi.fn()
212
+ const { getByText } = render(ManualInjection, { pageText: document.body.innerText, onsave })
213
+
214
+ await fireEvent.mouseUp(window)
215
+ await fireEvent.click(getByText(title.title))
216
+ await fireEvent.click(getByText('Add playlink'))
217
+
218
+ expect(onsave).toHaveBeenCalledWith({
219
+ sid: title.sid,
220
+ title: 'Some other paragraph',
221
+ sentence: 'Some other paragraph',
222
+ playpilot_url: `https://www.playpilot.com/show/${title.slug}`,
223
+ key: expect.any(String),
224
+ title_details: title,
225
+ manual: true,
226
+ phrase_before: expect.any(String),
227
+ phrase_after: expect.any(String),
228
+ })
229
+ })
230
+
189
231
  it('Should not select content if it is outside of given parent', async () => {
190
232
  vi.mocked(searchTitles).mockResolvedValueOnce([title])
191
233
  vi.mocked(getIndexOfSelection).mockReturnValueOnce({ start: 0, end: 0 })