@playpilot/tpi 3.1.0-beta.5 → 3.2.0-beta.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.
Files changed (41) hide show
  1. package/dist/link-injections.js +8 -7
  2. package/package.json +1 -1
  3. package/src/lib/api.ts +1 -3
  4. package/src/lib/auth.ts +19 -3
  5. package/src/lib/constants.ts +2 -0
  6. package/src/lib/enums/TrackingEvent.ts +2 -0
  7. package/src/lib/linkInjection.ts +83 -43
  8. package/src/lib/scss/global.scss +6 -6
  9. package/src/lib/scss/variables.scss +2 -0
  10. package/src/lib/stores/organization.ts +4 -0
  11. package/src/lib/tracking.ts +14 -1
  12. package/src/lib/types/global.d.ts +2 -0
  13. package/src/lib/types/injection.d.ts +3 -0
  14. package/src/main.js +5 -1
  15. package/src/routes/+layout.svelte +13 -1
  16. package/src/routes/+page.svelte +39 -8
  17. package/src/routes/components/AfterArticlePlaylinks.svelte +4 -0
  18. package/src/routes/components/Editorial/AIIndicator.svelte +12 -4
  19. package/src/routes/components/Editorial/Alert.svelte +12 -2
  20. package/src/routes/components/Editorial/Editor.svelte +51 -24
  21. package/src/routes/components/Editorial/EditorItem.svelte +32 -7
  22. package/src/routes/components/Editorial/EditorTrigger.svelte +72 -0
  23. package/src/routes/components/Editorial/ManualInjection.svelte +13 -10
  24. package/src/routes/components/Editorial/PlaylinkTypeSelect.svelte +14 -0
  25. package/src/routes/components/Editorial/ResizeHandle.svelte +5 -5
  26. package/src/routes/components/Editorial/Search/TitleSearch.svelte +0 -2
  27. package/src/routes/components/Editorial/Search/TitleSearchItem.svelte +6 -4
  28. package/src/routes/components/Icons/IconPlayPilot.svelte +8 -0
  29. package/src/routes/components/Icons/IconWarning.svelte +5 -0
  30. package/src/routes/components/TitlePopover.svelte +1 -1
  31. package/src/tests/lib/auth.test.js +41 -1
  32. package/src/tests/lib/linkInjection.test.js +199 -41
  33. package/src/tests/lib/tracking.test.js +61 -1
  34. package/src/tests/routes/+page.test.js +100 -6
  35. package/src/tests/routes/components/Editorial/AiIndicator.test.js +12 -5
  36. package/src/tests/routes/components/Editorial/Alert.test.js +10 -3
  37. package/src/tests/routes/components/Editorial/Editor.test.js +19 -4
  38. package/src/tests/routes/components/Editorial/EditorItem.test.js +32 -7
  39. package/src/tests/routes/components/Editorial/EditorTrigger.test.js +24 -0
  40. package/src/tests/routes/components/Editorial/ManualInjection.test.js +65 -21
  41. package/src/tests/routes/components/Editorial/PlaylinkTypeSelect.test.js +13 -1
@@ -2,13 +2,14 @@
2
2
  import type { Snippet } from 'svelte'
3
3
 
4
4
  interface Props {
5
+ type?: 'error' | 'warning'
5
6
  children: Snippet
6
7
  }
7
8
 
8
- const { children }: Props = $props()
9
+ const { type = 'error', children }: Props = $props()
9
10
  </script>
10
11
 
11
- <div class="alert">
12
+ <div class="alert {type}">
12
13
  {@render children()}
13
14
  </div>
14
15
 
@@ -19,5 +20,14 @@
19
20
  border: 1px solid var(--playpilot-error);
20
21
  background: var(--playpilot-error-dark);
21
22
  font-size: margin(0.75);
23
+
24
+ &.warning {
25
+ border-color: var(--playpilot-warning);
26
+ background: var(--playpilot-warning-dark);
27
+ }
28
+
29
+ :global(a) {
30
+ color: currentColor;
31
+ }
22
32
  }
23
33
  </style>
@@ -19,10 +19,19 @@
19
19
  linkInjections: LinkInjection[],
20
20
  htmlString?: string,
21
21
  loading?: boolean,
22
- aiRunning?: boolean
22
+ aiRunning?: boolean,
23
+ injectionsEnabled?: boolean,
24
+ automationEnabled?: boolean,
23
25
  }
24
26
 
25
- let { linkInjections = $bindable(), htmlString = '', loading = false, aiRunning = false }: Props = $props()
27
+ let {
28
+ linkInjections = $bindable(),
29
+ htmlString = '',
30
+ loading = false,
31
+ aiRunning = false,
32
+ injectionsEnabled = false,
33
+ automationEnabled = false,
34
+ }: Props = $props()
26
35
 
27
36
  const editorPositionKey = 'editor-position'
28
37
  const editorHeightKey = 'editor-height'
@@ -38,7 +47,7 @@
38
47
  let aIIndicator = $state()
39
48
 
40
49
  const linkInjectionsString = $derived(JSON.stringify(linkInjections))
41
- const hasChanged = $derived(initialStateString !== linkInjectionsString)
50
+ const hasChanged = $derived(initialStateString && initialStateString !== linkInjectionsString)
42
51
  // Filter out injections without title_details, injections that are removed, duplicate, or are AI injections that failed to inject
43
52
  const filteredInjections = $derived(linkInjections.filter((i) => i.title_details && !i.removed && !i.duplicate && (i.manual || (!i.manual && !i.failed))))
44
53
  const sortedInjections = $derived(sortInjections(filteredInjections))
@@ -47,7 +56,7 @@
47
56
  if (loading) return
48
57
 
49
58
  untrack(() => {
50
- initialStateString = linkInjectionsString
59
+ requestAnimationFrame(() => initialStateString = linkInjectionsString)
51
60
  trackInjectionsCount()
52
61
  })
53
62
  })
@@ -112,10 +121,10 @@
112
121
 
113
122
  function trackInjectionsCount() {
114
123
  const payload = {
115
- total: linkInjections.length.toString(),
116
- failed_automatic: linkInjections.filter(i => i.failed && !i.manual).length.toString(),
117
- failed_manual: linkInjections.filter(i => i.failed && i.manual).length.toString(),
118
- final_injected: filteredInjections.length.toString(),
124
+ total: linkInjections.length,
125
+ failed_automatic: linkInjections.filter(i => i.failed && !i.manual).length,
126
+ failed_manual: linkInjections.filter(i => i.failed && i.manual).length,
127
+ final_injected: filteredInjections.length,
119
128
  }
120
129
 
121
130
  track(TrackingEvent.TotalInjectionsCount, null, payload)
@@ -137,19 +146,19 @@
137
146
  </script>
138
147
 
139
148
  <section class="editor playpilot-styled-scrollbar" class:panel-open={manualInjectionActive} class:loading bind:this={editorElement} {onscroll}>
140
- <header class="header">
141
- {#if editorElement}
142
- {#if !loading}
143
- <div class="handle">
144
- <ResizeHandle element={editorElement} {height} onchange={(height) => saveLocalStorage(editorHeightKey, JSON.stringify(height))} />
145
- </div>
146
- {/if}
149
+ {#if editorElement && !loading}
150
+ <div class="handles">
151
+ <div class="handle">
152
+ <ResizeHandle element={editorElement} {height} onchange={(height) => saveLocalStorage(editorHeightKey, JSON.stringify(height))} />
153
+ </div>
147
154
 
148
155
  <div class="handle">
149
156
  <DragHandle element={editorElement} {position} limit={{ x: 16, y: 16 }} onchange={(position) => saveLocalStorage(editorPositionKey, JSON.stringify(position))} />
150
157
  </div>
151
- {/if}
158
+ </div>
159
+ {/if}
152
160
 
161
+ <header class="header">
153
162
  <h1>Playlinks</h1>
154
163
 
155
164
  {#if loading}
@@ -165,11 +174,20 @@
165
174
  {/if}
166
175
  </header>
167
176
 
168
- {#if !loading && aiRunning}
169
- <AIIndicator {htmlString} bind:this={aIIndicator} onadd={(newInjections) => newInjections.forEach(i => linkInjections.push(i))} />
170
- {/if}
171
-
172
177
  {#if !loading}
178
+ {#if !injectionsEnabled}
179
+ <div class="alert">
180
+ <Alert type="warning">
181
+ <strong>Playlinks are currently not published.</strong> Visitors to this page will not see any of the injected links.
182
+ Publish playlinks from the <a href="https://partner.playpilot.net">Partner Portal</a>
183
+ </Alert>
184
+ </div>
185
+ {/if}
186
+
187
+ {#if aiRunning || !automationEnabled}
188
+ <AIIndicator {htmlString} bind:this={aIIndicator} {automationEnabled} onadd={(newInjections) => newInjections.forEach(i => linkInjections.push(i))} />
189
+ {/if}
190
+
173
191
  {#if hasError}
174
192
  <div class="error" transition:slide|global={{ duration: 150 }}>
175
193
  <Alert>Something went wrong, check your links below.</Alert>
@@ -225,7 +243,7 @@
225
243
  right: margin(1);
226
244
  width: 100%;
227
245
  max-width: margin(22);
228
- height: min(50vh, margin(50));
246
+ height: min(70vh, margin(40));
229
247
  min-height: 10rem;
230
248
  margin: 0;
231
249
  padding: margin(1);
@@ -254,6 +272,13 @@
254
272
  font-size: margin(0.85);
255
273
  }
256
274
 
275
+ .handles {
276
+ z-index: 20;
277
+ position: sticky;
278
+ top: margin(-1);
279
+ margin: margin(-1) margin(-1) 0;
280
+ }
281
+
257
282
  .handle {
258
283
  opacity: 0;
259
284
  transition: opacity 150ms;
@@ -264,10 +289,8 @@
264
289
  }
265
290
 
266
291
  .header {
292
+ @extend .handles;
267
293
  z-index: 5;
268
- position: sticky;
269
- top: margin(-1);
270
- margin: margin(-1) margin(-1) 0;
271
294
  padding: margin(1) margin(1) margin(1) margin(1.5);
272
295
  border: 0;
273
296
  background: var(--playpilot-dark);
@@ -340,6 +363,10 @@
340
363
  margin-top: margin(0.5);
341
364
  }
342
365
 
366
+ .alert {
367
+ margin: 0 margin(0.5) margin(0.5);
368
+ }
369
+
343
370
  .panel {
344
371
  z-index: 10;
345
372
  position: absolute;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
- import { slide } from 'svelte/transition'
2
+ import { fade, slide } from 'svelte/transition'
3
3
  import IconChevron from '../Icons/IconChevron.svelte'
4
+ import IconWarning from '../Icons/IconWarning.svelte'
4
5
  import Switch from './Switch.svelte'
5
6
  import TextInput from './TextInput.svelte'
6
7
  import PlaylinkTypeSelect from './PlaylinkTypeSelect.svelte'
@@ -11,7 +12,9 @@
11
12
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
12
13
  import type { LinkInjection } from '$lib/types/injection'
13
14
  import type { TitleData } from '$lib/types/title'
14
- import { truncateAroundPhrase } from '$lib/text'
15
+ import { cleanPhrase, truncateAroundPhrase } from '$lib/text'
16
+ import { getLinkInjectionElements, getLinkInjectionsParentElement, isValidPlaylinkType } from '$lib/linkInjection'
17
+ import { imagePlaceholderDataUrl } from '$lib/constants'
15
18
 
16
19
  interface Props {
17
20
  linkInjection: LinkInjection,
@@ -22,7 +25,7 @@
22
25
 
23
26
  const { linkInjection = $bindable(), onremove = () => null, onhighlight = () => null }: Props = $props()
24
27
 
25
- const { key, sentence, title_details, failed, inactive } = $derived(linkInjection || {})
28
+ const { key, sentence, title_details, failed, failed_message, inactive } = $derived(linkInjection || {})
26
29
 
27
30
  // @ts-ignore Definitely not null
28
31
  const title: TitleData = $derived(title_details)
@@ -42,8 +45,9 @@
42
45
  */
43
46
  function toggleOnPageResultHighlight(state: boolean = true): void {
44
47
  const matchingElements = getMatchingElements()
48
+
45
49
  matchingElements.forEach(element => {
46
- element.classList.toggle('injection-highlight', state)
50
+ element.classList.toggle('playpilot-injection-highlight', state)
47
51
  })
48
52
  }
49
53
 
@@ -76,7 +80,14 @@
76
80
  }
77
81
 
78
82
  function getMatchingElements(): Element[] {
79
- return Array.from(document.querySelectorAll(`[data-playpilot-injection-key="${key}"]`))
83
+ const injectedElements = Array.from(document.querySelectorAll(`[data-playpilot-injection-key="${key}"]`))
84
+ if (injectedElements.length) return injectedElements
85
+
86
+ // No matching injection was found, so we try and get the element the sentence might be in.
87
+ // Could not be entirely accurate. Could also result in nothing depending on why the injection failed.
88
+ return getLinkInjectionElements((getLinkInjectionsParentElement())).filter((element) => {
89
+ return cleanPhrase(element.innerText).includes(cleanPhrase(linkInjection.sentence))
90
+ }) || []
80
91
  }
81
92
  </script>
82
93
 
@@ -94,7 +105,7 @@
94
105
  bind:this={element}
95
106
  out:slide|global={{ duration: 200 }}>
96
107
  <div class="header">
97
- <img class="poster" src={title.standing_poster} alt="" width="32" height="48" />
108
+ <img class="poster" src={title.standing_poster} alt="" width="32" height="48" onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl} />
98
109
 
99
110
  <div class="info">
100
111
  <div class="title">{title.title}</div>
@@ -120,13 +131,19 @@
120
131
 
121
132
  <div class="content">
122
133
  {#if failed}
123
- <Alert>A match was found, but the link could not be injected.</Alert>
134
+ <Alert>{failed_message}</Alert>
124
135
  {:else}
125
136
  <div class="actions">
126
137
  <button class="expand" onclick={() => expanded = !expanded} aria-label="Expand" aria-expanded={expanded}>
127
138
  <IconChevron {expanded} />
128
139
  </button>
129
140
 
141
+ {#if !isValidPlaylinkType(linkInjection)}
142
+ <div class="warning" transition:fade={{ duration: 100 }} aria-label="Invalid playlink settings">
143
+ <IconWarning />
144
+ </div>
145
+ {/if}
146
+
130
147
  <Switch label={inactive ? 'Inactive' : 'Visible'} active={!linkInjection.inactive} onclick={(active) => { linkInjection.inactive = !active; linkInjection.manual = true }}>
131
148
  {inactive ? 'Inactive' : 'Visible'}
132
149
  </Switch>
@@ -293,5 +310,13 @@
293
310
  .offset {
294
311
  margin-top: margin(0.75);
295
312
  }
313
+
314
+ .warning {
315
+ margin: 0 auto 0 margin(0.5);
316
+
317
+ :global(svg) {
318
+ display: block;
319
+ }
320
+ }
296
321
  </style>
297
322
 
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import IconClose from '../Icons/IconClose.svelte'
3
+ import IconPlayPilot from '../Icons/IconPlayPilot.svelte'
4
+
5
+ interface Props {
6
+ onclick: () => void,
7
+ onclose: () => void,
8
+ }
9
+
10
+ const { onclick, onclose }: Props = $props()
11
+ </script>
12
+
13
+ <div class="trigger">
14
+ <button type="button" class="button" title="Show PlayPilot TPI editor" {onclick}>
15
+ <IconPlayPilot />
16
+ </button>
17
+
18
+ <button type="button" class="button close" title="Close PlayPilot TPI" onclick={onclose}>
19
+ <IconClose />
20
+ </button>
21
+ </div>
22
+
23
+ <style lang="scss">
24
+ .trigger {
25
+ position: fixed;
26
+ bottom: margin(1);
27
+ right: margin(1);
28
+ }
29
+
30
+ .button {
31
+ cursor: pointer;
32
+ appearance: none;
33
+ width: margin(3);
34
+ height: margin(3);
35
+ border: 0;
36
+ padding: margin(0.75);
37
+ border-radius: 50%;
38
+ background: var(--playpilot-light);
39
+ box-shadow: var(--playpilot-shadow-large);
40
+ transition: transform 50ms;
41
+ line-height: margin(1.5);
42
+ text-align: center;
43
+ font-size: margin(0.85);
44
+
45
+ &:hover {
46
+ filter: brightness(1.1);
47
+ transform: scale(1.05);
48
+ }
49
+
50
+ :global(svg) {
51
+ width: 100%;
52
+ }
53
+ }
54
+
55
+ .close {
56
+ display: flex;
57
+ text-align: center;
58
+ justify-content: center;
59
+ position: absolute;
60
+ top: margin(-0.5);
61
+ right: margin(-0.5);
62
+ width: margin(1.5);
63
+ height: margin(1.5);
64
+ padding: margin(0.25);
65
+ background: var(--playpilot-content);
66
+ color: white;
67
+
68
+ :global(svg) {
69
+ width: margin(0.75);
70
+ }
71
+ }
72
+ </style>
@@ -36,8 +36,7 @@
36
36
  function updateSelection(): void {
37
37
  const selection = window.getSelection()
38
38
  if (!selection?.anchorNode) return
39
-
40
- if (!getLinkInjectionsParentElement().contains(selection.anchorNode.parentElement)) return
39
+ if (selection.anchorNode.parentElement && !getLinkInjectionsParentElement().contains(selection.anchorNode.parentElement)) return
41
40
 
42
41
  const selectionText = selection.toString().trim()
43
42
  if (!selectionText) return // No content was selected
@@ -45,9 +44,12 @@
45
44
  error = ''
46
45
  currentSelection = selectionText
47
46
 
48
- // Selection spanned more than 1 element or contained another element. This will likely result in a failed injection.
49
- if (selection.anchorNode !== selection.focusNode) {
50
- error = 'Selection is broken up by multiple elements. Please select the text more directly.'
47
+ // Selection spanned more than 1 element. This will likely result in a failed injection.
48
+ // Check if the selection contains more than 1 element with non-empty content.
49
+ const fragment = selection.getRangeAt(0).cloneContents()
50
+ const selectionNodesWithContent = Array.from(fragment.childNodes).filter(n => n.textContent?.trim())
51
+ if (selectionNodesWithContent.length > 1) {
52
+ error = 'Selection contains multiple items. Selection may not contain a mix of styled and non styled text. Please select the text more directly.'
51
53
  return
52
54
  }
53
55
 
@@ -56,7 +58,6 @@
56
58
 
57
59
  const nodeContent = selection.getRangeAt(0).commonAncestorContainer.textContent
58
60
  const documentTextContent = decodeHtmlEntities(htmlString)
59
-
60
61
  if (!nodeContent || !documentTextContent.includes(nodeContent)) { // Selected content is not within the ALI selector
61
62
  error = 'Selection was not inside of given content'
62
63
  }
@@ -172,10 +173,12 @@
172
173
  </div>
173
174
  {/if}
174
175
 
175
- <label for="text">Paired title</label>
176
- {#key selectionSentence + currentSelection}
177
- <TitleSearch {onselect} bind:query />
178
- {/key}
176
+ {#if !error}
177
+ <label for="text">Paired title</label>
178
+ {#key selectionSentence + currentSelection}
179
+ <TitleSearch {onselect} bind:query />
180
+ {/key}
181
+ {/if}
179
182
 
180
183
  <button class="save" onclick={save} disabled={!currentSelection || !selectedTitle}>
181
184
  Add playlink
@@ -3,6 +3,8 @@
3
3
  import IconAlign from '../Icons/IconAlign.svelte'
4
4
  import Switch from './Switch.svelte'
5
5
  import type { LinkInjection } from '$lib/types/injection'
6
+ import { isValidPlaylinkType } from '$lib/linkInjection'
7
+ import Alert from './Alert.svelte'
6
8
 
7
9
  interface Props {
8
10
  linkInjection: LinkInjection
@@ -33,6 +35,14 @@
33
35
  </Switch>
34
36
  </div>
35
37
 
38
+ {#if !isValidPlaylinkType(linkInjection)}
39
+ <div class="alert" transition:slide={{ duration: 200 }}>
40
+ <Alert type="warning">
41
+ At least one layout option must be selected for the playlink to be visible.
42
+ </Alert>
43
+ </div>
44
+ {/if}
45
+
36
46
  {#if linkInjection.after_article}
37
47
  <div transition:slide={{ duration: 100 }}>
38
48
  <div class="label">Bottom playlinks style</div>
@@ -133,4 +143,8 @@
133
143
  font-size: margin(0.675);
134
144
  color: var(--playpilot-text-color-alt);
135
145
  }
146
+
147
+ .alert {
148
+ margin-top: margin(0.5);
149
+ }
136
150
  </style>
@@ -63,7 +63,7 @@
63
63
  ontouchmove={move}
64
64
  onmouseup={end}
65
65
  ontouchend={end}
66
- onresize={() => setHeight(height)} />
66
+ onresize={() => { if (height) setHeight(height) }} />
67
67
 
68
68
  <button class="resize-handle" onmousedown={start} ontouchstart={start} aria-label="Move editor"></button>
69
69
 
@@ -73,8 +73,8 @@
73
73
  appearance: none;
74
74
  position: absolute;
75
75
  top: 0;
76
- left: 20%;
77
- width: 60%;
76
+ left: 0;
77
+ width: 100%;
78
78
  height: margin(0.5);
79
79
  background: transparent;
80
80
  border: 0;
@@ -99,8 +99,8 @@
99
99
  content: "";
100
100
  position: absolute;
101
101
  top: 0;
102
- right: 0;
103
- left: 0;
102
+ right: 20%;
103
+ left: 20%;
104
104
  bottom: 80%;
105
105
  border-radius: 0 0 margin(1) margin(1);
106
106
  background: var(--playpilot-text-color);
@@ -20,8 +20,6 @@
20
20
  if (query) search(query)
21
21
  })
22
22
 
23
- $inspect(selectedResult)
24
-
25
23
  async function search(query: string): Promise<void> {
26
24
  loading = true
27
25
 
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { playPilotBaseUrl } from '$lib/constants'
2
+ import { imagePlaceholderDataUrl, playPilotBaseUrl } from '$lib/constants'
3
3
  import type { TitleData } from '$lib/types/title'
4
4
  import IconIMDb from '../../Icons/IconIMDb.svelte'
5
5
  import IconNewTab from '../../Icons/IconNewTab.svelte'
@@ -14,7 +14,7 @@
14
14
  </script>
15
15
 
16
16
  <button class="item" class:hoverable {onclick}>
17
- <img class="poster" src={title.standing_poster} alt="" width="28" height="42" />
17
+ <img class="poster" src={title.standing_poster} alt="" width="28" height="42" onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl} />
18
18
 
19
19
  <div class="content">
20
20
  <div class="name">{title.title}</div>
@@ -73,10 +73,12 @@
73
73
  }
74
74
 
75
75
  .poster {
76
+ flex: 0 0 auto;
77
+ height: auto;
76
78
  width: margin(1.75);
79
+ aspect-ratio: 28/42;
77
80
  border-radius: margin(0.25);
78
- height: auto;
79
- background: var(--playpilot-dark);
81
+ background: var(--playpilot-content);
80
82
  }
81
83
 
82
84
  .name {
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 57.48 62.96">
2
+ <defs>
3
+ <style>.playpilot-logo{fill:url(#playpilot-linear-gradient);}</style>
4
+ <linearGradient id="playpilot-linear-gradient" x1="-90.03" y1="-40.23" x2="-34.8" y2="-101.48" gradientTransform="translate(74.02 108.65)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#de00b9"/><stop offset="0.99" stop-color="#ff557b"/></linearGradient>
5
+ </defs>
6
+
7
+ <path class="playpilot-logo" d="M57.48,31.48C57.48,22.17,20.85,2.19,9.2.18,7.42-.12,3.78-.37,1.36,2.25-4.32,8.39,9.62,21.5,9.62,31.48S-4.32,54.57,1.36,60.72c2.42,2.61,6.06,2.37,7.84,2.06C20.85,60.78,57.48,40.79,57.48,31.48Z"/>
8
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="24px" height="24px" viewBox="0 0 24 24" fill="none">
2
+ <circle cx="12" cy="17" r="1" fill="var(--playpilot-warning)"/>
3
+ <path d="M12 10L12 14" stroke="var(--playpilot-warning)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4
+ <path d="M3.44722 18.1056L10.2111 4.57771C10.9482 3.10361 13.0518 3.10362 13.7889 4.57771L20.5528 18.1056C21.2177 19.4354 20.2507 21 18.7639 21H5.23607C3.7493 21 2.78231 19.4354 3.44722 18.1056Z" stroke="var(--playpilot-warning)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
5
+ </svg>
@@ -17,5 +17,5 @@
17
17
  </script>
18
18
 
19
19
  <Popover bind:maxHeight>
20
- <Title {title} small compact={maxHeight < 250} />
20
+ <Title {title} small compact={!!maxHeight && maxHeight < 250} />
21
21
  </Popover>
@@ -1,5 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
- import { authorize, getAuthToken, isEditorialModeEnabled } from '$lib/auth'
2
+
3
+ import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, removeAuthParamFromUrl } from '$lib/auth'
4
+ import { track } from '$lib/tracking'
3
5
 
4
6
  /**
5
7
  * @param {Object} [options]
@@ -18,6 +20,10 @@ function fakeFetch({ response = '', status = 200, ok = true } = {}) {
18
20
  )
19
21
  }
20
22
 
23
+ vi.mock('$lib/tracking', () => ({
24
+ track: vi.fn(),
25
+ }))
26
+
21
27
  describe('$lib/auth', () => {
22
28
  beforeEach(() => {
23
29
  vi.resetAllMocks()
@@ -67,6 +73,7 @@ describe('$lib/auth', () => {
67
73
  let authorized = await authorize('https://example.com/some-path?articleReplacementEditToken=some-token')
68
74
 
69
75
  expect(authorized).not.toBeTruthy()
76
+ expect(track).toHaveBeenCalled()
70
77
  })
71
78
  })
72
79
 
@@ -92,6 +99,16 @@ describe('$lib/auth', () => {
92
99
  })
93
100
  })
94
101
 
102
+ describe('getAuthToken', () => {
103
+ it('Should remove auth cookie when clicked', () => {
104
+ document.cookie = 'EncryptedToken=some-token=; expires=Thu, 01 Jan 2020 00:00:00 UTC; path='
105
+
106
+ removeAuthCookie()
107
+
108
+ expect(document.cookie).toBe('EncryptedToken=')
109
+ })
110
+ })
111
+
95
112
  describe('isEditorialModeEnabled', () => {
96
113
  it('Should return true if both search token and window token are given', async () => {
97
114
  window.location.search = '?playpilot-editorial-mode=false'
@@ -112,4 +129,27 @@ describe('$lib/auth', () => {
112
129
  expect(isEditorialModeEnabled()).toBe(true)
113
130
  })
114
131
  })
132
+
133
+ describe('removeAuthParamFromUrl', () => {
134
+ it('Should remove auth param from url', async () => {
135
+ window.location.search = '?articleReplacementEditToken=test'
136
+ removeAuthParamFromUrl()
137
+
138
+ expect(window.location.search).toBe('')
139
+ })
140
+
141
+ it('Should leave other params intact', async () => {
142
+ window.location.search = '?articleReplacementEditToken=test&some_param=abc'
143
+ removeAuthParamFromUrl()
144
+
145
+ expect(window.location.search).toBe('?some_param=abc')
146
+ })
147
+
148
+ it('Should not alter the url if the param is not present', async () => {
149
+ window.location.search = '?some_param=abc&some-other_param=123'
150
+ removeAuthParamFromUrl()
151
+
152
+ expect(window.location.search).toBe('?some_param=abc&some-other_param=123')
153
+ })
154
+ })
115
155
  })