@streamscloud/embeddable 8.1.0 → 8.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 (52) hide show
  1. package/dist/ads/ad-card/cmp.ad-card.svelte +8 -5
  2. package/dist/ads/ad-card/cmp.ad-card.svelte.d.ts +2 -0
  3. package/dist/content-player/cmp.content-player.svelte +1 -0
  4. package/dist/content-player/content-player-config.svelte.d.ts +8 -0
  5. package/dist/content-player/content-player-config.svelte.js +9 -1
  6. package/dist/content-player/controls-and-attachments.svelte +1 -0
  7. package/dist/content-player/overview-panel.svelte +15 -12
  8. package/dist/core/enums.d.ts +5 -1
  9. package/dist/core/enums.js +4 -0
  10. package/dist/marketing-tracking/index.d.ts +2 -0
  11. package/dist/marketing-tracking/index.js +1 -0
  12. package/dist/marketing-tracking/service.d.ts +11 -0
  13. package/dist/marketing-tracking/service.js +35 -0
  14. package/dist/marketing-tracking/types.d.ts +5 -0
  15. package/dist/marketing-tracking/types.js +1 -0
  16. package/dist/media-center/media-center/cmp.media-center.svelte +6 -3
  17. package/dist/media-center/media-center/discover-panel-handler.svelte.d.ts +2 -0
  18. package/dist/media-center/media-center/discover-panel-handler.svelte.js +6 -2
  19. package/dist/media-center/media-center/discover-panel.svelte +1 -1
  20. package/dist/posts/attachments/cmp.attachments.svelte +14 -2
  21. package/dist/posts/attachments/cmp.attachments.svelte.d.ts +2 -0
  22. package/dist/posts/model/post-model.d.ts +2 -0
  23. package/dist/posts/model/post-model.js +3 -0
  24. package/dist/posts/model/types.d.ts +2 -1
  25. package/dist/posts/model/types.js +1 -1
  26. package/dist/posts/post-viewer/attachments-horizontal.svelte +5 -4
  27. package/dist/posts/post-viewer/attachments-horizontal.svelte.d.ts +2 -0
  28. package/dist/posts/post-viewer/cmp.post-viewer.svelte +13 -2
  29. package/dist/posts/post-viewer/cmp.post-viewer.svelte.d.ts +2 -0
  30. package/dist/posts/post-viewer/mapper.js +18 -1
  31. package/dist/products/product-card/cmp.product-card.svelte +10 -7
  32. package/dist/products/product-card/cmp.product-card.svelte.d.ts +2 -0
  33. package/dist/products/product-card/mapper.d.ts +3 -1
  34. package/dist/products/product-card/mapper.js +2 -2
  35. package/dist/streams/layout/cmp.slot-content.svelte +14 -6
  36. package/dist/streams/layout/cmp.slot-content.svelte.d.ts +2 -0
  37. package/dist/streams/layout/element-views/cmp.container-stream-element.svelte +7 -2
  38. package/dist/streams/layout/element-views/cmp.container-stream-element.svelte.d.ts +2 -0
  39. package/dist/streams/layout/element-views/cmp.short-video-stream-element.svelte +8 -2
  40. package/dist/streams/layout/element-views/cmp.short-video-stream-element.svelte.d.ts +2 -0
  41. package/dist/streams/layout/element-views/cmp.stream-element.svelte +3 -2
  42. package/dist/streams/layout/element-views/cmp.stream-element.svelte.d.ts +2 -0
  43. package/dist/streams/layout/index.d.ts +1 -0
  44. package/dist/streams/layout/models/mapper.js +2 -1
  45. package/dist/streams/layout/serializer.svelte.js +0 -1
  46. package/dist/streams/layout/types.d.ts +4 -0
  47. package/dist/streams/layout/types.js +1 -0
  48. package/dist/streams/stream-page-viewer/cmp.stream-page-viewer.svelte +2 -2
  49. package/dist/streams/stream-page-viewer/cmp.stream-page-viewer.svelte.d.ts +2 -0
  50. package/dist/streams/stream-player/stream-player-view.svelte +15 -3
  51. package/dist/ui/video/cmp.video.svelte +1 -1
  52. package/package.json +1 -1
@@ -1,8 +1,10 @@
1
- <script lang="ts">import { toPriceRepresentation } from '../../products/price-helper';
1
+ <script lang="ts">var _a;
2
+ import { enrichAdLinkWithTracking } from '../../marketing-tracking';
3
+ import { toPriceRepresentation } from '../../products/price-helper';
2
4
  import { Button, ButtonSize } from '../../ui/button';
3
5
  import { Image } from '../../ui/image';
4
6
  import { LineClamp } from '../../ui/line-clamp';
5
- let { ad, inert = false, on } = $props();
7
+ let { ad, trackingParams, inert = false, on } = $props();
6
8
  const trackImpression = (node) => {
7
9
  if (on === null || on === void 0 ? void 0 : on.impression) {
8
10
  const observer = new IntersectionObserver((entries) => {
@@ -22,12 +24,13 @@ const trackImpression = (node) => {
22
24
  };
23
25
  }
24
26
  };
27
+ const enrichedLink = $derived(((_a = ad.ctaButton) === null || _a === void 0 ? void 0 : _a.url) ? enrichAdLinkWithTracking({ link: ad.ctaButton.url, adId: ad.id, trackingParams }) : null);
25
28
  const handleAdClick = () => {
26
29
  if (on === null || on === void 0 ? void 0 : on.click) {
27
30
  on.click(ad.id);
28
31
  }
29
- if (ad.ctaButton) {
30
- window.open(ad.ctaButton.url, '_blank');
32
+ if (enrichedLink) {
33
+ window.open(enrichedLink, '_blank', 'noopener noreferrer');
31
34
  }
32
35
  };
33
36
  </script>
@@ -60,7 +63,7 @@ const handleAdClick = () => {
60
63
  {/if}
61
64
  </div>
62
65
  <div class="ad-card__button-container">
63
- {#if ad.ctaButton && ad.ctaButton.url && ad.ctaButton.text}
66
+ {#if ad.ctaButton && enrichedLink && ad.ctaButton.text}
64
67
  <div class="ad-card__button">
65
68
  <Button
66
69
  size={ButtonSize.Standard}
@@ -1,6 +1,8 @@
1
+ import { type TrackingParams } from '../../marketing-tracking';
1
2
  import type { AdCardModel } from './types';
2
3
  type Props = {
3
4
  ad: AdCardModel;
5
+ trackingParams: TrackingParams;
4
6
  inert?: boolean;
5
7
  on?: {
6
8
  click?: (id: string) => void;
@@ -171,6 +171,7 @@ const variables = $derived.by(() => {
171
171
  {#if postModel}
172
172
  <PostViewer
173
173
  model={postModel}
174
+ trackingParams={config.trackingParams}
174
175
  socialInteractionsHandler={config.socialInteractionsHandler}
175
176
  enableAttachments={config.uiManager.showPostOverlayAttachments}
176
177
  enableControls={config.uiManager.showPostOverlayControls}
@@ -15,6 +15,7 @@ export declare class ContentPlayerConfig<T extends {
15
15
  readonly socialInteractionsHandler: IPostSocialInteractionsHandler | undefined;
16
16
  readonly uiManager: ContentPlayerUIManager;
17
17
  private _mediaCenterData;
18
+ private _trackingParams;
18
19
  private _mappers;
19
20
  private mappedPostsCache;
20
21
  constructor(init: {
@@ -25,16 +26,19 @@ export declare class ContentPlayerConfig<T extends {
25
26
  settings?: ContentPlayerSettings;
26
27
  callbacks?: ContentPlayerCallbacks;
27
28
  playerSliderCallbacks?: PlayerSliderCallbacks<T>;
29
+ trackingParams?: ContentPlayerTrackingParams | null;
28
30
  });
29
31
  get mediaCenterControlsPanel(): MediaCenterData['controlsPanel'] | null;
30
32
  get mediaCenterCallbacks(): MediaCenterData['callbacks'] | null;
31
33
  get playerLogo(): string | null;
32
34
  get fadeContent(): boolean;
35
+ get trackingParams(): ContentPlayerConfig<T>['_trackingParams'];
33
36
  itemAsPostViewerModel: (item: T & {
34
37
  mediaIndex?: number;
35
38
  }) => PostModel | null;
36
39
  setBackgroundImageUrl: (imageUrl: string | null) => void;
37
40
  updateMediaCenterData: (data: MediaCenterData | undefined) => void;
41
+ updateTrackingParams: (data: ContentPlayerTrackingParams | null) => void;
38
42
  }
39
43
  export type ContentPlayerMappers<T> = {
40
44
  postModelFromCurrentItem: (item: T) => IPostModel | null;
@@ -47,3 +51,7 @@ export type ContentPlayerCallbacks = {
47
51
  adClick?: (adId: string, postId: string) => void;
48
52
  adImpression?: (adId: string, postId: string) => void;
49
53
  };
54
+ export type ContentPlayerTrackingParams = {
55
+ streamId?: string;
56
+ campaignId?: string;
57
+ } | false;
@@ -9,11 +9,13 @@ export class ContentPlayerConfig {
9
9
  socialInteractionsHandler;
10
10
  uiManager = new ContentPlayerUIManager();
11
11
  _mediaCenterData = $state.raw(null);
12
+ _trackingParams = $state.raw(null);
12
13
  _mappers;
13
14
  mappedPostsCache = new Map();
14
15
  constructor(init) {
15
- const { playerBuffer, mappers, socialInteractionsHandler, mediaCenterData, settings, callbacks, playerSliderCallbacks } = init;
16
+ const { playerBuffer, trackingParams, mappers, socialInteractionsHandler, mediaCenterData, settings, callbacks, playerSliderCallbacks } = init;
16
17
  this.playerBuffer = playerBuffer;
18
+ this._trackingParams = trackingParams ?? null;
17
19
  this.settings = settings || new ContentPlayerSettings();
18
20
  this._mediaCenterData = mediaCenterData || null;
19
21
  this.callbacks = callbacks || null;
@@ -33,6 +35,9 @@ export class ContentPlayerConfig {
33
35
  get fadeContent() {
34
36
  return this._mediaCenterData?.overlayIsActive || false;
35
37
  }
38
+ get trackingParams() {
39
+ return this._trackingParams;
40
+ }
36
41
  itemAsPostViewerModel = (item) => {
37
42
  if (this.mappedPostsCache.has(item.id)) {
38
43
  return this.mappedPostsCache.get(item.id) || null;
@@ -52,4 +57,7 @@ export class ContentPlayerConfig {
52
57
  updateMediaCenterData = (data) => {
53
58
  this._mediaCenterData = data ?? null;
54
59
  };
60
+ updateTrackingParams = (data) => {
61
+ this._trackingParams = data ?? null;
62
+ };
55
63
  }
@@ -75,6 +75,7 @@ const variables = $derived.by(() => {
75
75
  <div class="controls-and-attachments__post-attachments" transition:slideHorizontally|local>
76
76
  <PostAttachments
77
77
  model={currentItemPostContainer}
78
+ trackingParams={config.trackingParams}
78
79
  locale={config.settings.locale}
79
80
  on={{
80
81
  productClick: (id) => config.callbacks?.productClick?.(id, currentItemPostContainer.id),
@@ -28,19 +28,22 @@ const buttonVariables = $derived.by(() => {
28
28
  });
29
29
  </script>
30
30
 
31
- {#if !uiManager.overviewCollapsed && !contentFaded}
32
- <div
33
- class="overview-panel"
34
- transition:slideHorizontally|local
35
- use:overviewAttached
36
- style={panelVariables}
37
- onclick={handlePanelClick}
38
- onkeydown={() => {}}
39
- role="none">
40
- <div class="overview-panel__content" onclick={(e) => e.stopPropagation()} onkeydown={() => {}} role="none">
41
- {@render children()}
31
+ <!--Double if for correct behavior of slideHorizontally-->
32
+ {#if uiManager.viewInitialized}
33
+ {#if !uiManager.overviewCollapsed && !contentFaded}
34
+ <div
35
+ class="overview-panel"
36
+ transition:slideHorizontally|local
37
+ use:overviewAttached
38
+ style={panelVariables}
39
+ onclick={handlePanelClick}
40
+ onkeydown={() => {}}
41
+ role="none">
42
+ <div class="overview-panel__content" onclick={(e) => e.stopPropagation()} onkeydown={() => {}} role="none">
43
+ {@render children()}
44
+ </div>
42
45
  </div>
43
- </div>
46
+ {/if}
44
47
  {/if}
45
48
 
46
49
  <button
@@ -33,7 +33,11 @@ export declare enum ProfileType {
33
33
  Organization = "ORGANIZATION"
34
34
  }
35
35
  export declare enum PostType {
36
- ShortVideo = "SHORT_VIDEO"
36
+ Article = "ARTICLE",
37
+ Media = "MEDIA",
38
+ Moment = "MOMENT",
39
+ ShortVideo = "SHORT_VIDEO",
40
+ Video = "VIDEO"
37
41
  }
38
42
  export declare enum Status {
39
43
  Published = "PUBLISHED"
@@ -41,7 +41,11 @@ export var ProfileType;
41
41
  })(ProfileType || (ProfileType = {}));
42
42
  export var PostType;
43
43
  (function (PostType) {
44
+ PostType["Article"] = "ARTICLE";
45
+ PostType["Media"] = "MEDIA";
46
+ PostType["Moment"] = "MOMENT";
44
47
  PostType["ShortVideo"] = "SHORT_VIDEO";
48
+ PostType["Video"] = "VIDEO";
45
49
  })(PostType || (PostType = {}));
46
50
  export var Status;
47
51
  (function (Status) {
@@ -0,0 +1,2 @@
1
+ export type { TrackingParams } from './types';
2
+ export { enrichAdLinkWithTracking, enrichProductLinkWithTracking } from './service';
@@ -0,0 +1 @@
1
+ export { enrichAdLinkWithTracking, enrichProductLinkWithTracking } from './service';
@@ -0,0 +1,11 @@
1
+ import type { TrackingParams } from './types';
2
+ export declare const enrichProductLinkWithTracking: (data: {
3
+ link: string | URL;
4
+ productId: string;
5
+ trackingParams: TrackingParams | false | null | undefined;
6
+ }) => URL;
7
+ export declare const enrichAdLinkWithTracking: (data: {
8
+ link: string | URL;
9
+ adId: string;
10
+ trackingParams: TrackingParams | false | null | undefined;
11
+ }) => URL;
@@ -0,0 +1,35 @@
1
+ export const enrichProductLinkWithTracking = (data) => {
2
+ const { link, productId, trackingParams } = data;
3
+ const url = link instanceof URL ? link : new URL(link);
4
+ if (trackingParams === false) {
5
+ return url;
6
+ }
7
+ url.searchParams.set('utm_source', 'streams');
8
+ url.searchParams.set('streams_product_id', productId);
9
+ return addTrackingParamsToUrl(url, trackingParams);
10
+ };
11
+ export const enrichAdLinkWithTracking = (data) => {
12
+ const { link, adId, trackingParams } = data;
13
+ const url = link instanceof URL ? link : new URL(link);
14
+ if (trackingParams === false) {
15
+ return url;
16
+ }
17
+ url.searchParams.set('utm_source', 'streams');
18
+ url.searchParams.set('streams_ad_id', adId);
19
+ return addTrackingParamsToUrl(url, trackingParams);
20
+ };
21
+ const addTrackingParamsToUrl = (url, trackingParams) => {
22
+ if (!trackingParams) {
23
+ return url;
24
+ }
25
+ if (trackingParams.campaignId) {
26
+ url.searchParams.set('utm_id', trackingParams.campaignId);
27
+ }
28
+ if (trackingParams.shortVideoId) {
29
+ url.searchParams.set('streams_short_video_id', trackingParams.shortVideoId);
30
+ }
31
+ if (trackingParams.streamId) {
32
+ url.searchParams.set('streams_stream_id', trackingParams.streamId);
33
+ }
34
+ return url;
35
+ };
@@ -0,0 +1,5 @@
1
+ export type TrackingParams = {
2
+ streamId?: string;
3
+ shortVideoId?: string;
4
+ campaignId?: string;
5
+ } | false;
@@ -0,0 +1 @@
1
+ export {};
@@ -292,7 +292,7 @@ const onWidthAnchorMounted = (node) => {
292
292
  pointer-events: auto;
293
293
  position: relative;
294
294
  flex: 1 1 auto;
295
- max-width: max-content;
295
+ max-width: 100%;
296
296
  min-width: 0;
297
297
  overflow-x: auto;
298
298
  overflow-y: hidden;
@@ -322,9 +322,11 @@ const onWidthAnchorMounted = (node) => {
322
322
  gap: 0.375rem;
323
323
  justify-content: center;
324
324
  align-items: center;
325
- max-width: 12.5rem;
325
+ max-width: 9.375rem;
326
+ flex: 0 0 auto;
327
+ min-width: auto;
326
328
  width: auto;
327
- min-width: 0;
329
+ white-space: nowrap;
328
330
  border-radius: 0.875rem;
329
331
  background-color: rgba(0, 0, 0, 0.6);
330
332
  color: #f2f2f2;
@@ -372,6 +374,7 @@ const onWidthAnchorMounted = (node) => {
372
374
  width: 100%;
373
375
  white-space: nowrap;
374
376
  overflow: hidden;
377
+ min-width: 0;
375
378
  }
376
379
 
377
380
  .media-center-overlay {
@@ -1,3 +1,4 @@
1
+ import type { TrackingParams } from '../../marketing-tracking';
1
2
  import type { ProductCardModel } from '../../products/product-card';
2
3
  import type { IMediaCenterConfig, ShortVideoPlayerModel } from '../../short-videos/short-videos-player';
3
4
  import type { MediaCenterStreamModel } from './types';
@@ -25,5 +26,6 @@ type ShortVideoSectionItemType = {
25
26
  } | {
26
27
  kind: 'product';
27
28
  data: ProductCardModel;
29
+ trackingParams: TrackingParams;
28
30
  };
29
31
  export {};
@@ -24,7 +24,9 @@ export class DiscoverPanelHandler {
24
24
  result.push({ kind: 'video', data: vids.shift() });
25
25
  }
26
26
  else if (want === 'product' && prods.length > 0) {
27
- result.push({ kind: 'product', data: prods.shift() });
27
+ const productModel = prods.shift();
28
+ const shortVideoId = this._shortVideos.find((v) => v.products.some((p) => p.id === productModel.id))?.id;
29
+ result.push({ kind: 'product', data: productModel, trackingParams: { shortVideoId } });
28
30
  }
29
31
  else {
30
32
  // temporary "empty" position - will fill later
@@ -38,7 +40,9 @@ export class DiscoverPanelHandler {
38
40
  result[i] = { kind: 'video', data: vids.shift() };
39
41
  }
40
42
  else if (prods.length > 0) {
41
- result[i] = { kind: 'product', data: prods.shift() };
43
+ const productModel = prods.shift();
44
+ const shortVideoId = this._shortVideos.find((v) => v.products.some((p) => p.id === productModel.id))?.id;
45
+ result[i] = { kind: 'product', data: productModel, trackingParams: { shortVideoId } };
42
46
  }
43
47
  }
44
48
  }
@@ -36,7 +36,7 @@ let { handler, localization, on } = $props();
36
36
  </div>
37
37
  {:else if item.kind === 'product'}
38
38
  <div class="media-center-overview__card-wrapper" data-theme="dark">
39
- <ProductCard product={item.data} locale={localization.locale} />
39
+ <ProductCard product={item.data} trackingParams={item.trackingParams} locale={localization.locale} />
40
40
  </div>
41
41
  {/if}
42
42
  {/each}
@@ -1,7 +1,17 @@
1
- <script lang="ts">import { AdCard } from '../../ads/ad-card';
1
+ <script lang="ts">import { PostType } from '../..';
2
+ import { AdCard } from '../../ads/ad-card';
2
3
  import { PostModel } from '../model';
3
4
  import { ProductCard } from '../../products/product-card';
4
- let { model, locale = 'en', on } = $props();
5
+ let { model, trackingParams: externalTrackingParams, locale = 'en', on } = $props();
6
+ const trackingParams = $derived.by(() => {
7
+ if (externalTrackingParams || externalTrackingParams === false) {
8
+ return externalTrackingParams;
9
+ }
10
+ if (model.postType === PostType.ShortVideo) {
11
+ return { shortVideoId: model.id };
12
+ }
13
+ return false;
14
+ });
5
15
  </script>
6
16
 
7
17
  {#if model.attachments}
@@ -10,6 +20,7 @@ let { model, locale = 'en', on } = $props();
10
20
  {#each model.attachments.ads as ad (ad.id)}
11
21
  <AdCard
12
22
  ad={ad}
23
+ trackingParams={trackingParams}
13
24
  on={{
14
25
  click: on?.adClick,
15
26
  impression: on?.adImpression
@@ -21,6 +32,7 @@ let { model, locale = 'en', on } = $props();
21
32
  {#each model.attachments.products as product (product.id)}
22
33
  <ProductCard
23
34
  product={product}
35
+ trackingParams={trackingParams}
24
36
  locale={locale}
25
37
  on={{
26
38
  click: on?.productClick,
@@ -1,7 +1,9 @@
1
1
  import type { Locale } from '../../core/locale';
2
+ import type { TrackingParams } from '../../marketing-tracking';
2
3
  import { PostModel } from '../model';
3
4
  type Props = {
4
5
  model: PostModel;
6
+ trackingParams: TrackingParams | null;
5
7
  locale?: Locale;
6
8
  on?: {
7
9
  productClick?: (productId: string) => void;
@@ -1,8 +1,10 @@
1
+ import { PostType } from '../../core/enums';
1
2
  import { PostViewerMediaModel } from './post-media-model.svelte';
2
3
  import type { IPostModel, IPostAdCardModel, IPostHeadingModel, IPostProductCardModel } from './types';
3
4
  export declare class PostModel {
4
5
  id: string;
5
6
  media: PostViewerMediaModel;
7
+ postType: PostType | null;
6
8
  texts: {
7
9
  kicker: string | null;
8
10
  title: string | null;
@@ -1,13 +1,16 @@
1
+ import { PostType } from '../../core/enums';
1
2
  import { PostViewerMediaModel } from './post-media-model.svelte';
2
3
  export class PostModel {
3
4
  id;
4
5
  media;
6
+ postType;
5
7
  texts;
6
8
  heading;
7
9
  enableSocialInteractions;
8
10
  attachments;
9
11
  constructor(init) {
10
12
  this.id = init.id;
13
+ this.postType = init.postType;
11
14
  this.media = new PostViewerMediaModel({ media: init.media, mediaFit: init.mediaFit, mediaIndex: init.mediaIndex });
12
15
  this.texts = {
13
16
  kicker: init.kicker,
@@ -1,6 +1,7 @@
1
- import { AdType, type Currency } from '../../core/enums';
1
+ import { type AdType, type Currency, type PostType } from '../../core/enums';
2
2
  export type IPostModel = {
3
3
  id: string;
4
+ postType: PostType | null;
4
5
  media: IPostMediaItemModel[];
5
6
  mediaFit: PostModelMediaFit;
6
7
  kicker: string | null;
@@ -1 +1 @@
1
- import { AdType } from '../../core/enums';
1
+ import {} from '../../core/enums';
@@ -1,12 +1,13 @@
1
1
  <script lang="ts">import { horizontalWheelScroll, swallowTouch } from '../../core/actions';
2
2
  import { Currency } from '../../core/enums';
3
+ import { enrichAdLinkWithTracking, enrichProductLinkWithTracking } from '../../marketing-tracking';
3
4
  import { PostModel } from '../model';
4
5
  import { toPriceRepresentation } from '../../products/price-helper';
5
6
  import { Icon, IconColor } from '../../ui/icon';
6
7
  import { ImageRounded } from '../../ui/image';
7
8
  import { PostViewerUiManager } from './ui-manager.svelte';
8
9
  import IconTargetArrow from '@fluentui/svg-icons/icons/target_arrow_20_regular.svg?raw';
9
- let { model, uiManager, on } = $props();
10
+ let { model, trackingParams, uiManager, on } = $props();
10
11
  const attachmentsToShow = $derived.by(() => {
11
12
  if (!model.attachments) {
12
13
  return [];
@@ -16,7 +17,7 @@ const attachmentsToShow = $derived.by(() => {
16
17
  .map((p) => ({
17
18
  isAd: false,
18
19
  image: p.image,
19
- link: p.link,
20
+ link: p.link ? enrichProductLinkWithTracking({ link: p.link, productId: p.id, trackingParams }) : null,
20
21
  productId: p.id,
21
22
  adId: null,
22
23
  price: {
@@ -33,7 +34,7 @@ const attachmentsToShow = $derived.by(() => {
33
34
  return ({
34
35
  isAd: true,
35
36
  image: a.image,
36
- link: ((_a = a.ctaButton) === null || _a === void 0 ? void 0 : _a.url) || null,
37
+ link: ((_a = a.ctaButton) === null || _a === void 0 ? void 0 : _a.url) ? enrichAdLinkWithTracking({ link: a.ctaButton.url, adId: a.id, trackingParams }) : null,
37
38
  productId: null,
38
39
  adId: a.id,
39
40
  price: a.price && a.currency
@@ -58,7 +59,7 @@ const handleAttachmentClick = (attachment) => {
58
59
  on.adClick(attachment.adId);
59
60
  }
60
61
  if (attachment.link) {
61
- window.open(attachment.link, '_blank');
62
+ window.open(attachment.link, '_blank', 'noopener noreferrer');
62
63
  }
63
64
  };
64
65
  let attachmentElements = $state({});
@@ -1,7 +1,9 @@
1
+ import { type TrackingParams } from '../../marketing-tracking';
1
2
  import { PostModel } from '../model';
2
3
  import { PostViewerUiManager } from './ui-manager.svelte';
3
4
  type Props = {
4
5
  model: PostModel;
6
+ trackingParams: TrackingParams;
5
7
  uiManager: PostViewerUiManager;
6
8
  on?: {
7
9
  productClick?: (productId: string) => void;
@@ -1,4 +1,5 @@
1
- <script lang="ts">import { PostControls } from '../controls';
1
+ <script lang="ts">import { PostType } from '../..';
2
+ import { PostControls } from '../controls';
2
3
  import { PostModel } from '../model';
3
4
  import { default as AttachmentsHorizontal } from './attachments-horizontal.svelte';
4
5
  import { default as Heading } from './heading.svelte';
@@ -6,13 +7,22 @@ import { default as PostMedia } from './media/post-media.svelte';
6
7
  import { default as Texts } from './post-texts.svelte';
7
8
  import { PostViewerLocalization } from './post-viewer-localization';
8
9
  import { PostViewerUiManager } from './ui-manager.svelte';
9
- let { model, socialInteractionsHandler, enableAttachments = true, enableControls = true, autoplay = 'on-appearance', locale = 'en', on } = $props();
10
+ let { model, socialInteractionsHandler, trackingParams: externalTrackingParams, enableAttachments = true, enableControls = true, autoplay = 'on-appearance', locale = 'en', on } = $props();
10
11
  const localization = $derived(new PostViewerLocalization(locale));
11
12
  const uiManager = new PostViewerUiManager();
12
13
  $effect(() => {
13
14
  uiManager.enableAttachments = enableAttachments;
14
15
  uiManager.enableControls = enableControls;
15
16
  });
17
+ const trackingParams = $derived.by(() => {
18
+ if (externalTrackingParams || externalTrackingParams === false) {
19
+ return externalTrackingParams;
20
+ }
21
+ if (model.postType === PostType.ShortVideo) {
22
+ return { shortVideoId: model.id };
23
+ }
24
+ return false;
25
+ });
16
26
  const viewerMounted = (node) => {
17
27
  const resizeObserver = new ResizeObserver(() => {
18
28
  uiManager.isMobileView = node.clientWidth <= 576;
@@ -56,6 +66,7 @@ const variables = $derived.by(() => {
56
66
  {#if uiManager.showAttachments && model.attachments}
57
67
  <AttachmentsHorizontal
58
68
  model={model}
69
+ trackingParams={trackingParams}
59
70
  uiManager={uiManager}
60
71
  on={{
61
72
  productClick: on?.productClick,
@@ -1,9 +1,11 @@
1
1
  import type { Locale } from '../../core/locale';
2
+ import type { TrackingParams } from '../../marketing-tracking';
2
3
  import { PostModel } from '../model';
3
4
  import type { IPostSocialInteractionsHandler } from '../social-interactions';
4
5
  type Props = {
5
6
  model: PostModel;
6
7
  socialInteractionsHandler?: IPostSocialInteractionsHandler;
8
+ trackingParams: TrackingParams | null | false;
7
9
  enableAttachments?: boolean;
8
10
  enableControls?: boolean;
9
11
  autoplay?: true | false | 'on-appearance';
@@ -1,9 +1,10 @@
1
- import { MediaType } from '../../core/enums';
1
+ import { MediaType, PostType } from '../../core/enums';
2
2
  import { getMediaItemImageUrl } from '../../core/media';
3
3
  import { shouldUseSalePrice } from '../../products/price-helper';
4
4
  export const mapToPostModel = (payload) => {
5
5
  return {
6
6
  id: payload.id,
7
+ postType: mapToPostType(payload.postData),
7
8
  media: mapToPostViewerMediaModel(payload.postData),
8
9
  mediaFit: mediaFitFromPostType(payload.postData),
9
10
  kicker: extractPostKicker(payload.postData),
@@ -22,6 +23,22 @@ export const mapToPostModel = (payload) => {
22
23
  // )
23
24
  };
24
25
  };
26
+ const mapToPostType = (payload) => {
27
+ switch (true) {
28
+ case !!payload.shortVideoData:
29
+ return PostType.ShortVideo;
30
+ case !!payload.momentData:
31
+ return PostType.Moment;
32
+ case !!payload.articleData:
33
+ return PostType.Article;
34
+ case !!payload.videoData:
35
+ return PostType.Video;
36
+ case !!payload.mediaData:
37
+ return PostType.Media;
38
+ default:
39
+ return null;
40
+ }
41
+ };
25
42
  const mediaFitFromPostType = (payload) => {
26
43
  return payload.shortVideoData || payload.momentData ? 'cover' : 'contain';
27
44
  };
@@ -1,9 +1,10 @@
1
- <script lang="ts">import { toPriceRepresentation } from '../price-helper';
1
+ <script lang="ts">import { enrichProductLinkWithTracking } from '../../marketing-tracking';
2
+ import { toPriceRepresentation } from '../price-helper';
2
3
  import { Image } from '../../ui/image';
3
4
  import { LineClamp } from '../../ui/line-clamp';
4
5
  import { ProportionalContainer } from '../../ui/proportional-container';
5
6
  import { ProductCardLocalization } from './product-card-localization';
6
- let { product, includeBeforeNowPrefix, inert = false, locale = 'en', on } = $props();
7
+ let { product, includeBeforeNowPrefix, trackingParams, inert = false, locale = 'en', on } = $props();
7
8
  const localization = $derived(new ProductCardLocalization(locale));
8
9
  const shortDescriptionPresented = $derived(product.shortDescription && product.shortDescription.length > 0);
9
10
  const trackImpression = (node) => {
@@ -25,16 +26,16 @@ const trackImpression = (node) => {
25
26
  };
26
27
  }
27
28
  };
29
+ const enrichedLink = $derived(product.link ? enrichProductLinkWithTracking({ link: product.link, productId: product.id, trackingParams }) : null);
28
30
  const onProductClicked = (event) => {
29
31
  event.preventDefault();
30
32
  event.stopPropagation();
31
33
  if (on === null || on === void 0 ? void 0 : on.click) {
32
34
  on.click(product.id);
33
35
  }
34
- if (!product.link) {
35
- return;
36
+ if (enrichedLink) {
37
+ window.open(enrichedLink, '_blank', 'noopener noreferrer');
36
38
  }
37
- window.open(product.link, '_blank', 'noopener noreferrer');
38
39
  };
39
40
  </script>
40
41
 
@@ -68,8 +69,10 @@ const onProductClicked = (event) => {
68
69
  </div>
69
70
  </div>
70
71
 
71
- {#if product.link || on?.click}
72
- <a href={product.link} onclick={onProductClicked} target="_blank" rel="noopener noreferrer" class="product-card__link" aria-label="none">&nbsp;</a>
72
+ {#if enrichedLink}
73
+ <a href={enrichedLink.href} onclick={onProductClicked} target="_blank" rel="noopener noreferrer" class="product-card__link" aria-label="none">&nbsp;</a>
74
+ {:else if on?.click}
75
+ <button type="button" onclick={onProductClicked} class="product-card__link" aria-label="none">&nbsp;</button>
73
76
  {/if}
74
77
  </div>
75
78
 
@@ -1,10 +1,12 @@
1
1
  import type { Locale } from '../../core/locale';
2
+ import { type TrackingParams } from '../../marketing-tracking';
2
3
  import type { ProductCardModel } from './types';
3
4
  type Props = {
4
5
  product: ProductCardModel;
5
6
  locale?: Locale;
6
7
  includeBeforeNowPrefix?: boolean;
7
8
  inert?: boolean;
9
+ trackingParams: TrackingParams;
8
10
  on?: {
9
11
  click?: (id: string) => void;
10
12
  impression?: (id: string) => void;
@@ -1,3 +1,5 @@
1
1
  import type { ProductCardPayloadFragment } from './operations.generated';
2
2
  import type { ProductCardModel } from './types';
3
- export declare const mapToProductCard: (payload: ProductCardPayloadFragment, referenceDate?: string | null) => ProductCardModel;
3
+ export declare const mapToProductCard: (payload: ProductCardPayloadFragment, options?: Partial<{
4
+ referenceDate: string | null;
5
+ }>) => ProductCardModel;
@@ -1,12 +1,12 @@
1
1
  import { getMediaItemImageUrl } from '../../core/media';
2
2
  import { shouldUseSalePrice } from '../price-helper';
3
- export const mapToProductCard = (payload, referenceDate) => {
3
+ export const mapToProductCard = (payload, options) => {
4
4
  const effectiveSalePrice = payload.priceAndAvailability.productSalePrices?.find((x) => shouldUseSalePrice({
5
5
  price: payload.priceAndAvailability.price,
6
6
  salePrice: x.salePrice,
7
7
  effectiveDateFrom: x.salePriceEffectiveDateFrom,
8
8
  effectiveDateTo: x.salePriceEffectiveDateTo,
9
- referenceDate
9
+ referenceDate: options?.referenceDate
10
10
  }));
11
11
  return {
12
12
  id: payload.id,
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">import { Utils } from '../../core/utils';
2
+ import { enrichProductLinkWithTracking } from '../../marketing-tracking';
2
3
  import { StreamElementView } from './element-views';
3
4
  import { StreamComponentDataType } from './enums';
4
- let { model, locale, on } = $props();
5
+ let { model, locale, trackingParams, on } = $props();
5
6
  const component = $derived.by(() => {
6
7
  return model.components.find((c) => (model.data ? c.dataType === model.data.type : c.dataType === StreamComponentDataType.NoData));
7
8
  });
@@ -27,6 +28,13 @@ const productModel = $derived.by(() => {
27
28
  var _a;
28
29
  return ((_a = model.data) === null || _a === void 0 ? void 0 : _a.type) === StreamComponentDataType.Product ? model.data.product : undefined;
29
30
  });
31
+ const enrichedLink = $derived((productModel === null || productModel === void 0 ? void 0 : productModel.link)
32
+ ? enrichProductLinkWithTracking({
33
+ link: productModel.link,
34
+ productId: productModel.id,
35
+ trackingParams
36
+ })
37
+ : null);
30
38
  const handleProductClick = (e) => {
31
39
  e.preventDefault();
32
40
  if (!productModel) {
@@ -35,8 +43,8 @@ const handleProductClick = (e) => {
35
43
  if (productModel.id && (on === null || on === void 0 ? void 0 : on.productClick)) {
36
44
  on.productClick(productModel.id);
37
45
  }
38
- if (productModel.link) {
39
- window.open(productModel.link, '_blank', 'noopener noreferrer');
46
+ if (enrichedLink) {
47
+ window.open(enrichedLink, '_blank', 'noopener noreferrer');
40
48
  }
41
49
  };
42
50
  const productLinkMounted = (node, productModel) => {
@@ -65,14 +73,14 @@ const productLinkMounted = (node, productModel) => {
65
73
  {#snippet slotContent()}
66
74
  {#if component && (!model.data || dataIsFilled)}
67
75
  {#each component.elements as element (element)}
68
- <StreamElementView model={element} data={model.data} locale={locale} on={on} />
76
+ <StreamElementView model={element} data={model.data} trackingParams={trackingParams} locale={locale} on={on} />
69
77
  {/each}
70
78
  {/if}
71
79
  {/snippet}
72
- {#if productModel?.link}
80
+ {#if productModel && enrichedLink}
73
81
  <a
74
82
  class="stream-slot-content-product-link"
75
- href={productModel.link}
83
+ href={enrichedLink.href}
76
84
  onclick={handleProductClick}
77
85
  target="_blank"
78
86
  rel="noopener noreferrer"
@@ -1,8 +1,10 @@
1
1
  import type { Locale } from '../../core/locale';
2
+ import type { StreamTrackingParams } from './types';
2
3
  import type { StreamSlot } from './slot';
3
4
  type Props = {
4
5
  model: StreamSlot;
5
6
  locale: Locale;
7
+ trackingParams: StreamTrackingParams;
6
8
  on?: {
7
9
  productClick: (productId: string) => void;
8
10
  productImpression?: (productId: string) => void;
@@ -1,11 +1,16 @@
1
1
  <script lang="ts">import { StreamElementStyleDirection } from '../enums';
2
2
  import { generateContainerStyles } from '../styles-transformer';
3
3
  import { default as StreamElement } from './cmp.stream-element.svelte';
4
- let { model, data, locale } = $props();
4
+ let { model, data, trackingParams, locale } = $props();
5
5
  </script>
6
6
 
7
7
  <div class="container-stream-element" style={generateContainerStyles(model.styles)}>
8
8
  {#each model.elements as element (element)}
9
- <StreamElement model={element} data={data} constainerDirection={model.styles?.direction ?? StreamElementStyleDirection.Vertical} locale={locale} />
9
+ <StreamElement
10
+ model={element}
11
+ data={data}
12
+ trackingParams={trackingParams}
13
+ constainerDirection={model.styles?.direction ?? StreamElementStyleDirection.Vertical}
14
+ locale={locale} />
10
15
  {/each}
11
16
  </div>
@@ -1,9 +1,11 @@
1
1
  import type { Locale } from '../../../core/locale';
2
+ import type { StreamTrackingParams } from '..';
2
3
  import type { ContainerStreamElementModel } from '../elements';
3
4
  import type { StreamSlotData } from '../slot-data';
4
5
  type Props = {
5
6
  model: ContainerStreamElementModel;
6
7
  data: StreamSlotData | null;
8
+ trackingParams: StreamTrackingParams;
7
9
  locale: Locale;
8
10
  };
9
11
  declare const Cmp: import("svelte").Component<Props, {}, "">;
@@ -2,8 +2,14 @@
2
2
  import { PostViewer } from '../../../posts/post-viewer';
3
3
  import { ShortVideoStreamElementLocalization } from './short-video-stream-element-localization';
4
4
  import { mapToPostModel } from '../models';
5
- let { data, locale, on } = $props();
5
+ let { data, trackingParams, locale, on } = $props();
6
6
  const localization = $derived(new ShortVideoStreamElementLocalization(locale));
7
7
  </script>
8
8
 
9
- <PostViewer model={new PostModel(mapToPostModel(data))} autoplay={false} enableControls={false} locale={localization.shortVideoViewerLocale} on={on} />
9
+ <PostViewer
10
+ model={new PostModel(mapToPostModel(data))}
11
+ trackingParams={trackingParams ? { streamId: trackingParams.streamId, campaignId: trackingParams.campaignId } : false}
12
+ autoplay={false}
13
+ enableControls={false}
14
+ locale={localization.shortVideoViewerLocale}
15
+ on={on} />
@@ -1,7 +1,9 @@
1
1
  import type { Locale } from '../../../core/locale';
2
+ import type { StreamTrackingParams } from '..';
2
3
  import { type StreamLayoutShortVideoModel } from '../models';
3
4
  type Props = {
4
5
  data: StreamLayoutShortVideoModel;
6
+ trackingParams: StreamTrackingParams;
5
7
  locale: Locale;
6
8
  on?: {
7
9
  progress?: (progress: number) => void;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">import { AnnotationStreamElementView, ContainerStreamElementView, ImageRefStreamElementView, ImagesStreamElementView, PriceStreamElementView, ShortVideoStreamElementView, SpacerStreamElementView, StockStreamElementView, TextRefStreamElementView, TextStreamElementView, WebViewStreamElementView } from '.';
2
2
  import { StreamElementLocalization } from './stream-element-localization';
3
3
  import { StreamComponentDataType, StreamElementStyleDirection, StreamElementType } from '../enums';
4
- let { model, data, constainerDirection = StreamElementStyleDirection.Vertical, locale, on } = $props();
4
+ let { model, data, trackingParams, constainerDirection = StreamElementStyleDirection.Vertical, locale, on } = $props();
5
5
  const localization = $derived(new StreamElementLocalization(locale));
6
6
  const shortVideoModel = $derived.by(() => {
7
7
  if (!data) {
@@ -44,7 +44,7 @@ const productModel = $derived.by(() => {
44
44
  {#if model.type === StreamElementType.Annotation}
45
45
  <AnnotationStreamElementView model={model} />
46
46
  {:else if model.type === StreamElementType.Container}
47
- <ContainerStreamElementView model={model} data={data} locale={locale} />
47
+ <ContainerStreamElementView model={model} data={data} trackingParams={trackingParams} locale={locale} />
48
48
  {:else if model.type === StreamElementType.ImageRef && data}
49
49
  <ImageRefStreamElementView model={model} data={data} />
50
50
  {:else if model.type === StreamElementType.Images && imagesModel?.length}
@@ -54,6 +54,7 @@ const productModel = $derived.by(() => {
54
54
  {:else if model.type === StreamElementType.ShortVideo && shortVideoModel}
55
55
  <ShortVideoStreamElementView
56
56
  data={shortVideoModel}
57
+ trackingParams={trackingParams}
57
58
  locale={localization.shortVideoElementLocalization}
58
59
  on={on
59
60
  ? {
@@ -1,10 +1,12 @@
1
1
  import type { Locale } from '../../../core/locale';
2
+ import type { StreamTrackingParams } from '..';
2
3
  import type { StreamElementModel } from '../elements';
3
4
  import { StreamElementStyleDirection } from '../enums';
4
5
  import type { StreamSlotData } from '../slot-data';
5
6
  type Props = {
6
7
  model: StreamElementModel;
7
8
  data: StreamSlotData | null;
9
+ trackingParams: StreamTrackingParams;
8
10
  constainerDirection?: StreamElementStyleDirection;
9
11
  locale: Locale;
10
12
  on?: {
@@ -7,6 +7,7 @@ export { default as StreamLayoutSlot } from './cmp.slot.svelte';
7
7
  export { default as StreamLayoutSlotContent } from './cmp.slot-content.svelte';
8
8
  export * from './layout';
9
9
  export * from './svg-attributes';
10
+ export type { StreamTrackingParams } from './types';
10
11
  export { parseToStreamLayout, parseToStreamLayoutTemplate, stringifyToStreamLayoutInput, IdPopulator } from './serializer.svelte';
11
12
  export declare const getAllowedDataTypesForSlot: (slot: StreamSlot) => StreamComponentDataType[];
12
13
  export declare const getSingleShortVideoFromLayout: (layout: StreamLayout) => StreamLayoutShortVideoModel | null;
@@ -1,8 +1,9 @@
1
- import { MediaType } from '../../../core/enums';
1
+ import { MediaType, PostType } from '../../../core/enums';
2
2
  import {} from '../../../posts/model';
3
3
  export const mapToPostModel = (model) => {
4
4
  return {
5
5
  id: model.id,
6
+ postType: PostType.ShortVideo,
6
7
  media: [
7
8
  model.media.type === MediaType.Image
8
9
  ? { isImage: true, url: model.media.url }
@@ -79,7 +79,6 @@ export class IdPopulator {
79
79
  }
80
80
  static populateElementId(element) {
81
81
  element.$id = nanoid(10);
82
- // Рекурсивно обрабатываем дочерние элементы
83
82
  if (element.type === StreamElementType.Container && element.elements) {
84
83
  element.elements.forEach((element) => IdPopulator.populateElementId(element));
85
84
  }
@@ -0,0 +1,4 @@
1
+ export type StreamTrackingParams = {
2
+ streamId: string;
3
+ campaignId?: string;
4
+ } | false;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">import { StreamLayoutSlot, StreamPageLayout, StreamLayoutSlotContent } from '../layout';
2
2
  import { StreamPageViewerLocalization } from './stream-page-viewer-localization';
3
- let { page, locale, on } = $props();
3
+ let { page, trackingParams, locale, on } = $props();
4
4
  const localization = $derived(new StreamPageViewerLocalization(locale));
5
5
  </script>
6
6
 
@@ -8,7 +8,7 @@ const localization = $derived(new StreamPageViewerLocalization(locale));
8
8
  <StreamPageLayout model={page.layout}>
9
9
  {#each page.layout.slots as slot (slot)}
10
10
  <StreamLayoutSlot model={slot}>
11
- <StreamLayoutSlotContent model={slot} on={on} locale={localization.elementsLocale} />
11
+ <StreamLayoutSlotContent model={slot} trackingParams={trackingParams} on={on} locale={localization.elementsLocale} />
12
12
  </StreamLayoutSlot>
13
13
  {/each}
14
14
  </StreamPageLayout>
@@ -1,7 +1,9 @@
1
1
  import type { Locale } from '../../core/locale';
2
+ import { type StreamTrackingParams } from '../layout';
2
3
  import type { StreamPageViewerModel } from './types';
3
4
  type Props = {
4
5
  page: StreamPageViewerModel;
6
+ trackingParams: StreamTrackingParams;
5
7
  locale: Locale;
6
8
  on?: {
7
9
  productClick: (productId: string) => void;
@@ -29,9 +29,6 @@ let isActive = true;
29
29
  let activityTimeout = null;
30
30
  let trackingInterval = null;
31
31
  let maxPageIndexViewed = 0;
32
- if (amplificationParameters) {
33
- console.warn(`amplificationParameters: ${JSON.stringify(amplificationParameters)}`);
34
- }
35
32
  $effect(() => {
36
33
  void streamId;
37
34
  untrack(() => {
@@ -51,6 +48,20 @@ $effect(() => {
51
48
  };
52
49
  });
53
50
  $effect(() => contentPlayerConfig.updateMediaCenterData(mediaCenterData));
51
+ $effect(() => contentPlayerConfig.updateTrackingParams(currentStreamModel
52
+ ? {
53
+ streamId: currentStreamModel.id,
54
+ campaignId: amplificationParameters === null || amplificationParameters === void 0 ? void 0 : amplificationParameters.campaignId
55
+ }
56
+ : null));
57
+ const streamTrackingParams = $derived.by(() => {
58
+ return currentStreamModel
59
+ ? {
60
+ streamId: currentStreamModel.id,
61
+ campaignId: amplificationParameters === null || amplificationParameters === void 0 ? void 0 : amplificationParameters.campaignId
62
+ }
63
+ : false;
64
+ });
54
65
  const initNewStream = (streamId) => __awaiter(void 0, void 0, void 0, function* () {
55
66
  var _a, _b;
56
67
  const stream = yield dataProvider.getStream(streamId);
@@ -201,6 +212,7 @@ const stopActivityTracking = () => {
201
212
  {#if item.type === 'general'}
202
213
  <StreamPageViewer
203
214
  page={item}
215
+ trackingParams={streamTrackingParams}
204
216
  locale={localization.locale}
205
217
  on={{
206
218
  progress: (videoId, progress) => onProgress(item.id, videoId, progress),
@@ -317,7 +317,7 @@ const handleSeek = (percent) => {
317
317
  --_video--background-color: var(--video--background-color, #000000);
318
318
  --_video--border-radius: var(--video--border-radius, 0);
319
319
  --_video--media-fit: var(--video--media-fit, contain);
320
- --_video--poster--media-fit: var(--video--poster--media-fit, cover);
320
+ --_video--poster--media-fit: var(--video--poster--media-fit, contain);
321
321
  height: 100%;
322
322
  min-height: 100%;
323
323
  max-height: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/embeddable",
3
- "version": "8.1.0",
3
+ "version": "8.3.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",