@playpilot/tpi 1.1.1 → 1.3.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 (40) hide show
  1. package/dist/link-injections.js +7 -7
  2. package/package.json +3 -1
  3. package/src/lib/api.js +2 -1
  4. package/src/lib/linkInjection.js +11 -4
  5. package/src/lib/playlink.js +28 -0
  6. package/src/lib/scss/_functions.scss +3 -0
  7. package/src/lib/scss/global.scss +75 -0
  8. package/src/lib/{variables.css → scss/variables.scss} +8 -2
  9. package/src/lib/url.js +7 -0
  10. package/src/routes/+layout.svelte +23 -21
  11. package/src/routes/+page.svelte +20 -18
  12. package/src/routes/components/AfterArticlePlaylinks.svelte +12 -11
  13. package/src/routes/components/ContextMenu.svelte +9 -9
  14. package/src/routes/components/Description.svelte +7 -7
  15. package/src/routes/components/Editorial/Alert.svelte +4 -4
  16. package/src/routes/components/Editorial/DragHandle.svelte +27 -26
  17. package/src/routes/components/Editorial/Editor.svelte +63 -47
  18. package/src/routes/components/Editorial/EditorItem.svelte +44 -44
  19. package/src/routes/components/Editorial/ManualInjection.svelte +23 -20
  20. package/src/routes/components/Editorial/PlaylinkTypeSelect.svelte +22 -22
  21. package/src/routes/components/Editorial/Search/TitleSearch.svelte +22 -22
  22. package/src/routes/components/Editorial/Switch.svelte +21 -20
  23. package/src/routes/components/Editorial/TextInput.svelte +12 -12
  24. package/src/routes/components/Genres.svelte +8 -8
  25. package/src/routes/components/Icons/IconChevron.svelte +1 -1
  26. package/src/routes/components/Modal.svelte +13 -15
  27. package/src/routes/components/Participants.svelte +11 -9
  28. package/src/routes/components/Playlinks.svelte +44 -39
  29. package/src/routes/components/Popover.svelte +17 -15
  30. package/src/routes/components/RoundButton.svelte +6 -6
  31. package/src/routes/components/SkeletonText.svelte +15 -15
  32. package/src/routes/components/Title.svelte +61 -57
  33. package/src/tests/lib/linkInjection.test.js +8 -5
  34. package/src/tests/lib/playlink.test.js +82 -0
  35. package/src/tests/lib/url.test.js +35 -0
  36. package/src/tests/routes/components/Editorial/Editor.test.js +8 -2
  37. package/src/tests/routes/components/Editorial/ManualInjection.test.js +51 -0
  38. package/src/typedefs.js +1 -1
  39. package/svelte.config.js +9 -0
  40. package/src/lib/global.css +0 -69
@@ -54,49 +54,53 @@
54
54
  <img src={title.medium_poster} alt="" />
55
55
  </div>
56
56
 
57
- <style>
57
+ <style lang="scss">
58
58
  h1 {
59
- margin: 0.5rem 0;
59
+ margin: margin(0.5) 0;
60
+ color: var(--playpilot-detail-title-text-color, #fff);
60
61
  font-family: var(--playpilot-detail-title-font-family, inherit);
61
62
  font-weight: var(--playpilot-detail-title-font-weight, lighter);
62
- font-size: var(--playpilot-detail-title-font-size, 1.5rem);
63
- color: var(--playpilot-detail-title-text-color, #fff);
64
- }
63
+ font-size: var(--playpilot-detail-title-font-size, margin(1.5));
64
+ line-height: normal;
65
+ font-style: var(--playpilot-detail-title-font-style, normal);
65
66
 
66
- .small h1 {
67
- font-size: var(--playpilot-detail-title-font-size-small, 1.125rem);
68
- }
67
+ .small & {
68
+ font-size: var(--playpilot-detail-title-font-size-small, margin(1.125));
69
+ }
69
70
 
70
- .compact h1 {
71
- margin-top: 0;
71
+ .compact & {
72
+ margin-top: 0;
73
+ }
72
74
  }
73
75
 
74
76
  .content {
75
77
  z-index: 1;
76
78
  position: relative;
77
- padding: 1rem;
79
+ padding: margin(1);
78
80
  color: var(--playpilot-detail-text-color, var(--playpilot-text-color));
79
81
  font-family: var(--playpilot-detail-font-family, var(--playpilot-font-family));
80
82
  font-weight: var(--playpilot-detail-font-weight, normal);
81
83
  font-size: var(--playpilot-detail-font-size, 14px);
82
- }
84
+ line-height: normal;
85
+ font-style: normal;
83
86
 
84
- .content.small {
85
- font-size: var(--playpilot-detail-font-size, 12px);
86
- line-height: 1.45;
87
- padding-bottom: 0.5rem;
87
+ &.small {
88
+ font-size: var(--playpilot-detail-font-size, 12px);
89
+ line-height: 1.45;
90
+ padding-bottom: margin(0.5);
91
+ }
88
92
  }
89
93
 
90
94
  .header {
91
- padding: 2rem 0 1rem;
92
- }
95
+ padding: margin(2) 0 margin(1);
93
96
 
94
- .small .header {
95
- padding-top: 1rem;
96
- }
97
+ .small & {
98
+ padding-top: margin(1);
99
+ }
97
100
 
98
- .compact .header {
99
- padding: 0 0 1rem;
101
+ .compact & {
102
+ padding: 0 0 margin(1);
103
+ }
100
104
  }
101
105
 
102
106
  .top {
@@ -107,10 +111,10 @@
107
111
 
108
112
  .poster {
109
113
  display: block;
110
- width: 4.5rem;
114
+ width: margin(4.5);
111
115
  margin: 0;
112
116
  aspect-ratio: 2 / 3;
113
- border-radius: var(--playpilot-detail-image-border-radius, 0.5rem);
117
+ border-radius: var(--playpilot-detail-image-border-radius, margin(0.5));
114
118
  background: var(--playpilot-detail-image-background, var(--playpilot-content));
115
119
  box-shadow: var(--playpilot-detail-image-shadow, var(--playpilot-shadow));
116
120
  }
@@ -119,27 +123,27 @@
119
123
  display: flex;
120
124
  flex-wrap: wrap;
121
125
  align-items: center;
122
- gap: 0.5rem 1rem;
123
- }
126
+ gap: margin(0.5) margin(1);
124
127
 
125
- .small .info {
126
- gap: 0.5rem 0.75rem;
128
+ .small & {
129
+ gap: margin(0.5) margin(0.75);
130
+ }
127
131
  }
128
132
 
129
133
  .imdb {
130
134
  display: flex;
131
135
  align-items: center;
132
- gap: 0.25rem;
133
- }
136
+ gap: margin(0.25);
134
137
 
135
- .imdb :global(svg) {
136
- margin-top: -0.125rem;
137
- }
138
+ :global(svg) {
139
+ margin-top: margin(-0.125);
138
140
 
139
- .small .imdb :global(svg) {
140
- margin-top: 0;
141
- width: 0.75rem;
142
- height: 0.75rem;
141
+ .small & {
142
+ margin-top: 0;
143
+ width: margin(0.75);
144
+ height: margin(0.75);
145
+ }
146
+ }
143
147
  }
144
148
 
145
149
  .background {
@@ -147,28 +151,28 @@
147
151
  top: 0;
148
152
  left: 0;
149
153
  width: 100%;
150
- height: 12rem;
154
+ height: margin(12);
151
155
  overflow: hidden;
152
156
  background: var(--playpilot-detail-background, var(--playpilot-lighter));
153
- }
154
157
 
155
- .background::before {
156
- content: "";
157
- display: block;
158
- position: absolute;
159
- top: 0;
160
- right: 0;
161
- bottom: 0;
162
- left: 0;
163
- background: linear-gradient(to top, var(--playpilot-detail-background, var(--playpilot-light)), transparent 40%);
164
- }
165
-
166
- .background img {
167
- width: 100%;
168
- height: 100%;
169
- object-fit: cover;
170
- object-position: center;
171
- margin: 0;
158
+ &::before {
159
+ content: "";
160
+ display: block;
161
+ position: absolute;
162
+ top: 0;
163
+ right: 0;
164
+ bottom: 0;
165
+ left: 0;
166
+ background: linear-gradient(to top, var(--playpilot-detail-background, var(--playpilot-light)), transparent 40%);
167
+ }
168
+
169
+ img {
170
+ width: 100%;
171
+ height: 100%;
172
+ object-fit: cover;
173
+ object-position: center;
174
+ margin: 0;
175
+ }
172
176
  }
173
177
 
174
178
  .faded {
@@ -617,9 +617,10 @@ describe('linkInjection.js', () => {
617
617
  it('Should return elements in the same order they were given', () => {
618
618
  document.body.innerHTML = `<section>
619
619
  <div>
620
- <p>Button inside header</p>
620
+ <p><span><strong>Some first text</strong></span></p>
621
621
  <div>
622
- <strong>Header inside link</strong>
622
+ <em>Some empasized</em>
623
+ <main><p><em><i>Some deeply nested text</i></em></p></main>
623
624
  <div>Some text</div>
624
625
  </div>
625
626
  </div>
@@ -628,9 +629,11 @@ describe('linkInjection.js', () => {
628
629
  const parent = /** @type {HTMLElement} */ (document.querySelector('section'))
629
630
  const elements = getLinkInjectionElements(parent)
630
631
 
631
- expect(elements[0].nodeName === 'P')
632
- expect(elements[1].nodeName === 'STRONG')
633
- expect(elements[2].nodeName === 'DIV')
632
+ expect(elements).toHaveLength(4)
633
+ expect(elements[0].nodeName).toBe('STRONG')
634
+ expect(elements[1].nodeName).toBe('EM')
635
+ expect(elements[2].nodeName).toBe('I')
636
+ expect(elements[3].nodeName).toBe('DIV')
634
637
  })
635
638
  })
636
639
 
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mergePlaylinks } from '$lib/playlink'
3
+
4
+ describe('$lib/playlink', () => {
5
+ describe('mergePlaylinks', () => {
6
+ it('Should return an array with RENT and BUY categories of the same provider as one entry', () => {
7
+ /** @type {PlaylinkData[]} */
8
+ const playlinks = [{
9
+ sid: '1',
10
+ name: 'Netflix',
11
+ url: '',
12
+ logo_url: '',
13
+ extra_info: {
14
+ category: 'RENT',
15
+ },
16
+ }, {
17
+ sid: '2',
18
+ name: 'Netflix',
19
+ url: '',
20
+ logo_url: '',
21
+ extra_info: {
22
+ category: 'BUY',
23
+ },
24
+ }]
25
+
26
+ expect(mergePlaylinks(playlinks)).toHaveLength(1)
27
+ expect(mergePlaylinks(playlinks)[0].extra_info.category).toBe('TVOD')
28
+ })
29
+
30
+ it('Should not merge playlinks if they do not share the same name', () => {
31
+ /** @type {PlaylinkData[]} */
32
+ const playlinks = [{
33
+ sid: '1',
34
+ name: 'Netflix',
35
+ url: '',
36
+ logo_url: '',
37
+ extra_info: {
38
+ category: 'RENT',
39
+ },
40
+ }, {
41
+ sid: '2',
42
+ name: 'Apple',
43
+ url: '',
44
+ logo_url: '',
45
+ extra_info: {
46
+ category: 'BUY',
47
+ },
48
+ }]
49
+
50
+ expect(mergePlaylinks(playlinks)).toHaveLength(2)
51
+ })
52
+
53
+ it('Should not merge playlinks if one of the categories is not RENT or BUY', () => {
54
+ /** @type {PlaylinkData[]} */
55
+ const playlinks = [{
56
+ sid: '1',
57
+ name: 'Netflix',
58
+ url: '',
59
+ logo_url: '',
60
+ extra_info: {
61
+ category: 'RENT',
62
+ },
63
+ }, {
64
+ sid: '2',
65
+ name: 'Netflix',
66
+ url: '',
67
+ logo_url: '',
68
+ extra_info: {
69
+ category: 'SVOD',
70
+ },
71
+ }]
72
+
73
+ expect(mergePlaylinks(playlinks)).toHaveLength(2)
74
+ })
75
+
76
+ it('Should return an empty array if given', () => {
77
+ /** @type {PlaylinkData[]} */
78
+ const playlinks = []
79
+ expect(mergePlaylinks(playlinks)).toEqual([])
80
+ })
81
+ })
82
+ })
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
+ import { getFullUrlPath } from '$lib/url'
3
+
4
+ describe('getFullUrlPath', () => {
5
+ const originalLocation = window.location
6
+
7
+ beforeEach(() => {
8
+ // @ts-expect-error: we're mocking window.location
9
+ delete window.location
10
+
11
+ // @ts-expect-error
12
+ window.location = {
13
+ protocol: 'https:',
14
+ host: 'example.com',
15
+ pathname: '/some/path',
16
+ }
17
+ })
18
+
19
+ afterEach(() => {
20
+ // @ts-expect-error
21
+ window.location = originalLocation
22
+ })
23
+
24
+ it('Should return the full URL path including protocol, host and pathname', () => {
25
+ const result = getFullUrlPath()
26
+ expect(result).toBe('https://example.com/some/path')
27
+ })
28
+
29
+ it('Should exclude search params', () => {
30
+ window.location.search = '?ignore=me'
31
+
32
+ const result = getFullUrlPath()
33
+ expect(result).toBe('https://example.com/some/path')
34
+ })
35
+ })
@@ -50,15 +50,21 @@ describe('Editor.svelte', () => {
50
50
  expect(queryByText('Loading...')).not.toBeTruthy()
51
51
  })
52
52
 
53
- it('Should not show save button in enlarged view if no injections are present', async () => {
53
+ it('Should not show save button if no injections are present', async () => {
54
54
  const { queryByText } = render(Editor, { linkInjections: [], loading: false })
55
55
 
56
56
  expect(queryByText('Save links')).not.toBeTruthy()
57
57
  })
58
58
 
59
- it('Should show save button in enlarged view if injections are present', async () => {
59
+ it('Should show save button if injections are present', async () => {
60
60
  const { queryByText } = render(Editor, { linkInjections, loading: false })
61
61
 
62
62
  expect(queryByText('Save links')).toBeTruthy()
63
63
  })
64
+
65
+ it('Should show empty state if no links are found', async () => {
66
+ const { queryByText } = render(Editor, { linkInjections: [], loading: false })
67
+
68
+ expect(queryByText('No links available', { exact: false })).toBeTruthy()
69
+ })
64
70
  })
@@ -26,6 +26,7 @@ describe('TitleSearch.svelte', () => {
26
26
  startOffset: 0,
27
27
  endOffset: 0,
28
28
  }),
29
+ anchorNode: container,
29
30
  }))
30
31
  })
31
32
 
@@ -93,6 +94,7 @@ describe('TitleSearch.svelte', () => {
93
94
  startOffset: 0,
94
95
  endOffset: 0,
95
96
  }),
97
+ anchorNode: container,
96
98
  }))
97
99
 
98
100
  const onsave = vi.fn()
@@ -111,4 +113,53 @@ describe('TitleSearch.svelte', () => {
111
113
  title_details: title,
112
114
  })
113
115
  })
116
+
117
+ it('Should not select content if it is outside of given parent', async () => {
118
+ vi.mocked(searchTitles).mockResolvedValueOnce([title])
119
+
120
+ document.body.innerHTML = '<div>Some text <main><p>in a sentence</p></main></div>'
121
+
122
+ let container = document.querySelector('div')
123
+
124
+ const onsave = vi.fn()
125
+ const { unmount } = render(TitleSearch, { htmlString: document.body.innerHTML, onsave })
126
+
127
+ // @ts-ignore
128
+ window.getSelection = vi.fn(() => ({
129
+ toString: () => 'Some text',
130
+ getRangeAt: () => ({
131
+ commonAncestorContainer: container,
132
+ startContainer: container,
133
+ startOffset: 0,
134
+ endOffset: 0,
135
+ }),
136
+ anchorNode: container,
137
+ }))
138
+
139
+ await fireEvent.click(window)
140
+
141
+ expect(searchTitles).not.toHaveBeenCalled()
142
+
143
+ unmount()
144
+
145
+ container = document.querySelector('p')
146
+
147
+ // @ts-ignore
148
+ window.getSelection = vi.fn(() => ({
149
+ toString: () => 'in a sentence',
150
+ getRangeAt: () => ({
151
+ commonAncestorContainer: container,
152
+ startContainer: container,
153
+ startOffset: 0,
154
+ endOffset: 0,
155
+ }),
156
+ anchorNode: container,
157
+ }))
158
+
159
+ ;(render(TitleSearch, { htmlString: document.body.innerHTML, onsave }))
160
+
161
+ await fireEvent.click(window)
162
+
163
+ await waitFor(() => expect(searchTitles).toHaveBeenCalled())
164
+ })
114
165
  })
package/src/typedefs.js CHANGED
@@ -26,7 +26,7 @@
26
26
  */
27
27
 
28
28
  /**
29
- * @typedef {'SVOD' | 'BUY' | 'RENT'} PlaylinkCategory
29
+ * @typedef {'SVOD' | 'BUY' | 'RENT' | 'TVOD'} PlaylinkCategory
30
30
  */
31
31
 
32
32
  /**
package/svelte.config.js CHANGED
@@ -1,7 +1,16 @@
1
1
  import adapter from '@sveltejs/adapter-auto'
2
+ import { sveltePreprocess } from 'svelte-preprocess'
2
3
 
3
4
  /** @type {import('@sveltejs/kit').Config} */
4
5
  const config = {
6
+ preprocess: sveltePreprocess({
7
+ scss: {
8
+ includePaths: ['src'],
9
+ prependData: `
10
+ @use "src/lib/scss/_functions.scss" as *;
11
+ `,
12
+ },
13
+ }),
5
14
  kit: {
6
15
  // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
7
16
  // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
@@ -1,69 +0,0 @@
1
- [data-playpilot-injection-key] {
2
- position: relative;
3
- }
4
-
5
- [data-playpilot-injection-key].injection-highlight {
6
- outline: 0.25rem solid var(--playpilot-primary) !important;
7
- outline-offset: 0.5rem !important;
8
- border-radius: 0.05rem;
9
- scroll-margin: 5rem;
10
- }
11
-
12
- .playpilot-styled-scrollbar {
13
- scrollbar-color: var(--playpilot-content-light) var(--playpilot-lighter);
14
- scrollbar-width: thin;
15
- }
16
-
17
- .playpilot-styled-scrollbed::-webkit-scrollbar {
18
- width: 0.75rem;
19
- }
20
-
21
- .playpilot-styled-scrollbed::-webkit-scrollbar-track {
22
- background: var(--playpilot-light);
23
- }
24
-
25
- .playpilot-styled-scrollbed::-webkit-scrollbar-thumb {
26
- border: 2px solid var(--playpilot-light);
27
- border-radius: 1rem;
28
- background: var(--playpilot-lighter)
29
- }
30
-
31
- .playpilot-styled-scrollbed::-webkit-scrollbar-thumb:hover {
32
- background: var(--playpilot-content-light);
33
- }
34
-
35
- .playpilot-styled-scrollbed::-webkit-scrollbar-thumb:active {
36
- background: var(--playpilot-text-color-alt);
37
- }
38
-
39
- /*
40
- Styling for individual CSS variables, with a selector for each variable.
41
- This is very verbose, but necessary in order to only use the CSS variable when they are actually set.
42
- */
43
- [data-playpilot-injection-key][data-used-css-variables*="--playpilot-injection-font-weight"] {
44
- font-weight: var(--playpilot-injection-font-weight);
45
- }
46
-
47
- [data-playpilot-injection-key][data-used-css-variables*="--playpilot-injection-text-color"] {
48
- color: var(--playpilot-injection-text-color);
49
- }
50
-
51
- [data-playpilot-injection-key][data-used-css-variables*="--playpilot-injection-text-color-hover"]:hover {
52
- color: var(--playpilot-injection-text-color-hover);
53
- }
54
-
55
- [data-playpilot-injection-key][data-used-css-variables*="--playpilot-injection-text-decoration"] {
56
- text-decoration: var(--playpilot-injection-text-decoration);
57
- }
58
-
59
- [data-playpilot-injection-key][data-used-css-variables*="--playpilot-injection-text-decoration-hover"]:hover {
60
- text-decoration: var(--playpilot-injection-text-decoration-hover);
61
- }
62
-
63
- [data-playpilot-injection-key][data-used-css-variables*="--playpilot-injection-background-color"] {
64
- background-color: var(--playpilot-injection-background-color);
65
- }
66
-
67
- [data-playpilot-injection-key][data-used-css-variables*="--playpilot-injection-background-color-hover"]:hover {
68
- background-color: var(--playpilot-injection-background-color-hover);
69
- }