@playpilot/tpi 1.0.0

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.
Files changed (101) hide show
  1. package/.github/workflows/tests.yml +22 -0
  2. package/.prettierignore +4 -0
  3. package/.prettierrc +16 -0
  4. package/README.md +38 -0
  5. package/dist/link-injections.js +7 -0
  6. package/eslint.config.js +33 -0
  7. package/index.html +11 -0
  8. package/jsconfig.json +19 -0
  9. package/package.json +35 -0
  10. package/src/app.d.ts +13 -0
  11. package/src/app.html +12 -0
  12. package/src/demo.spec.js +7 -0
  13. package/src/lib/api.js +160 -0
  14. package/src/lib/array.js +15 -0
  15. package/src/lib/auth.js +84 -0
  16. package/src/lib/constants.js +2 -0
  17. package/src/lib/enums/TrackingEvent.js +15 -0
  18. package/src/lib/fakeData.js +140 -0
  19. package/src/lib/genres.json +420 -0
  20. package/src/lib/global.css +37 -0
  21. package/src/lib/hash.js +15 -0
  22. package/src/lib/html.js +21 -0
  23. package/src/lib/index.js +1 -0
  24. package/src/lib/linkInjection.js +275 -0
  25. package/src/lib/search.js +24 -0
  26. package/src/lib/text.js +61 -0
  27. package/src/lib/tracking.js +32 -0
  28. package/src/lib/variables.css +16 -0
  29. package/src/main.js +45 -0
  30. package/src/routes/+layout.svelte +54 -0
  31. package/src/routes/+page.svelte +96 -0
  32. package/src/routes/components/AfterArticlePlaylinks.svelte +90 -0
  33. package/src/routes/components/ContextMenu.svelte +67 -0
  34. package/src/routes/components/Description.svelte +47 -0
  35. package/src/routes/components/Editorial/Alert.svelte +18 -0
  36. package/src/routes/components/Editorial/DragHandle.svelte +134 -0
  37. package/src/routes/components/Editorial/Editor.svelte +277 -0
  38. package/src/routes/components/Editorial/EditorItem.svelte +260 -0
  39. package/src/routes/components/Editorial/ManualInjection.svelte +192 -0
  40. package/src/routes/components/Editorial/PlaylinkTypeSelect.svelte +132 -0
  41. package/src/routes/components/Editorial/Search/TitleSearch.svelte +176 -0
  42. package/src/routes/components/Editorial/Switch.svelte +76 -0
  43. package/src/routes/components/Editorial/TextInput.svelte +29 -0
  44. package/src/routes/components/Genres.svelte +41 -0
  45. package/src/routes/components/Icons/IconAlign.svelte +12 -0
  46. package/src/routes/components/Icons/IconBack.svelte +3 -0
  47. package/src/routes/components/Icons/IconBookmark.svelte +3 -0
  48. package/src/routes/components/Icons/IconChevron.svelte +18 -0
  49. package/src/routes/components/Icons/IconClose.svelte +3 -0
  50. package/src/routes/components/Icons/IconContinue.svelte +3 -0
  51. package/src/routes/components/Icons/IconDots.svelte +5 -0
  52. package/src/routes/components/Icons/IconEnlarge.svelte +12 -0
  53. package/src/routes/components/Icons/IconIMDb.svelte +3 -0
  54. package/src/routes/components/Icons/IconNewTab.svelte +3 -0
  55. package/src/routes/components/Modal.svelte +106 -0
  56. package/src/routes/components/Participants.svelte +44 -0
  57. package/src/routes/components/Playlinks.svelte +155 -0
  58. package/src/routes/components/Popover.svelte +95 -0
  59. package/src/routes/components/RoundButton.svelte +38 -0
  60. package/src/routes/components/SkeletonText.svelte +33 -0
  61. package/src/routes/components/Title.svelte +180 -0
  62. package/src/routes/components/TitleModal.svelte +24 -0
  63. package/src/routes/components/TitlePopover.svelte +17 -0
  64. package/src/tests/helpers.js +18 -0
  65. package/src/tests/lib/api.test.js +162 -0
  66. package/src/tests/lib/array.test.js +14 -0
  67. package/src/tests/lib/auth.test.js +115 -0
  68. package/src/tests/lib/hash.test.js +28 -0
  69. package/src/tests/lib/html.test.js +16 -0
  70. package/src/tests/lib/linkInjection.test.js +754 -0
  71. package/src/tests/lib/search.test.js +42 -0
  72. package/src/tests/lib/text.test.js +94 -0
  73. package/src/tests/lib/tracking.test.js +71 -0
  74. package/src/tests/routes/+page.test.js +109 -0
  75. package/src/tests/routes/components/AfterArticlePlaylinks.test.js +115 -0
  76. package/src/tests/routes/components/ContextMenu.test.js +37 -0
  77. package/src/tests/routes/components/Description.test.js +58 -0
  78. package/src/tests/routes/components/Editorial/Alert.test.js +17 -0
  79. package/src/tests/routes/components/Editorial/DragHandle.test.js +55 -0
  80. package/src/tests/routes/components/Editorial/Editor.test.js +64 -0
  81. package/src/tests/routes/components/Editorial/EditorItem.test.js +142 -0
  82. package/src/tests/routes/components/Editorial/ManualInjection.test.js +114 -0
  83. package/src/tests/routes/components/Editorial/PlaylinkTypeSelect.test.js +63 -0
  84. package/src/tests/routes/components/Editorial/Search/TitleSearch.test.js +58 -0
  85. package/src/tests/routes/components/Editorial/Switch.test.js +60 -0
  86. package/src/tests/routes/components/Editorial/TextInput.test.js +30 -0
  87. package/src/tests/routes/components/Genres.test.js +37 -0
  88. package/src/tests/routes/components/Modal.test.js +84 -0
  89. package/src/tests/routes/components/Participants.test.js +33 -0
  90. package/src/tests/routes/components/Playlinks.test.js +101 -0
  91. package/src/tests/routes/components/Popover.test.js +66 -0
  92. package/src/tests/routes/components/RoundButton.test.js +35 -0
  93. package/src/tests/routes/components/SkeletonText.test.js +12 -0
  94. package/src/tests/routes/components/Title.test.js +82 -0
  95. package/src/tests/routes/components/TitleModal.test.js +33 -0
  96. package/src/tests/routes/components/TitlePopover.test.js +23 -0
  97. package/src/tests/setup.js +53 -0
  98. package/src/typedefs.js +72 -0
  99. package/static/favicon.png +0 -0
  100. package/svelte.config.js +13 -0
  101. package/vite.config.js +61 -0
@@ -0,0 +1,101 @@
1
+ import { fireEvent, render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+
4
+ import Playlinks from '../../../routes/components/Playlinks.svelte'
5
+ import { title } from '$lib/fakeData'
6
+ import { track } from '$lib/tracking'
7
+ import { TrackingEvent } from '$lib/enums/TrackingEvent'
8
+ import { getContext } from 'svelte'
9
+
10
+ vi.mock('$lib/tracking', () => ({
11
+ track: vi.fn(),
12
+ }))
13
+
14
+ vi.mock('svelte', async (importActual) => ({
15
+ ...(await importActual()),
16
+ getContext: vi.fn(),
17
+ }))
18
+
19
+ describe('Playlinks.svelte', () => {
20
+ it('Should render each given playlink', () => {
21
+ const playlinks = [
22
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
23
+ { name: 'Some other playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
24
+ ]
25
+ const { getByText } = render(Playlinks, { playlinks, title })
26
+
27
+ expect(getByText('Some playlink')).toBeTruthy()
28
+ expect(getByText('Some other playlink')).toBeTruthy()
29
+ })
30
+
31
+ it('Should show empty state when no playlinks were given', () => {
32
+ /** @type {PlaylinkData[]} */
33
+ const playlinks = []
34
+ const { container } = render(Playlinks, { playlinks, title })
35
+
36
+ expect(container.querySelector('.empty')).toBeTruthy()
37
+ })
38
+
39
+ it('Should render category as words', () => {
40
+ const playlinks = [
41
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
42
+ { name: 'Some buy playlink', logo_url: 'logo', extra_info: { category: 'BUY' } },
43
+ { name: 'Some rent playlink', logo_url: 'logo', extra_info: { category: 'RENT' } },
44
+ { name: 'Some other playlink', logo_url: 'logo', extra_info: { category: 'other' } },
45
+ ]
46
+ const { getByText, getAllByText } = render(Playlinks, { playlinks, title })
47
+
48
+ expect(getAllByText('Stream')).toHaveLength(2)
49
+ expect(getByText('Buy')).toBeTruthy()
50
+ expect(getByText('Rent')).toBeTruthy()
51
+ })
52
+
53
+ it('Should call track function when clicked', async () => {
54
+ const playlinks = [
55
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
56
+ { name: 'Some other playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
57
+ ]
58
+ const { getByText } = render(Playlinks, { playlinks, title })
59
+
60
+ await fireEvent.click(getByText(playlinks[0].name))
61
+
62
+ expect(track).toHaveBeenCalledWith(TrackingEvent.TitlePopoverPlaylinkClick, title, { playlink: playlinks[0].name })
63
+ })
64
+
65
+ it('Should call track function for modal when clicked inside of modal scope', async () => {
66
+ /** @type {import('vitest').Mock} */ (getContext).mockReturnValueOnce('modal')
67
+
68
+ const playlinks = [
69
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
70
+ ]
71
+ const { getByText } = render(Playlinks, { playlinks, title })
72
+
73
+ await fireEvent.click(getByText(playlinks[0].name))
74
+
75
+ expect(track).toHaveBeenCalledWith(TrackingEvent.TitleModalPlaylinkClick, title, { playlink: playlinks[0].name })
76
+ })
77
+
78
+ it('Should exclude playlinks without logo', () => {
79
+ const playlinks = [
80
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
81
+ { name: 'Some playlink', extra_info: { category: 'BUY' } },
82
+ { name: 'Some playlink', logo_url: '', extra_info: { category: 'RENT' } },
83
+ { name: 'Some playlink', logo_url: null, extra_info: { category: 'other' } },
84
+ ]
85
+ const { getAllByText } = render(Playlinks, { playlinks, title })
86
+
87
+ expect(getAllByText('Some playlink')).toHaveLength(1)
88
+ })
89
+
90
+ it('Should not have list class by default', async () => {
91
+ const { container } = render(Playlinks, { playlinks: [], title })
92
+
93
+ expect(container.querySelector('.list')).not.toBeTruthy()
94
+ })
95
+
96
+ it('Should have list class when prop is given', async () => {
97
+ const { container } = render(Playlinks, { playlinks: [], title, list: true })
98
+
99
+ expect(container.querySelector('.list')).toBeTruthy()
100
+ })
101
+ })
@@ -0,0 +1,66 @@
1
+ import { render, waitFor } from '@testing-library/svelte'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import Popover from '../../../routes/components/Popover.svelte'
5
+ import { createRawSnippet } from 'svelte'
6
+
7
+ describe('Popover.svelte', () => {
8
+ const children = createRawSnippet(() => ({ render: () => 'Some snippet' }))
9
+
10
+ it('Should render given snippet', () => {
11
+ const { getByText } = render(Popover, { children })
12
+
13
+ expect(getByText('Some snippet')).toBeTruthy()
14
+ })
15
+
16
+ it('Should flip its position when near the top of the screen', async () => {
17
+ const { container } = render(Popover, { children })
18
+
19
+ const element = /** @type {HTMLElement} */ (container.querySelector('.popover'))
20
+
21
+ await waitFor(() => {
22
+ expect(element?.classList).toContain('flip')
23
+ })
24
+ })
25
+
26
+ it('Should not flip its position when not near the top of the screen', async () => {
27
+ document.body.innerHTML = '<div style="height: 1000px"></div>'
28
+
29
+ const { container } = render(Popover, { children })
30
+ const element = /** @type {HTMLElement} */ (container.querySelector('.popover'))
31
+
32
+ // @ts-ignore
33
+ window.HTMLElement.prototype.getBoundingClientRect = () => ({ top: 500 })
34
+
35
+ await new Promise(res => setTimeout(res, 500)) // Wait for element to be positioned
36
+
37
+ expect(element?.classList).not.toContain('flip')
38
+ })
39
+
40
+ it('Should position itself from the right side of the screen if the element is offscreen', async () => {
41
+ document.body.innerHTML = '<div style="height: 1000px"></div>'
42
+
43
+ const { container } = render(Popover, { children })
44
+ const element = /** @type {HTMLElement} */ (container.querySelector('.popover'))
45
+
46
+ // @ts-ignore
47
+ window.HTMLElement.prototype.getBoundingClientRect = () => ({ right: 2000 })
48
+
49
+ await waitFor(() => {
50
+ expect(element?.style).toContain('left')
51
+ })
52
+ })
53
+
54
+ it('Should position itself from the right side of the screen if the element is offscreen', async () => {
55
+ document.body.innerHTML = '<div style="height: 1000px"></div>'
56
+
57
+ const { container } = render(Popover, { children })
58
+ const element = /** @type {HTMLElement} */ (container.querySelector('.popover'))
59
+
60
+ // @ts-ignore
61
+ window.HTMLElement.prototype.getBoundingClientRect = () => ({ right: 0 })
62
+
63
+ await new Promise(res => setTimeout(res, 500)) // Wait for element to be positioned
64
+ expect(element?.style).not.toContain('left: -992px;')
65
+ })
66
+ })
@@ -0,0 +1,35 @@
1
+ import { fireEvent, render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+
4
+ import RoundButton from '../../../routes/components/RoundButton.svelte'
5
+
6
+ describe('RoundButton.svelte', () => {
7
+ it('Should render as a button by default', () => {
8
+ const { getByRole } = render(RoundButton)
9
+
10
+ expect(getByRole('button')).toBeTruthy()
11
+ })
12
+
13
+ it('Should render as a link when an href is passed', () => {
14
+ const { getByRole } = render(RoundButton, { href: 'https://some-link.com' })
15
+
16
+ expect(getByRole('link')).toBeTruthy()
17
+ })
18
+
19
+ it('Should include any rest props on the element', () => {
20
+ const { getByRole } = render(RoundButton, { href: 'https://some-link.com', 'aria-label': 'Some label', id: 'some-id' })
21
+
22
+ expect(getByRole('link').getAttribute('href')).toBe('https://some-link.com')
23
+ expect(getByRole('link').getAttribute('aria-label')).toBe('Some label')
24
+ expect(getByRole('link').getAttribute('id')).toBe('some-id')
25
+ })
26
+
27
+ it('Should fire given onclick function on click', async () => {
28
+ const onclick = vi.fn()
29
+ const { getByRole } = render(RoundButton, { onclick })
30
+
31
+ await fireEvent.click(getByRole('button'))
32
+
33
+ expect(onclick).toHaveBeenCalled()
34
+ })
35
+ })
@@ -0,0 +1,12 @@
1
+ import { render } from '@testing-library/svelte'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import SkeletonText from '../../../routes/components/SkeletonText.svelte'
5
+
6
+ describe('SkeletonText.svelte', () => {
7
+ it('Should render the given amount of lines', () => {
8
+ const { container } = render(SkeletonText, { lines: 3 })
9
+
10
+ expect(container.querySelectorAll('.skeleton').length).toBe(3)
11
+ })
12
+ })
@@ -0,0 +1,82 @@
1
+ import { render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi, beforeEach } from 'vitest'
3
+
4
+ import Title from '../../../routes/components/Title.svelte'
5
+ import { title } from '$lib/fakeData'
6
+
7
+ vi.mock('$lib/tracking', () => ({
8
+ track: vi.fn(),
9
+ }))
10
+
11
+ vi.mock('svelte', async (importActual) => ({
12
+ ...(await importActual()),
13
+ getContext: vi.fn(),
14
+ }))
15
+
16
+ describe('Title.svelte', () => {
17
+ beforeEach(() => {
18
+ vi.resetAllMocks()
19
+ })
20
+
21
+ it('Should render the given playlinks', () => {
22
+ const { getByText } = render(Title, { title })
23
+
24
+ expect(getByText(title.providers[0].name)).toBeTruthy()
25
+ expect(getByText(title.providers[1].name)).toBeTruthy()
26
+ })
27
+
28
+ it('Should render the given image', () => {
29
+ const { getByAltText } = render(Title, { title })
30
+
31
+ expect(getByAltText(`Movie poster for '${title.title}'`)).toBeTruthy()
32
+ })
33
+
34
+ it('Should render the given info', () => {
35
+ const { getByText } = render(Title, { title })
36
+
37
+ expect(getByText(title.title)).toBeTruthy()
38
+ expect(getByText(title.imdb_score)).toBeTruthy()
39
+ expect(getByText(title.year)).toBeTruthy()
40
+ })
41
+
42
+ it('Should include data attributes', () => {
43
+ const { container } = render(Title, { title })
44
+
45
+ const element = /** @type {HTMLElement} */ (container.querySelector('[data-playpilot-link-injections-title]'))
46
+
47
+ expect(element).toBeTruthy()
48
+ expect(element?.dataset.playpilotOriginalTitle).toBe(title.original_title)
49
+ })
50
+
51
+ it('Should not have small class by default', () => {
52
+ const { container } = render(Title, { title })
53
+
54
+ expect(container.querySelector('.small')).not.toBeTruthy()
55
+ })
56
+
57
+ it('Should have small class when prop is given', () => {
58
+ const { container } = render(Title, { title, small: true })
59
+
60
+ expect(container.querySelector('.small')).toBeTruthy()
61
+ })
62
+
63
+ it('Should render given description', () => {
64
+ const { getByText } = render(Title, { title: { ...title, description: 'Some description' } })
65
+
66
+ expect(getByText('Some description')).toBeTruthy()
67
+ })
68
+
69
+ it('Should not render description when small prop is given', () => {
70
+ const { queryByText } = render(Title, { title: { ...title, description: 'Some description' }, small: true })
71
+
72
+ expect(queryByText('Some description')).not.toBeTruthy()
73
+ })
74
+
75
+ it('Should render as compacter version when compact prop is true', () => {
76
+ const { queryByAltText, container } = render(Title, { title: { ...title, description: 'Some description' }, compact: true })
77
+
78
+ expect(queryByAltText(`Movie poster for '${title.title}'`)).not.toBeTruthy()
79
+ expect(container.querySelector('.compact')).toBeTruthy()
80
+ expect(container.querySelector('.faded')).toBeTruthy()
81
+ })
82
+ })
@@ -0,0 +1,33 @@
1
+ import { fireEvent, render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi, beforeEach } from 'vitest'
3
+
4
+ import TitleModal from '../../../routes/components/TitleModal.svelte'
5
+ import { title } from '$lib/fakeData'
6
+ import { track } from '$lib/tracking'
7
+ import { TrackingEvent } from '$lib/enums/TrackingEvent'
8
+
9
+ vi.mock('$lib/tracking', () => ({
10
+ track: vi.fn(),
11
+ }))
12
+
13
+ describe('TitleModal.svelte', () => {
14
+ beforeEach(() => {
15
+ vi.resetAllMocks()
16
+ })
17
+
18
+ const onclose = vi.fn()
19
+
20
+ it('Should call track function when rendered', () => {
21
+ render(TitleModal, { onclose, title })
22
+
23
+ expect(track).toHaveBeenCalledWith(TrackingEvent.TitleModalView, title)
24
+ })
25
+
26
+ it('Should call track function when scrolled', async () => {
27
+ const { getByRole } = render(TitleModal, { onclose, title })
28
+
29
+ await fireEvent.scroll(getByRole('dialog'))
30
+
31
+ expect(track).toHaveBeenCalledWith(TrackingEvent.TitleModalScroll, title)
32
+ })
33
+ })
@@ -0,0 +1,23 @@
1
+ import { render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi, beforeEach } from 'vitest'
3
+
4
+ import TitlePopover from '../../../routes/components/TitlePopover.svelte'
5
+ import { title } from '$lib/fakeData'
6
+ import { track } from '$lib/tracking'
7
+ import { TrackingEvent } from '$lib/enums/TrackingEvent'
8
+
9
+ vi.mock('$lib/tracking', () => ({
10
+ track: vi.fn(),
11
+ }))
12
+
13
+ describe('TitlePopover.svelte', () => {
14
+ beforeEach(() => {
15
+ vi.resetAllMocks()
16
+ })
17
+
18
+ it('Should call track function when rendered', () => {
19
+ render(TitlePopover, { title })
20
+
21
+ expect(track).toHaveBeenCalledWith(TrackingEvent.TitlePopoverView, title)
22
+ })
23
+ })
@@ -0,0 +1,53 @@
1
+ import { cleanup } from '@testing-library/svelte'
2
+ import { afterEach, beforeAll, beforeEach, vi } from 'vitest'
3
+
4
+ // https://github.com/jsdom/jsdom/issues/3429#issuecomment-1936128876
5
+ const mockAnimations = () => {
6
+ Element.prototype.animate ??= vi.fn().mockReturnValue({
7
+ finished: Promise.resolve(),
8
+ cancel: vi.fn(),
9
+ startTime: null,
10
+ currentTime: null,
11
+ })
12
+ }
13
+
14
+ const mockLocalStorage = () => {
15
+ /** @type {Record<string, string>} */
16
+ let store = {}
17
+
18
+ // @ts-ignore
19
+ ;(window).localStorage = {
20
+ getItem: function (key) {
21
+ return store[key] || null
22
+ },
23
+ setItem: function (key, value) {
24
+ store[key] = value.toString()
25
+ },
26
+ removeItem: function (key) {
27
+ delete store[key]
28
+ },
29
+ clear: function () {
30
+ store = {}
31
+ },
32
+ }
33
+ }
34
+
35
+ beforeAll(() => {
36
+ mockAnimations()
37
+ mockLocalStorage()
38
+ })
39
+
40
+ beforeEach(() => {
41
+ // @ts-ignore
42
+ window.PlayPilotLinkInjections = { token: 'some-token' }
43
+
44
+ // Reset cookies
45
+ document.cookie.split(';').forEach((cookie) => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, '=;'))
46
+
47
+ window.HTMLElement.prototype.scrollIntoView = vi.fn()
48
+ localStorage.clear()
49
+ })
50
+
51
+ afterEach(() => {
52
+ cleanup()
53
+ })
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @typedef {{
3
+ * sid: string,
4
+ * slug: string,
5
+ * poster_uuid: string,
6
+ * standing_poster_uuid: string,
7
+ * genres: string[],
8
+ * year: number,
9
+ * imdb_score: number,
10
+ * type: string,
11
+ * providers: PlaylinkData[],
12
+ * description: string,
13
+ * small_poster: string,
14
+ * medium_poster: string,
15
+ * standing_poster: string,
16
+ * title: string,
17
+ * original_title: string,
18
+ * length?: number,
19
+ * participants?: Participant[],
20
+ * blurb?: string,
21
+ * }} TitleData
22
+ */
23
+
24
+ /**
25
+ * @typedef {{ sid: string, name: string, url: string, logo_url: string, extra_info: { category: PlaylinkCategory } }} PlaylinkData
26
+ */
27
+
28
+ /**
29
+ * @typedef {'SVOD' | 'BUY' | 'RENT'} PlaylinkCategory
30
+ */
31
+
32
+ /**
33
+ * @typedef {{ username: string, profile: { display_name: string, profile_photo: string } }} User
34
+ */
35
+
36
+ /**
37
+ * @typedef {{ sid: string, name: string, birth_date: string, death_date: string | null, jobs: Job[], image: string | null, image_uuid: string | null, gender: string, character: string | null }} Participant
38
+ */
39
+
40
+ /**
41
+ * @typedef {'actor' | 'writer' | 'director'} Job
42
+ */
43
+
44
+ /**
45
+ * @typedef {{
46
+ * sid: string,
47
+ * title: string,
48
+ * sentence: string,
49
+ * playpilot_url: string,
50
+ * key: string,
51
+ * title_details?: TitleData,
52
+ * inactive?: boolean,
53
+ * failed?: boolean,
54
+ * in_text?: boolean,
55
+ * after_article?: boolean
56
+ * after_article_style?: 'modal_button' | 'playlinks' | null
57
+ * }} LinkInjection
58
+ */
59
+
60
+ /** @typedef {Record<string, { elementIndex: number, from: number, to: number }>} LinkInjectionRanges */
61
+
62
+ /**
63
+ * @typedef {'bottom' | 'center'} Alignment
64
+ */
65
+
66
+ /**
67
+ * @typedef {{ injections_ready: boolean, injections_enabled: boolean, link_injections: LinkInjection[] | null, automation_enabled: boolean }} LinkInjectionResponse
68
+ */
69
+
70
+ /**
71
+ * @typedef {{ x: number, y: number }} Position
72
+ */
Binary file
@@ -0,0 +1,13 @@
1
+ import adapter from '@sveltejs/adapter-auto'
2
+
3
+ /** @type {import('@sveltejs/kit').Config} */
4
+ const config = {
5
+ kit: {
6
+ // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
7
+ // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
8
+ // See https://svelte.dev/docs/kit/adapters for more information about adapters.
9
+ adapter: adapter(),
10
+ },
11
+ }
12
+
13
+ export default config
package/vite.config.js ADDED
@@ -0,0 +1,61 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { defineConfig } from 'vitest/config'
4
+ import { sveltekit } from '@sveltejs/kit/vite'
5
+ import { svelte } from '@sveltejs/vite-plugin-svelte'
6
+ import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
7
+
8
+ export default defineConfig(({ command }) => ({
9
+ plugins: [
10
+ command === 'build' ? svelte() : sveltekit(),
11
+ command === 'build' && cssInjectedByJsPlugin(),
12
+ {
13
+ // The build is output to /dist/index.html and /dist/assets/index-[random-hash].js
14
+ // We only care for the .js file. We remove the .html file, and move and remove the .js to the root .dist folder.
15
+
16
+ name: 'move-build-files',
17
+ writeBundle() {
18
+ const distPath = path.resolve(__dirname, './dist')
19
+ const assetsPath = path.resolve(__dirname, './dist/assets')
20
+
21
+ // 1. Remove index.html
22
+ const indexHtml = path.join(distPath, 'index.html')
23
+ if (fs.existsSync(indexHtml)) fs.unlinkSync(indexHtml)
24
+
25
+ // 2. Find the index-[hash].js file
26
+ const files = fs.readdirSync(assetsPath)
27
+ const jsFile = files.find(f => /^index-.*\.js$/.test(f))
28
+
29
+ if (!jsFile) return
30
+
31
+ const sourcePath = path.join(assetsPath, jsFile)
32
+ const targetPath = path.join(distPath, 'link-injections.js')
33
+
34
+ // 3. Move and rename the file
35
+ fs.renameSync(sourcePath, targetPath)
36
+ console.log(`Moved and renamed ${jsFile} to link-injections.js`)
37
+
38
+ // 4. Remove the now-empty /assets directory
39
+ const remaining = fs.readdirSync(assetsPath)
40
+ if (remaining.length === 0) fs.rmdirSync(assetsPath)
41
+ },
42
+ },
43
+ ],
44
+
45
+ server: {
46
+ port: 3000,
47
+ },
48
+
49
+ test: {
50
+ environment: 'happy-dom',
51
+ include: ['src/**/*.{test,spec}.{js,ts}'],
52
+ setupFiles: ['src/tests/setup.js'],
53
+ },
54
+
55
+ resolve: {
56
+ conditions: process.env.VITEST ? ['browser'] : [],
57
+ alias: {
58
+ '$lib': path.resolve(__dirname, './src/lib'),
59
+ },
60
+ },
61
+ }))