@playpilot/tpi 3.7.2 → 3.8.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/README.md +32 -38
- package/dist/link-injections.js +9 -9
- package/index.html +1 -1
- package/package.json +1 -1
- package/src/lib/fakeData.ts +0 -71
- package/src/lib/linkInjection.ts +34 -28
- package/src/lib/session.ts +7 -7
- package/src/lib/types/config.d.ts +25 -0
- package/src/lib/types/global.d.ts +6 -9
- package/src/lib/types/script.d.ts +10 -0
- package/src/lib/types/title.d.ts +0 -2
- package/src/{main.js → main.ts} +3 -5
- package/src/routes/+layout.svelte +99 -47
- package/src/routes/+page.svelte +11 -7
- package/src/routes/components/Editorial/Editor.svelte +1 -4
- package/src/routes/components/Editorial/ManualInjection.svelte +3 -3
- package/src/routes/components/Modal.svelte +0 -3
- package/src/routes/components/Popover.svelte +0 -3
- package/src/routes/components/Title.svelte +0 -5
- package/src/tests/lib/linkInjection.test.js +70 -3
- package/src/tests/routes/+page.test.js +2 -12
- package/static/favicon.png +0 -0
- package/src/lib/types/participant.d.ts +0 -13
- package/src/routes/components/Participants.svelte +0 -51
- package/src/tests/routes/components/Participants.test.js +0 -33
|
@@ -260,9 +260,6 @@
|
|
|
260
260
|
overflow-y: auto;
|
|
261
261
|
overflow-x: hidden;
|
|
262
262
|
line-height: normal;
|
|
263
|
-
|
|
264
|
-
@include reset-svg();
|
|
265
|
-
@include global-outlines();
|
|
266
263
|
}
|
|
267
264
|
|
|
268
265
|
.panel-open {
|
|
@@ -380,7 +377,7 @@
|
|
|
380
377
|
}
|
|
381
378
|
|
|
382
379
|
.alert {
|
|
383
|
-
margin:
|
|
380
|
+
margin: margin(0.5) margin(0.5) 0;
|
|
384
381
|
}
|
|
385
382
|
|
|
386
383
|
.panel {
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
import TitleSearch from './Search/TitleSearch.svelte'
|
|
8
8
|
import { playPilotBaseUrl } from '$lib/constants'
|
|
9
9
|
import { generateInjectionKey } from '$lib/api'
|
|
10
|
-
import { decodeHtmlEntities } from '$lib/html'
|
|
11
10
|
import { getLinkInjectionsParentElement } from '$lib/linkInjection'
|
|
12
11
|
import type { LinkInjection } from '$lib/types/injection'
|
|
13
12
|
import type { TitleData } from '$lib/types/title'
|
|
14
13
|
import { heading } from '$lib/actions/heading'
|
|
14
|
+
import { cleanPhrase } from '$lib/text'
|
|
15
15
|
|
|
16
16
|
interface Props {
|
|
17
17
|
pageText: string
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
selectionSentence = findSentenceForSelection(selection, selectionText)
|
|
59
59
|
|
|
60
60
|
const nodeContent = selection.getRangeAt(0).commonAncestorContainer.textContent
|
|
61
|
-
const documentTextContent =
|
|
62
|
-
if (!nodeContent || !documentTextContent.includes(nodeContent)) { // Selected content is not within the ALI selector
|
|
61
|
+
const documentTextContent = cleanPhrase(pageText)
|
|
62
|
+
if (!nodeContent || !documentTextContent.includes(cleanPhrase(nodeContent))) { // Selected content is not within the ALI selector
|
|
63
63
|
error = 'Selection was not inside of given content'
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -76,9 +76,6 @@
|
|
|
76
76
|
transform: translateY(calc(-100% + var(--offset)));
|
|
77
77
|
z-index: 2147483647; // As high as she goes
|
|
78
78
|
|
|
79
|
-
@include reset-svg();
|
|
80
|
-
@include global-outlines();
|
|
81
|
-
|
|
82
79
|
&.flip {
|
|
83
80
|
top: auto;
|
|
84
81
|
bottom: calc(var(--offset) + 1px); /* Add 1 pixel to account for rounding errors */
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import Genres from './Genres.svelte'
|
|
3
3
|
import Playlinks from './Playlinks.svelte'
|
|
4
4
|
import Description from './Description.svelte'
|
|
5
|
-
import Participants from './Participants.svelte'
|
|
6
5
|
import IconIMDb from './Icons/IconIMDb.svelte'
|
|
7
6
|
import { t } from '$lib/localization'
|
|
8
7
|
import type { TitleData } from '$lib/types/title'
|
|
@@ -59,10 +58,6 @@
|
|
|
59
58
|
|
|
60
59
|
{#if !small}
|
|
61
60
|
<Description text={title.description} blurb={title.blurb} />
|
|
62
|
-
|
|
63
|
-
{#if title.participants?.length}
|
|
64
|
-
<Participants participants={title.participants} />
|
|
65
|
-
{/if}
|
|
66
61
|
{/if}
|
|
67
62
|
</div>
|
|
68
63
|
</div>
|
|
@@ -369,7 +369,7 @@ describe('linkInjection.js', () => {
|
|
|
369
369
|
})
|
|
370
370
|
|
|
371
371
|
it('Should return corresponding messages for failed injections', () => {
|
|
372
|
-
const sentence = '
|
|
372
|
+
const sentence = 'This is <a href="/">a sentence</a> with an injection.'
|
|
373
373
|
document.body.innerHTML = `<p>${sentence}</p>`
|
|
374
374
|
|
|
375
375
|
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
@@ -385,6 +385,23 @@ describe('linkInjection.js', () => {
|
|
|
385
385
|
expect(results[1].failed_message).toBe('Given sentence was not found in the article.')
|
|
386
386
|
})
|
|
387
387
|
|
|
388
|
+
it('Should disregard spaces and special characters in failed message', () => {
|
|
389
|
+
const sentence = 'This is a & sentence with an injection.'
|
|
390
|
+
document.body.innerHTML = `<p>${sentence}</p>`
|
|
391
|
+
|
|
392
|
+
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
393
|
+
|
|
394
|
+
const linkInjections = [
|
|
395
|
+
generateInjection('This is a&sentence with aninjection.', 'an injection'),
|
|
396
|
+
generateInjection('This is a&sentence with aninjection.', 'nothing'),
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
const results = injectLinksInDocument(elements, { aiInjections: [], manualInjections: linkInjections })
|
|
400
|
+
|
|
401
|
+
expect(results[0].failed_message).not.toBeTruthy()
|
|
402
|
+
expect(results[1].failed_message).toBe('The link failed to inject for unknown reasons.')
|
|
403
|
+
})
|
|
404
|
+
|
|
388
405
|
it('Should mount popover component when element other than popover or link is hovered', async () => {
|
|
389
406
|
document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
|
|
390
407
|
|
|
@@ -449,6 +466,29 @@ describe('linkInjection.js', () => {
|
|
|
449
466
|
expect(mount).not.toHaveBeenCalled()
|
|
450
467
|
})
|
|
451
468
|
|
|
469
|
+
it('Should not mount popover on mouseover again if link is already being hovered', async () => {
|
|
470
|
+
document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
|
|
471
|
+
|
|
472
|
+
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
473
|
+
const injections = [
|
|
474
|
+
generateInjection('This is a sentence with an injection.', 'a sentence'),
|
|
475
|
+
generateInjection('This is a sentence with an injection.', 'an injection'),
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
injectLinksInDocument(elements, { aiInjections: injections, manualInjections: [] })
|
|
479
|
+
|
|
480
|
+
const [link1, link2] = document.querySelectorAll('[data-playpilot-injection-key]')
|
|
481
|
+
|
|
482
|
+
vi.useFakeTimers()
|
|
483
|
+
|
|
484
|
+
await fireEvent.mouseEnter(link1)
|
|
485
|
+
vi.advanceTimersByTime(200)
|
|
486
|
+
await fireEvent.mouseEnter(link2)
|
|
487
|
+
vi.advanceTimersByTime(500)
|
|
488
|
+
|
|
489
|
+
expect(mount).toHaveBeenCalledTimes(1)
|
|
490
|
+
})
|
|
491
|
+
|
|
452
492
|
it('Should inject links of the same phrase when multiple are present', () => {
|
|
453
493
|
document.body.innerHTML = '<p>This is a sentence with an injection and another injection</p>'
|
|
454
494
|
|
|
@@ -734,10 +774,10 @@ describe('linkInjection.js', () => {
|
|
|
734
774
|
it('Should return elements in the same order they were given', () => {
|
|
735
775
|
document.body.innerHTML = `<section>
|
|
736
776
|
<div>
|
|
737
|
-
<
|
|
777
|
+
<div><span><strong>Some first text</strong></span></div>
|
|
738
778
|
<div>
|
|
739
779
|
<em>Some empasized</em>
|
|
740
|
-
<main><
|
|
780
|
+
<main><span><em><i>Some deeply nested text</i></em></span></main>
|
|
741
781
|
<div>Some text</div>
|
|
742
782
|
</div>
|
|
743
783
|
</div>
|
|
@@ -752,6 +792,33 @@ describe('linkInjection.js', () => {
|
|
|
752
792
|
expect(elements[2].nodeName).toBe('I')
|
|
753
793
|
expect(elements[3].nodeName).toBe('DIV')
|
|
754
794
|
})
|
|
795
|
+
|
|
796
|
+
it('Should exclude elements that match or are in the given excludeElementsSelector attribute', () => {
|
|
797
|
+
document.body.innerHTML = `<section>
|
|
798
|
+
<p>I am a regular element</p>
|
|
799
|
+
<div data-exclude>I am to be excluded. <span>And so am I!</span></div>
|
|
800
|
+
</section>
|
|
801
|
+
`
|
|
802
|
+
const parent = /** @type {HTMLElement} */ (document.querySelector('section'))
|
|
803
|
+
|
|
804
|
+
const result = getLinkInjectionElements(parent, '[data-exclude]')
|
|
805
|
+
expect(result).toHaveLength(1)
|
|
806
|
+
expect(result[0].innerText).toBe('I am a regular element')
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it('Should return paragraphs fully even if they contain no direct text nodes, skipping empty paragraphs', () => {
|
|
810
|
+
document.body.innerHTML = `<section>
|
|
811
|
+
<p>Some text</p>
|
|
812
|
+
<p><span>Some text</span> <span>broken up</span></p>
|
|
813
|
+
<p><span>Some text</span> <span>broken up</span> but also contains direct text</p>
|
|
814
|
+
<p></p>
|
|
815
|
+
</section>`
|
|
816
|
+
|
|
817
|
+
const parent = /** @type {HTMLElement} */ (document.querySelector('section'))
|
|
818
|
+
const elements = getLinkInjectionElements(parent)
|
|
819
|
+
|
|
820
|
+
expect(elements).toHaveLength(3)
|
|
821
|
+
})
|
|
755
822
|
})
|
|
756
823
|
|
|
757
824
|
describe('getLinkInjectionsParentElement', () => {
|
|
@@ -418,7 +418,7 @@ describe('$routes/+page.svelte', () => {
|
|
|
418
418
|
})
|
|
419
419
|
|
|
420
420
|
describe('custom_style', () => {
|
|
421
|
-
it('Should insert custom html tag with returned value
|
|
421
|
+
it('Should insert custom html tag with returned value from config', async () => {
|
|
422
422
|
vi.mocked(fetchConfig).mockResolvedValueOnce({ custom_style: 'some: style' })
|
|
423
423
|
|
|
424
424
|
// @ts-ignore
|
|
@@ -427,17 +427,7 @@ describe('$routes/+page.svelte', () => {
|
|
|
427
427
|
render(page)
|
|
428
428
|
|
|
429
429
|
await waitFor(() => {
|
|
430
|
-
expect(document.querySelector('#playpilot-custom-style')?.textContent).toBe('
|
|
431
|
-
})
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
it('Should insert custom html tag with returned value for body when no selector is given', async () => {
|
|
435
|
-
vi.mocked(fetchConfig).mockResolvedValueOnce({ custom_style: 'some: style' })
|
|
436
|
-
|
|
437
|
-
render(page)
|
|
438
|
-
|
|
439
|
-
await waitFor(() => {
|
|
440
|
-
expect(document.querySelector('#playpilot-custom-style')?.textContent).toBe('body, .modal, .popover { some: style }')
|
|
430
|
+
expect(document.querySelector('#playpilot-custom-style')?.textContent).toBe('[data-playpilot-link-injections] { some: style }')
|
|
441
431
|
})
|
|
442
432
|
})
|
|
443
433
|
|
package/static/favicon.png
CHANGED
|
Binary file
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export type Participant = {
|
|
2
|
-
sid: string
|
|
3
|
-
name: string
|
|
4
|
-
birth_date: string
|
|
5
|
-
death_date: string | null
|
|
6
|
-
jobs: Job[]
|
|
7
|
-
image: string | null
|
|
8
|
-
image_uuid: string | null
|
|
9
|
-
gender: string
|
|
10
|
-
character: string | null
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type Job = 'actor' | 'writer' | 'director'
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Participant } from '$lib/types/participant'
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
participants: Participant[]
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const { participants }: Props = $props()
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<h2>Cast</h2>
|
|
12
|
-
|
|
13
|
-
<div class="participants">
|
|
14
|
-
{#each participants as participant}
|
|
15
|
-
<div class="participant">
|
|
16
|
-
{participant.name}
|
|
17
|
-
|
|
18
|
-
<div class="character">{participant.character?.slice(0, 20) || ''}{#if participant.character?.length || 0 > 20}...{/if}</div>
|
|
19
|
-
</div>
|
|
20
|
-
{/each}
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<style lang="scss">
|
|
24
|
-
h2 {
|
|
25
|
-
margin: margin(1.5) 0 margin(1);
|
|
26
|
-
font-size: var(--playpilot-cast-title-font-size, 18px);
|
|
27
|
-
line-height: normal;
|
|
28
|
-
font-weight: inherit;
|
|
29
|
-
color: var(--playpilot-cast-title-text-color, var(--playpilot-text-color));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.participants {
|
|
33
|
-
display: flex;
|
|
34
|
-
gap: margin(1);
|
|
35
|
-
width: calc(100% + margin(2));
|
|
36
|
-
padding: 0 margin(1);
|
|
37
|
-
margin: 0 margin(-1);
|
|
38
|
-
overflow: auto;
|
|
39
|
-
white-space: nowrap;
|
|
40
|
-
scrollbar-width: none;
|
|
41
|
-
|
|
42
|
-
&::-webkit-scrollbar {
|
|
43
|
-
display: none;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.character {
|
|
48
|
-
color: var(--playpilot-cast-character-text-color, var(--playpilot-text-color-alt));
|
|
49
|
-
font-style: italic;
|
|
50
|
-
}
|
|
51
|
-
</style>
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import Participants from '../../../routes/components/Participants.svelte'
|
|
5
|
-
|
|
6
|
-
describe('Participants.svelte', () => {
|
|
7
|
-
it('Should render each given participant', () => {
|
|
8
|
-
const participants = [
|
|
9
|
-
{ name: 'Some name', character: 'Some character' },
|
|
10
|
-
{ name: 'Some second name', character: 'Second second character' },
|
|
11
|
-
]
|
|
12
|
-
const { getByText } = render(Participants, { participants })
|
|
13
|
-
|
|
14
|
-
expect(getByText('Some name')).toBeTruthy()
|
|
15
|
-
expect(getByText('Some second name')).toBeTruthy()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('Should limit the character name if it is too long', () => {
|
|
19
|
-
const participants = [{ name: 'Some name', character: 'Some longer character name' }]
|
|
20
|
-
const { getByText, queryByText } = render(Participants, { participants })
|
|
21
|
-
|
|
22
|
-
expect(queryByText('Some longer character name')).not.toBeTruthy()
|
|
23
|
-
expect(getByText('Some longer characte...')).toBeTruthy()
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('Should handle not being given any participants', () => {
|
|
27
|
-
/** @type {Participant[]} */
|
|
28
|
-
const participants = []
|
|
29
|
-
const { container } = render(Participants, { participants })
|
|
30
|
-
|
|
31
|
-
expect(container.querySelectorAll('.participant').length).toBe(0)
|
|
32
|
-
})
|
|
33
|
-
})
|