@financial-times/cp-content-pipeline-ui 7.3.2 → 7.4.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 (28) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/components/content-tree/Clip/client/index.d.ts +3 -0
  3. package/lib/components/content-tree/Clip/client/index.js +2 -0
  4. package/lib/components/content-tree/Clip/client/index.js.map +1 -1
  5. package/lib/components/content-tree/Clip/components/ClipTag.d.ts +1 -0
  6. package/lib/components/content-tree/Clip/components/ClipTag.js +2 -2
  7. package/lib/components/content-tree/Clip/components/ClipTag.js.map +1 -1
  8. package/lib/components/content-tree/Clip/template/component.js +3 -2
  9. package/lib/components/content-tree/Clip/template/component.js.map +1 -1
  10. package/lib/components/content-tree/Clip/test/index.spec.js +21 -0
  11. package/lib/components/content-tree/Clip/test/index.spec.js.map +1 -1
  12. package/lib/components/content-tree/CustomCodeComponent/client/index.d.ts +1 -1
  13. package/lib/components/content-tree/CustomCodeComponent/client/index.js +1 -1
  14. package/lib/components/content-tree/CustomCodeComponent/client/index.js.map +1 -1
  15. package/lib/components/content-tree/CustomCodeComponent/client/tracking.d.ts +4 -2
  16. package/lib/components/content-tree/CustomCodeComponent/client/tracking.js +38 -19
  17. package/lib/components/content-tree/CustomCodeComponent/client/tracking.js.map +1 -1
  18. package/lib/components/content-tree/Workarounds.d.ts +1 -0
  19. package/package.json +1 -1
  20. package/src/components/content-tree/Clip/client/index.ts +5 -0
  21. package/src/components/content-tree/Clip/components/ClipTag.tsx +3 -0
  22. package/src/components/content-tree/Clip/template/component.tsx +3 -0
  23. package/src/components/content-tree/Clip/test/__snapshots__/snapshot.spec.tsx.snap +8 -0
  24. package/src/components/content-tree/Clip/test/index.spec.ts +23 -0
  25. package/src/components/content-tree/CustomCodeComponent/client/index.ts +8 -5
  26. package/src/components/content-tree/CustomCodeComponent/client/tracking.ts +55 -29
  27. package/src/components/content-tree/Workarounds.ts +1 -0
  28. package/tsconfig.tsbuildinfo +1 -1
@@ -11,7 +11,7 @@ declare class CustomCodeComponentClient {
11
11
  */
12
12
  tracking: CustomCodeComponentTracker;
13
13
  constructor(el: Element);
14
- static init(rootEl?: Element): (CustomCodeComponentClient | undefined)[];
14
+ static init(rootEl?: Element): CustomCodeComponentClient[] | void;
15
15
  static destroy(components: CustomCodeComponentClient[]): void;
16
16
  }
17
17
  export default CustomCodeComponentClient;
@@ -20,7 +20,7 @@ class CustomCodeComponentClient {
20
20
  }
21
21
  static init(rootEl) {
22
22
  const root = rootEl instanceof Element ? rootEl : document;
23
- return Array.from(root.querySelectorAll('custom-code-component')).map((el) => new CustomCodeComponentClient(el));
23
+ return Array.from(root.querySelectorAll('custom-code-component:not([data-initialised])')).map((el) => new CustomCodeComponentClient(el));
24
24
  }
25
25
  static destroy(components) {
26
26
  components.forEach((component) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/components/content-tree/CustomCodeComponent/client/index.ts"],"names":[],"mappings":";;AAAA,yCAAuD;AAEvD;;;;;GAKG;AACH,MAAM,yBAAyB;IAM7B,YAAY,EAAW;QACrB,MAAM,YAAY,GAAG;YACnB,SAAS,EAAE;gBACT,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE;gBAC/B,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE;gBACnC,IAAI,EAAE,uBAAuB;aAC9B;SACF,CAAA;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,qCAA0B,CAAC,EAAa,EAAE,YAAY,CAAC,CAAA;IAC7E,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,MAAgB;QAC1B,MAAM,IAAI,GAAG,MAAM,YAAY,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;QAC1D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CACnE,CAAC,EAAW,EAAE,EAAE,CAAC,IAAI,yBAAyB,CAAC,EAAE,CAAC,CACnD,CAAA;IACH,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,UAAuC;QACpD,UAAU,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE;YAC/B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAA;QAC9B,CAAC,CAAC,CAAA;IACJ,CAAC;CACF;AAED,kBAAe,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/components/content-tree/CustomCodeComponent/client/index.ts"],"names":[],"mappings":";;AAAA,yCAAuD;AAEvD;;;;;GAKG;AACH,MAAM,yBAAyB;IAM7B,YAAY,EAAW;QACrB,MAAM,YAAY,GAAG;YACnB,SAAS,EAAE;gBACT,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE;gBAC/B,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE;gBACnC,IAAI,EAAE,uBAAuB;aAC9B;SACF,CAAA;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,qCAA0B,CAC5C,EAAiB,EACjB,YAAY,CACb,CAAA;IACH,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,MAAgB;QAC1B,MAAM,IAAI,GAAG,MAAM,YAAY,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;QAC1D,OAAO,KAAK,CAAC,IAAI,CACf,IAAI,CAAC,gBAAgB,CAAC,+CAA+C,CAAC,CACvE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,yBAAyB,CAAC,EAAE,CAAC,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,UAAuC;QACpD,UAAU,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE;YAC/B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAA;QAC9B,CAAC,CAAC,CAAA;IACJ,CAAC;CACF;AAED,kBAAe,yBAAyB,CAAA"}
@@ -16,10 +16,12 @@ type TrackingData = {
16
16
  declare class CustomCodeComponentTracker {
17
17
  private component;
18
18
  private trackingData;
19
- private observer?;
19
+ private mutationObserver?;
20
+ private intersectionObserver?;
20
21
  private viewing?;
21
- constructor(component: Element, trackingData: TrackingData);
22
+ constructor(component: HTMLElement, trackingData: TrackingData);
22
23
  private init;
24
+ private mutationCallback;
23
25
  private onCccConnected;
24
26
  private onCccError;
25
27
  private onView;
@@ -12,32 +12,53 @@ class CustomCodeComponentTracker {
12
12
  this.component = component;
13
13
  this.trackingData = trackingData;
14
14
  this.component = component;
15
- if (this.component.hasAttribute('data-o-tracking-initialised')) {
16
- return;
17
- }
18
15
  this.trackingData = trackingData;
19
- // this needs to instantiated here so that it can be accessed correctly in the destroy method
16
+ // we're using a mutation observer to watch for changes to the component rather than event listeners
17
+ // as dynamic imports in the app are run before the tracking client can be instantiated on second
18
+ // and subsequent page visits in a session
19
+ // the observers need to instantiated here so that they can be accessed correctly in the destroy method
20
20
  // called by the app
21
- this.observer = new IntersectionObserver(this.onChange.bind(this), {
21
+ this.mutationObserver = new MutationObserver(this.mutationCallback.bind(this));
22
+ this.mutationObserver.observe(this.component, {
23
+ attributes: true,
24
+ attributeFilter: ['data-ccc-ready', 'data-ccc-error'],
25
+ });
26
+ this.intersectionObserver = new IntersectionObserver(this.onChange.bind(this), {
22
27
  rootMargin: `0px 0px -300px 0px`,
23
28
  threshold: 0,
24
29
  });
25
- this.observer.observe(this.component);
30
+ this.intersectionObserver.observe(this.component);
26
31
  this.viewing = false;
27
32
  this.destroy = this.destroy.bind(this);
28
33
  this.init();
29
34
  }
30
35
  init() {
31
36
  this.dispatchEvent('mount');
32
- this.component.addEventListener('error', this.onCccError.bind(this));
33
- this.component.addEventListener('ccc-connected', this.onCccConnected.bind(this));
34
- this.component.setAttribute('data-o-tracking-initialised', 'true');
37
+ if (this.component.dataset.cccError) {
38
+ this.onCccError();
39
+ }
40
+ if (this.component.dataset.cccReady) {
41
+ this.onCccConnected();
42
+ }
43
+ this.component.dataset.initialised = 'true';
44
+ }
45
+ mutationCallback(mutationsList) {
46
+ for (const mutation of mutationsList) {
47
+ if (mutation.attributeName === 'data-ccc-ready') {
48
+ this.onCccConnected();
49
+ }
50
+ if (mutation.attributeName === 'data-ccc-error') {
51
+ this.onCccError();
52
+ }
53
+ }
35
54
  }
36
55
  onCccConnected() {
37
56
  this.dispatchEvent('act', { trigger_action: 'success' });
38
57
  }
39
58
  onCccError() {
40
- this.dispatchEvent('act', { trigger_action: 'error' });
59
+ this.dispatchEvent('act', {
60
+ trigger_action: `error: ${this.component.dataset.cccError}`,
61
+ });
41
62
  }
42
63
  onView() {
43
64
  this.dispatchEvent('view');
@@ -75,15 +96,13 @@ class CustomCodeComponentTracker {
75
96
  });
76
97
  }
77
98
  destroy() {
78
- if (this.observer && this.component) {
79
- this.observer.unobserve(this.component);
80
- this.observer.disconnect();
81
- this.component.removeEventListener('ccc-connected', this.onCccConnected.bind(this));
82
- this.component.removeEventListener('error', this.onCccError.bind(this));
83
- if (this.viewing) {
84
- this.onExitView();
85
- }
86
- }
99
+ if (!this.component)
100
+ return;
101
+ this.component.removeAttribute('data-initialised');
102
+ this.mutationObserver?.disconnect();
103
+ this.intersectionObserver?.unobserve(this.component);
104
+ this.intersectionObserver?.disconnect();
105
+ this.viewing && this.onExitView();
87
106
  }
88
107
  }
89
108
  exports.CustomCodeComponentTracker = CustomCodeComponentTracker;
@@ -1 +1 @@
1
- {"version":3,"file":"tracking.js","sourceRoot":"","sources":["../../../../../src/components/content-tree/CustomCodeComponent/client/tracking.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tracking.js","sourceRoot":"","sources":["../../../../../src/components/content-tree/CustomCodeComponent/client/tracking.ts"],"names":[],"mappings":"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"}
@@ -70,6 +70,7 @@ interface ClipSetReferences {
70
70
  credits?: string;
71
71
  description?: string;
72
72
  displayTitle?: string;
73
+ systemTitle?: string;
73
74
  contentWarning?: string[];
74
75
  source?: string;
75
76
  subtitle?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/cp-content-pipeline-ui",
3
- "version": "7.3.2",
3
+ "version": "7.4.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -33,6 +33,7 @@ interface Opts {
33
33
  intersectionObserverThreshold?: number
34
34
  closedCaption?: boolean
35
35
  caption?: string
36
+ systemTitle?: string
36
37
  }
37
38
 
38
39
  interface TrackingData {
@@ -44,8 +45,10 @@ interface TrackingData {
44
45
  amount?: number
45
46
  amountPercentage?: number
46
47
  video: {
48
+ id?: string
47
49
  duration?: number
48
50
  source_url?: string
51
+ name?: string
49
52
  layout?: string
50
53
  loop?: number
51
54
  type: string
@@ -353,8 +356,10 @@ class Clip extends ClipInterface {
353
356
  progress: this.getRelevantProgress(),
354
357
  url: window.location.href,
355
358
  video: {
359
+ id: this.opts.id,
356
360
  duration: this.getDuration(),
357
361
  source_url: this.opts.id,
362
+ name: this.opts.systemTitle,
358
363
  layout: this.opts.layout,
359
364
  // Counter of the number of times the video has looped
360
365
  loop: this.loops,
@@ -27,6 +27,7 @@ interface ClipTagProps {
27
27
  credits?: string
28
28
  caption?: string
29
29
  preload?: string
30
+ systemTitle?: string
30
31
  }
31
32
 
32
33
  export const ClipTag: React.FC<ClipTagProps> = ({
@@ -47,6 +48,7 @@ export const ClipTag: React.FC<ClipTagProps> = ({
47
48
  noInfoBox = false,
48
49
  noCaption = false,
49
50
  dataTrackable,
51
+ systemTitle,
50
52
  }) => {
51
53
  const { pixelWidth, pixelHeight } = clip?.dataSource?.[0]
52
54
  ? clip.dataSource[0]
@@ -68,6 +70,7 @@ export const ClipTag: React.FC<ClipTagProps> = ({
68
70
  data-trackable={dataTrackable}
69
71
  data-o-component="cp-clip"
70
72
  data-cp-clip-id={id}
73
+ data-cp-clip-system-title={systemTitle}
71
74
  >
72
75
  {!noInfoBox && <VideoInfoBox />}
73
76
  <div className="cp-clip__video-container">
@@ -64,6 +64,7 @@ const ClipComponent: React.FC<ClipProps> = ({
64
64
  clip?.dataSource?.length && content.autoplay ? '' : poster
65
65
  const noAudio = content.noAudio ?? false
66
66
  const accessibility = content.accessibility ?? {}
67
+ const systemTitle = content.systemTitle ?? ''
67
68
 
68
69
  if (preset === 'thumbnail') {
69
70
  return (
@@ -80,6 +81,7 @@ const ClipComponent: React.FC<ClipProps> = ({
80
81
  noCaption
81
82
  noDescription
82
83
  noInfoBox
84
+ systemTitle={systemTitle}
83
85
  />
84
86
  )
85
87
  }
@@ -99,6 +101,7 @@ const ClipComponent: React.FC<ClipProps> = ({
99
101
  clip={clip}
100
102
  credits={content.credits ?? ''}
101
103
  caption={content.caption ?? ''}
104
+ systemTitle={systemTitle}
102
105
  accessibility={accessibility}
103
106
  preload={preload}
104
107
  dataTrackable="next-article-cp-clip"
@@ -16,6 +16,7 @@ exports[`Clip Snapshot component rendered on server full-grid default render 1`]
16
16
  data-trackable=\\"next-article-cp-clip\\"
17
17
  data-o-component=\\"cp-clip\\"
18
18
  data-cp-clip-id=\\"localhost:8080/fakevideo.mpg\\"
19
+ data-cp-clip-system-title=\\"\\"
19
20
  >
20
21
  <div
21
22
  data-o-component=\\"o-expander\\"
@@ -82,6 +83,7 @@ exports[`Clip Snapshot component rendered on server full-grid render with attrib
82
83
  data-trackable=\\"next-article-cp-clip\\"
83
84
  data-o-component=\\"cp-clip\\"
84
85
  data-cp-clip-id=\\"localhost:8080/fakevideo.mpg\\"
86
+ data-cp-clip-system-title=\\"\\"
85
87
  >
86
88
  <div
87
89
  data-o-component=\\"o-expander\\"
@@ -172,6 +174,7 @@ exports[`Clip Snapshot component rendered on server in-line render 1`] = `
172
174
  data-trackable=\\"next-article-cp-clip\\"
173
175
  data-o-component=\\"cp-clip\\"
174
176
  data-cp-clip-id=\\"localhost:8080/fakevideo.mpg\\"
177
+ data-cp-clip-system-title=\\"\\"
175
178
  >
176
179
  <div
177
180
  data-o-component=\\"o-expander\\"
@@ -236,6 +239,7 @@ exports[`Clip Snapshot component rendered on server in-line render with attribut
236
239
  data-trackable=\\"next-article-cp-clip\\"
237
240
  data-o-component=\\"cp-clip\\"
238
241
  data-cp-clip-id=\\"localhost:8080/fakevideo.mpg\\"
242
+ data-cp-clip-system-title=\\"\\"
239
243
  >
240
244
  <div
241
245
  data-o-component=\\"o-expander\\"
@@ -330,6 +334,7 @@ exports[`Clip Snapshot component rendered on server mid-grid default render 1`]
330
334
  data-trackable=\\"next-article-cp-clip\\"
331
335
  data-o-component=\\"cp-clip\\"
332
336
  data-cp-clip-id=\\"localhost:8080/fakevideo.mpg\\"
337
+ data-cp-clip-system-title=\\"\\"
333
338
  >
334
339
  <div
335
340
  data-o-component=\\"o-expander\\"
@@ -401,6 +406,7 @@ exports[`Clip Snapshot component rendered on server mid-grid render with attribu
401
406
  data-trackable=\\"next-article-cp-clip\\"
402
407
  data-o-component=\\"cp-clip\\"
403
408
  data-cp-clip-id=\\"localhost:8080/fakevideo.mpg\\"
409
+ data-cp-clip-system-title=\\"\\"
404
410
  >
405
411
  <div
406
412
  data-o-component=\\"o-expander\\"
@@ -498,6 +504,7 @@ exports[`Clip Snapshot component rendered on server renders multiple video sourc
498
504
  data-trackable=\\"next-article-cp-clip\\"
499
505
  data-o-component=\\"cp-clip\\"
500
506
  data-cp-clip-id=\\"84d7e1b0-e8b2-4ffc-a798-306f29dc9d52\\"
507
+ data-cp-clip-system-title=\\"\\"
501
508
  >
502
509
  <div
503
510
  data-o-component=\\"o-expander\\"
@@ -600,6 +607,7 @@ exports[`Clip Snapshot component rendered on server supports new Origami images,
600
607
  data-trackable=\\"next-article-cp-clip\\"
601
608
  data-o-component=\\"cp-clip\\"
602
609
  data-cp-clip-id=\\"localhost:8080/fakevideo.mpg\\"
610
+ data-cp-clip-system-title=\\"\\"
603
611
  >
604
612
  <div
605
613
  data-o-component=\\"o-expander\\"
@@ -183,6 +183,9 @@ describe('Clip', () => {
183
183
  expect(clip.opts.layout).toBe(
184
184
  clip.containerEl.getAttribute('data-cp-clip-layout')
185
185
  )
186
+ expect(clip.opts.systemTitle).toBe(
187
+ clip.containerEl.getAttribute('data-cp-clip-system-title')
188
+ )
186
189
  })
187
190
 
188
191
  it('dispatches an analytics mount event when the component is initialised', () => {
@@ -192,10 +195,12 @@ describe('Clip', () => {
192
195
  expect(data).not.toBeFalsy()
193
196
  expect(data.video.duration).toBe(0)
194
197
  //expect(data.contentId).toBe(clip.opts.id);
198
+ expect(data.video.id).toBe(clip.opts.id)
195
199
  expect(data.video.layout).toBe(clip.opts.layout)
196
200
  expect(data.video.loop).toBe(0)
197
201
  expect(data.video.type).toBe('clip')
198
202
  expect(data.video.automated).toBe(false)
203
+ expect(data.video.name).toBe(clip.opts.systemTitle)
199
204
  expect(data.progress).toBe(0)
200
205
  expect(data.category).toBe('video')
201
206
  expect(data.action).toBe(eventType)
@@ -210,10 +215,12 @@ describe('Clip', () => {
210
215
  expect(e.detail.category).toBe('video')
211
216
  expect(e.detail.video.duration).toBe(10000)
212
217
  //expect(e.detail.contentId).toBe(clip.opts.id);
218
+ expect(e.detail.video.id).toBe(clip.opts.id)
213
219
  expect(e.detail.video.layout).toBe(clip.opts.layout)
214
220
  expect(e.detail.video.loop).toBe(0)
215
221
  expect(e.detail.video.type).toBe('clip')
216
222
  expect(e.detail.video.automated).toBe(false)
223
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
217
224
  expect(e.detail.progress).toBe(0)
218
225
  expect(spy).toHaveBeenCalled()
219
226
  expect(clip.started).toBe(true)
@@ -238,10 +245,12 @@ describe('Clip', () => {
238
245
  expect(e.detail.action).toBe(eventType)
239
246
  expect(e.detail.video.duration).toBe(10000)
240
247
  //expect(e.detail.contentId).toBe(clip.opts.id);
248
+ expect(e.detail.video.id).toBe(clip.opts.id)
241
249
  expect(e.detail.video.layout).toBe(clip.opts.layout)
242
250
  expect(e.detail.video.loop).toBe(0)
243
251
  expect(e.detail.video.type).toBe('clip')
244
252
  expect(e.detail.video.automated).toBe(false)
253
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
245
254
  expect(e.detail.progress).toBe(0)
246
255
  expect(spy).toHaveBeenCalled()
247
256
  expect(clip.started).toBe(true)
@@ -266,10 +275,12 @@ describe('Clip', () => {
266
275
  expect(e.detail.action).toBe(eventType)
267
276
  expect(e.detail.video.duration).toBe(10000)
268
277
  //expect(e.detail.contentId).toBe(clip.opts.id);
278
+ expect(e.detail.video.id).toBe(clip.opts.id)
269
279
  expect(e.detail.video.layout).toBe(clip.opts.layout)
270
280
  expect(e.detail.video.loop).toBe(0)
271
281
  expect(e.detail.video.type).toBe('clip')
272
282
  expect(e.detail.video.automated).toBe(false)
283
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
273
284
  expect(e.detail.progress).toBe(25)
274
285
  document.body.removeEventListener('oTracking.event', listener)
275
286
  done()
@@ -339,10 +350,12 @@ describe('Clip', () => {
339
350
  expect(e.detail.action).toBe(eventType)
340
351
  expect(e.detail.video.duration).toBe(10000)
341
352
  //expect(e.detail.contentId).toBe(clip.opts.id);
353
+ expect(e.detail.video.id).toBe(clip.opts.id)
342
354
  expect(e.detail.video.layout).toBe(clip.opts.layout)
343
355
  expect(e.detail.video.loop).toBe(0)
344
356
  expect(e.detail.video.type).toBe('clip')
345
357
  expect(e.detail.video.automated).toBe(false)
358
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
346
359
  expect(e.detail.progress).toBe(0)
347
360
  expect(spy).toHaveBeenCalled()
348
361
  expect(spy2).toHaveBeenCalled()
@@ -374,10 +387,12 @@ describe('Clip', () => {
374
387
  expect(e.detail.category).toBe('video')
375
388
  expect(e.detail.video.duration).toBe(10000)
376
389
  //expect(e.detail.contentId).toBe(clip.opts.id);
390
+ expect(e.detail.video.id).toBe(clip.opts.id)
377
391
  expect(e.detail.video.layout).toBe(clip.opts.layout)
378
392
  expect(e.detail.video.loop).toBe(0)
379
393
  expect(e.detail.video.type).toBe('clip')
380
394
  expect(e.detail.video.automated).toBe(false)
395
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
381
396
  expect(spy).toHaveBeenCalled()
382
397
  expect(clip.started).toBe(true)
383
398
  expect(clip.canAutoplay).toBe(false)
@@ -492,9 +507,11 @@ describe('Clip', () => {
492
507
  expect(e.detail.category).toBe('video')
493
508
  expect(e.detail.video.duration).toBe(10000)
494
509
  //expect(e.detail.contentId).toBe(clip.opts.id);
510
+ expect(e.detail.video.id).toBe(clip.opts.id)
495
511
  expect(e.detail.video.layout).toBe(clip.opts.layout)
496
512
  expect(e.detail.video.loop).toBe(0)
497
513
  expect(e.detail.video.type).toBe('clip')
514
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
498
515
  expect(e.detail.progress).toBe(0)
499
516
  expect(clip.started).toBe(true)
500
517
  expect(clip.playStart).not.toBeFalsy()
@@ -514,9 +531,11 @@ describe('Clip', () => {
514
531
  expect(e.detail.category).toBe('video')
515
532
  expect(e.detail.video.duration).toBe(10000)
516
533
  //expect(e.detail.contentId).toBe(clip.opts.id);
534
+ expect(e.detail.video.id).toBe(clip.opts.id)
517
535
  expect(e.detail.video.layout).toBe(clip.opts.layout)
518
536
  expect(e.detail.video.loop).toBe(0)
519
537
  expect(e.detail.video.type).toBe('clip')
538
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
520
539
  expect(e.detail.progress).toBe(0)
521
540
  expect(clip.started).toBe(true)
522
541
  expect(clip.playStart).toBeFalsy()
@@ -537,10 +556,12 @@ describe('Clip', () => {
537
556
  expect(e.detail.category).toBe('video')
538
557
  expect(e.detail.video.duration).toBe(10000)
539
558
  //expect(e.detail.contentId).toBe(clip.opts.id);
559
+ expect(e.detail.video.id).toBe(clip.opts.id)
540
560
  expect(e.detail.video.layout).toBe(clip.opts.layout)
541
561
  expect(e.detail.video.loop).toBe(0)
542
562
  expect(e.detail.video.type).toBe('clip')
543
563
  expect(e.detail.video.automated).toBe(false)
564
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
544
565
  expect(e.detail.progress).toBe(0)
545
566
  expect(clip.started).toBe(true)
546
567
  expect(clip.canAutoplay).toBe(false)
@@ -562,10 +583,12 @@ describe('Clip', () => {
562
583
  expect(e.detail.category).toBe('video')
563
584
  expect(e.detail.video.duration).toBe(10000)
564
585
  //expect(e.detail.contentId).toBe(clip.opts.id);
586
+ expect(e.detail.video.id).toBe(clip.opts.id)
565
587
  expect(e.detail.video.layout).toBe(clip.opts.layout)
566
588
  expect(e.detail.video.loop).toBe(0)
567
589
  expect(e.detail.video.type).toBe('clip')
568
590
  expect(e.detail.video.automated).toBe(false)
591
+ expect(e.detail.video.name).toBe(clip.opts.systemTitle)
569
592
  expect(e.detail.progress).toBe(0)
570
593
  expect(clip.started).toBe(true)
571
594
  expect(clip.canAutoplay).toBe(false)
@@ -20,14 +20,17 @@ class CustomCodeComponentClient {
20
20
  type: 'custom-code-component',
21
21
  },
22
22
  }
23
- this.tracking = new CustomCodeComponentTracker(el as Element, trackingData)
23
+ this.tracking = new CustomCodeComponentTracker(
24
+ el as HTMLElement,
25
+ trackingData
26
+ )
24
27
  }
25
28
 
26
- static init(rootEl?: Element): (CustomCodeComponentClient | undefined)[] {
29
+ static init(rootEl?: Element): CustomCodeComponentClient[] | void {
27
30
  const root = rootEl instanceof Element ? rootEl : document
28
- return Array.from(root.querySelectorAll('custom-code-component')).map(
29
- (el: Element) => new CustomCodeComponentClient(el)
30
- )
31
+ return Array.from(
32
+ root.querySelectorAll('custom-code-component:not([data-initialised])')
33
+ ).map((el) => new CustomCodeComponentClient(el))
31
34
  }
32
35
 
33
36
  static destroy(components: CustomCodeComponentClient[]): void {
@@ -16,22 +16,36 @@ type TrackingData = {
16
16
  * @param {TrackingData} trackingData - The specific component data
17
17
  */
18
18
  class CustomCodeComponentTracker {
19
- private observer?: IntersectionObserver
19
+ private mutationObserver?: MutationObserver
20
+ private intersectionObserver?: IntersectionObserver
20
21
  private viewing?: boolean
21
22
 
22
- constructor(private component: Element, private trackingData: TrackingData) {
23
+ constructor(
24
+ private component: HTMLElement,
25
+ private trackingData: TrackingData
26
+ ) {
23
27
  this.component = component
24
- if (this.component.hasAttribute('data-o-tracking-initialised')) {
25
- return
26
- }
27
28
  this.trackingData = trackingData
28
- // this needs to instantiated here so that it can be accessed correctly in the destroy method
29
+ // we're using a mutation observer to watch for changes to the component rather than event listeners
30
+ // as dynamic imports in the app are run before the tracking client can be instantiated on second
31
+ // and subsequent page visits in a session
32
+ // the observers need to instantiated here so that they can be accessed correctly in the destroy method
29
33
  // called by the app
30
- this.observer = new IntersectionObserver(this.onChange.bind(this), {
31
- rootMargin: `0px 0px -300px 0px`,
32
- threshold: 0,
34
+ this.mutationObserver = new MutationObserver(
35
+ this.mutationCallback.bind(this)
36
+ )
37
+ this.mutationObserver.observe(this.component, {
38
+ attributes: true,
39
+ attributeFilter: ['data-ccc-ready', 'data-ccc-error'],
33
40
  })
34
- this.observer.observe(this.component)
41
+ this.intersectionObserver = new IntersectionObserver(
42
+ this.onChange.bind(this),
43
+ {
44
+ rootMargin: `0px 0px -300px 0px`,
45
+ threshold: 0,
46
+ }
47
+ )
48
+ this.intersectionObserver.observe(this.component)
35
49
  this.viewing = false
36
50
  this.destroy = this.destroy.bind(this)
37
51
  this.init()
@@ -39,12 +53,26 @@ class CustomCodeComponentTracker {
39
53
 
40
54
  private init() {
41
55
  this.dispatchEvent('mount')
42
- this.component.addEventListener('error', this.onCccError.bind(this))
43
- this.component.addEventListener(
44
- 'ccc-connected',
45
- this.onCccConnected.bind(this)
46
- )
47
- this.component.setAttribute('data-o-tracking-initialised', 'true')
56
+ if (this.component.dataset.cccError) {
57
+ this.onCccError()
58
+ }
59
+
60
+ if (this.component.dataset.cccReady) {
61
+ this.onCccConnected()
62
+ }
63
+
64
+ this.component.dataset.initialised = 'true'
65
+ }
66
+
67
+ private mutationCallback(mutationsList: MutationRecord[]): void {
68
+ for (const mutation of mutationsList) {
69
+ if (mutation.attributeName === 'data-ccc-ready') {
70
+ this.onCccConnected()
71
+ }
72
+ if (mutation.attributeName === 'data-ccc-error') {
73
+ this.onCccError()
74
+ }
75
+ }
48
76
  }
49
77
 
50
78
  private onCccConnected(): void {
@@ -52,7 +80,9 @@ class CustomCodeComponentTracker {
52
80
  }
53
81
 
54
82
  private onCccError(): void {
55
- this.dispatchEvent('act', { trigger_action: 'error' })
83
+ this.dispatchEvent('act', {
84
+ trigger_action: `error: ${this.component.dataset.cccError}`,
85
+ })
56
86
  }
57
87
 
58
88
  private onView(): void {
@@ -99,18 +129,14 @@ class CustomCodeComponentTracker {
99
129
  }
100
130
 
101
131
  destroy(): void {
102
- if (this.observer && this.component) {
103
- this.observer.unobserve(this.component)
104
- this.observer.disconnect()
105
- this.component.removeEventListener(
106
- 'ccc-connected',
107
- this.onCccConnected.bind(this)
108
- )
109
- this.component.removeEventListener('error', this.onCccError.bind(this))
110
- if (this.viewing) {
111
- this.onExitView()
112
- }
113
- }
132
+ if (!this.component) return
133
+
134
+ this.component.removeAttribute('data-initialised')
135
+ this.mutationObserver?.disconnect()
136
+ this.intersectionObserver?.unobserve(this.component)
137
+ this.intersectionObserver?.disconnect()
138
+
139
+ this.viewing && this.onExitView()
114
140
  }
115
141
  }
116
142
 
@@ -162,6 +162,7 @@ interface ClipSetReferences {
162
162
  credits?: string
163
163
  description?: string
164
164
  displayTitle?: string
165
+ systemTitle?: string
165
166
  contentWarning?: string[]
166
167
  source?: string
167
168
  subtitle?: string