@streamscloud/embeddable 7.5.2 → 8.0.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 (83) hide show
  1. package/dist/content-player/cmp.content-player.svelte +69 -34
  2. package/dist/content-player/content-player-config.svelte.d.ts +8 -17
  3. package/dist/content-player/content-player-config.svelte.js +16 -15
  4. package/dist/content-player/content-player-settings.d.ts +14 -0
  5. package/dist/content-player/content-player-settings.js +14 -0
  6. package/dist/content-player/controls-and-attachments.svelte +9 -4
  7. package/dist/content-player/header.svelte +1 -1
  8. package/dist/content-player/index.d.ts +1 -1
  9. package/dist/core/analytics.profile-id.js +27 -1
  10. package/dist/core/utils/html-helper.d.ts +1 -0
  11. package/dist/core/utils/html-helper.js +3 -0
  12. package/dist/media-center/media-center/cmp.media-center.svelte +32 -24
  13. package/dist/media-center/media-center/discover-panel.svelte +1 -1
  14. package/dist/posts/attachments/cmp.attachments.svelte +7 -6
  15. package/dist/posts/attachments/cmp.attachments.svelte.d.ts +2 -2
  16. package/dist/posts/controls/cmp.controls.svelte +2 -2
  17. package/dist/posts/controls/cmp.controls.svelte.d.ts +2 -2
  18. package/dist/posts/index.d.ts +1 -0
  19. package/dist/posts/model/index.d.ts +3 -1
  20. package/dist/posts/model/index.js +2 -1
  21. package/dist/posts/model/post-media-model.svelte.d.ts +20 -0
  22. package/dist/posts/model/post-media-model.svelte.js +16 -0
  23. package/dist/posts/model/post-model.d.ts +25 -0
  24. package/dist/posts/model/post-model.js +28 -0
  25. package/dist/posts/model/types.d.ts +53 -9
  26. package/dist/posts/model/types.js +1 -1
  27. package/dist/posts/model/utils.d.ts +4 -0
  28. package/dist/posts/model/utils.js +7 -0
  29. package/dist/posts/post-viewer/attachments-horizontal.svelte +7 -2
  30. package/dist/posts/post-viewer/attachments-horizontal.svelte.d.ts +2 -2
  31. package/dist/posts/post-viewer/cmp.post-viewer.svelte +27 -50
  32. package/dist/posts/post-viewer/cmp.post-viewer.svelte.d.ts +2 -2
  33. package/dist/posts/post-viewer/heading.svelte +2 -1
  34. package/dist/posts/post-viewer/heading.svelte.d.ts +2 -2
  35. package/dist/posts/post-viewer/index.d.ts +1 -3
  36. package/dist/posts/post-viewer/index.js +1 -2
  37. package/dist/posts/post-viewer/mapper.d.ts +2 -2
  38. package/dist/posts/post-viewer/mapper.js +23 -27
  39. package/dist/posts/post-viewer/media/post-media.svelte +64 -0
  40. package/dist/posts/post-viewer/{post-media.svelte.d.ts → media/post-media.svelte.d.ts} +4 -6
  41. package/dist/posts/post-viewer/post-texts.svelte +101 -0
  42. package/dist/posts/post-viewer/post-texts.svelte.d.ts +11 -0
  43. package/dist/posts/post-viewer/post-viewer-localization.d.ts +1 -0
  44. package/dist/posts/post-viewer/post-viewer-localization.js +4 -1
  45. package/dist/posts/posts-player/index.d.ts +2 -3
  46. package/dist/posts/posts-player/index.js +0 -1
  47. package/dist/posts/posts-player/posts-player-view.svelte +3 -2
  48. package/dist/posts/posts-player/types.d.ts +5 -4
  49. package/dist/short-videos/short-videos-player/index.d.ts +3 -4
  50. package/dist/short-videos/short-videos-player/index.js +0 -1
  51. package/dist/short-videos/short-videos-player/mapper.js +2 -2
  52. package/dist/short-videos/short-videos-player/short-videos-player-view.svelte +3 -2
  53. package/dist/short-videos/short-videos-player/types.d.ts +5 -4
  54. package/dist/streams/layout/element-views/cmp.short-video-stream-element.svelte +4 -3
  55. package/dist/streams/layout/models/index.d.ts +1 -1
  56. package/dist/streams/layout/models/index.js +1 -1
  57. package/dist/streams/layout/models/mapper.d.ts +2 -2
  58. package/dist/streams/layout/models/mapper.js +10 -9
  59. package/dist/streams/stream-player/index.d.ts +3 -4
  60. package/dist/streams/stream-player/stream-player-view.svelte +4 -3
  61. package/dist/streams/stream-player/types.d.ts +3 -2
  62. package/dist/ui/line-clamp/cmp.line-clamp.svelte +1 -0
  63. package/dist/ui/player-slider/cmp.player-slider.svelte +44 -7
  64. package/dist/ui/player-slider/cmp.player-slider.svelte.d.ts +2 -2
  65. package/dist/ui/player-slider/player-buffer.svelte.d.ts +4 -3
  66. package/dist/ui/player-slider/player-buffer.svelte.js +9 -3
  67. package/dist/ui/player-slider/types.d.ts +5 -2
  68. package/dist/ui/slider/cmp.slider.svelte +398 -0
  69. package/dist/ui/slider/cmp.slider.svelte.d.ts +31 -0
  70. package/dist/ui/slider/index.d.ts +2 -0
  71. package/dist/ui/slider/index.js +2 -0
  72. package/dist/ui/slider/slider-localization.d.ts +5 -0
  73. package/dist/ui/slider/slider-localization.js +13 -0
  74. package/dist/ui/slider/types.d.ts +11 -0
  75. package/dist/ui/slider/types.js +8 -0
  76. package/package.json +2 -2
  77. package/dist/posts/post-viewer/media/media-slider.svelte +0 -10
  78. package/dist/posts/post-viewer/media/media-slider.svelte.d.ts +0 -27
  79. package/dist/posts/post-viewer/post-media.svelte +0 -26
  80. package/dist/posts/post-viewer/types.d.ts +0 -60
  81. package/dist/posts/post-viewer/types.js +0 -1
  82. package/dist/posts/post-viewer/utils.d.ts +0 -2
  83. package/dist/posts/post-viewer/utils.js +0 -13
@@ -18,7 +18,7 @@ import { default as Header } from './header.svelte';
18
18
  import { default as OverviewPanel } from './overview-panel.svelte';
19
19
  import { onMount, untrack } from 'svelte';
20
20
  let { config, nonPostItemView, overviewPanelContent } = $props();
21
- const localization = new ContentPlayerLocalization(config.locale);
21
+ const localization = new ContentPlayerLocalization(config.settings.locale);
22
22
  let everTouched = $state(false);
23
23
  const uiManager = config.uiManager;
24
24
  onMount(() => __awaiter(void 0, void 0, void 0, function* () {
@@ -48,6 +48,13 @@ const handleContentPlayerMounted = (node) => {
48
48
  node.addEventListener('wheel', markAsTouched);
49
49
  node.addEventListener('click', markAsTouched);
50
50
  node.addEventListener('keypress', markAsTouched);
51
+ return {
52
+ destroy: () => {
53
+ removeListeners();
54
+ }
55
+ };
56
+ };
57
+ const handleSliderMounted = (node) => {
51
58
  let resizeObserver = new ResizeObserver(() => {
52
59
  const { width: playerWidth, height: playerHeight } = node.getBoundingClientRect();
53
60
  uiManager.playerTotalWidth = playerWidth;
@@ -95,7 +102,6 @@ const handleContentPlayerMounted = (node) => {
95
102
  resizeObserver.observe(node);
96
103
  return {
97
104
  destroy: () => {
98
- removeListeners();
99
105
  resizeObserver.disconnect();
100
106
  }
101
107
  };
@@ -115,15 +121,31 @@ const variables = $derived.by(() => {
115
121
 
116
122
  <svelte:document
117
123
  onkeydown={(e) => {
118
- if (config.enableCloseButton) {
124
+ if (config.settings.enableCloseButton) {
119
125
  handleEsc(e, () => config.callbacks?.close?.());
120
126
  }
121
127
  }} />
122
128
 
129
+ <svelte:head>
130
+ <meta name="theme-color" content="#242424" />
131
+ <meta name="color-scheme" content="dark" />
132
+ <meta name="mobile-web-app-capable" content="yes" />
133
+ <meta name="apple-mobile-web-app-capable" content="yes" />
134
+ <meta name="apple-mobile-web-app-status-bar-style" content="black" />
135
+
136
+ <style>
137
+ html,
138
+ body {
139
+ background-color: #242424;
140
+ color-scheme: dark;
141
+ }
142
+ </style>
143
+ </svelte:head>
144
+
123
145
  <div
124
146
  class="content-player-container"
125
- class:content-player-container--background-enabled={!config.disableBackground}
126
- class:content-player-container--background-loading={!config.disableBackground && !uiManager.backgroundImageUrl}
147
+ class:content-player-container--background-enabled={!config.settings.disableBackground}
148
+ class:content-player-container--background-loading={!config.settings.disableBackground && !uiManager.backgroundImageUrl}
127
149
  class:content-player-container--faded={config.fadeContent}
128
150
  style={variables}>
129
151
  <Header config={config} overviewPanelContent={overviewPanelContent} localization={localization} />
@@ -133,7 +155,7 @@ const variables = $derived.by(() => {
133
155
  </OverviewPanel>
134
156
  {/if}
135
157
  <div class="content-player" use:handleContentPlayerMounted>
136
- {#if config.showStreamsCloudWatermark}
158
+ {#if config.settings.showStreamsCloudWatermark}
137
159
  <div class="content-player__watermark">
138
160
  <a href="https://streamscloud.com/" tabindex="-1" aria-label="none">
139
161
  <img src="https://embedabble-assets.streamscloud-cdn.com/watermark.svg" alt="StreamsCloud" />
@@ -141,35 +163,37 @@ const variables = $derived.by(() => {
141
163
  </div>
142
164
  {/if}
143
165
  {#if config.playerBuffer}
144
- <PlayerSlider buffer={config.playerBuffer} on={config.playerSliderCallbacks}>
145
- {#snippet children({ item })}
146
- {@const postModel = config.itemAsPostViewerModel(item)}
147
- <div class="content-player__content">
148
- {#if postModel}
149
- <PostViewer
150
- model={postModel}
151
- socialInteractionsHandler={config.socialInteractionsHandler}
152
- enableAttachments={config.uiManager.showPostOverlayAttachments}
153
- enableControls={config.uiManager.showPostOverlayControls}
154
- autoplay="on-appearance"
155
- locale={config.locale}
156
- on={{
157
- progress: (progress) => config.callbacks?.videoProgress?.(item.id, postModel.id, progress),
158
- productClick: (productId) => config.callbacks?.productClick?.(productId, postModel.id),
159
- productImpression: (productId) => config.callbacks?.productImpression?.(productId, postModel.id),
160
- adClick: (adId) => config.callbacks?.adClick?.(adId, postModel.id),
161
- adImpression: (adId) => config.callbacks?.adImpression?.(adId, postModel.id)
162
- }} />
163
- {:else if nonPostItemView}
164
- {@render nonPostItemView({ item })}
165
- {/if}
166
+ <div class="content-player__slider" use:handleSliderMounted>
167
+ <PlayerSlider buffer={config.playerBuffer} on={config.playerSliderCallbacks}>
168
+ {#snippet children({ item })}
169
+ {@const postModel = config.itemAsPostViewerModel(item)}
170
+ <div class="content-player__content">
171
+ {#if postModel}
172
+ <PostViewer
173
+ model={postModel}
174
+ socialInteractionsHandler={config.socialInteractionsHandler}
175
+ enableAttachments={config.uiManager.showPostOverlayAttachments}
176
+ enableControls={config.uiManager.showPostOverlayControls}
177
+ autoplay="on-appearance"
178
+ locale={config.settings.locale}
179
+ on={{
180
+ progress: (progress) => config.callbacks?.videoProgress?.(item.id, postModel.id, progress),
181
+ productClick: (productId) => config.callbacks?.productClick?.(productId, postModel.id),
182
+ productImpression: (productId) => config.callbacks?.productImpression?.(productId, postModel.id),
183
+ adClick: (adId) => config.callbacks?.adClick?.(adId, postModel.id),
184
+ adImpression: (adId) => config.callbacks?.adImpression?.(adId, postModel.id)
185
+ }} />
186
+ {:else if nonPostItemView}
187
+ {@render nonPostItemView({ item })}
188
+ {/if}
166
189
 
167
- {#if uiManager.isMobileView && config.playerBuffer && config.playerBuffer.loaded.length > 1 && !everTouched}
168
- <SwipeIndicator locale={config.locale} />
169
- {/if}
170
- </div>
171
- {/snippet}
172
- </PlayerSlider>
190
+ {#if uiManager.isMobileView && config.playerBuffer && config.playerBuffer.loaded.length > 1 && !everTouched}
191
+ <SwipeIndicator locale={config.settings.locale} />
192
+ {/if}
193
+ </div>
194
+ {/snippet}
195
+ </PlayerSlider>
196
+ </div>
173
197
  <ControlsAndAttachments config={config} />
174
198
  {:else}
175
199
  <Loading positionFixedCenter={true} timeout={1000} />
@@ -226,6 +250,14 @@ const variables = $derived.by(() => {
226
250
  padding: 0;
227
251
  }
228
252
  }
253
+ .content-player__slider {
254
+ width: 100%;
255
+ min-width: 100%;
256
+ max-width: 100%;
257
+ height: 100%;
258
+ min-height: 100%;
259
+ max-height: 100%;
260
+ }
229
261
  .content-player__watermark {
230
262
  position: absolute;
231
263
  bottom: 5rem;
@@ -240,4 +272,7 @@ const variables = $derived.by(() => {
240
272
  position: relative;
241
273
  opacity: var(--content-player--elements-opacity);
242
274
  transition: opacity 0.3s ease-in-out;
275
+ display: flex;
276
+ justify-content: center;
277
+ align-items: center;
243
278
  }</style>
@@ -1,25 +1,22 @@
1
- import type { Locale } from '../core/locale';
2
1
  import type { MediaCenterData } from '../media-center/model/types';
3
- import type { PostViewerModel } from '../posts/post-viewer';
2
+ import { type IPostModel, PostModel } from '../posts/model';
4
3
  import type { IPostSocialInteractionsHandler } from '../posts/social-interactions';
5
4
  import type { IPlayerBuffer } from '../ui/player-slider';
6
5
  import type { PlayerSliderCallbacks } from '../ui/player-slider/types';
6
+ import { ContentPlayerSettings } from './content-player-settings';
7
7
  import { ContentPlayerUIManager } from './ui-manager.svelte';
8
8
  export declare class ContentPlayerConfig<T extends {
9
9
  id: string;
10
10
  }> {
11
11
  playerBuffer: IPlayerBuffer<T> | null;
12
- readonly locale: Locale;
13
- readonly disableBackground: boolean;
14
- readonly enableCloseButton: boolean;
15
- readonly showStreamsCloudWatermark: boolean;
12
+ readonly settings: ContentPlayerSettings;
16
13
  readonly callbacks: ContentPlayerCallbacks | null;
17
14
  readonly playerSliderCallbacks: PlayerSliderCallbacks<T> | undefined;
18
15
  readonly socialInteractionsHandler: IPostSocialInteractionsHandler | undefined;
19
16
  readonly uiManager: ContentPlayerUIManager;
20
17
  private _mediaCenterData;
21
- private _initialMediaIndex;
22
18
  private _mappers;
19
+ private mappedPostsCache;
23
20
  constructor(init: {
24
21
  playerBuffer: IPlayerBuffer<T> | null;
25
22
  mappers: ContentPlayerMappers<T>;
@@ -33,20 +30,14 @@ export declare class ContentPlayerConfig<T extends {
33
30
  get mediaCenterCallbacks(): MediaCenterData['callbacks'] | null;
34
31
  get playerLogo(): string | null;
35
32
  get fadeContent(): boolean;
36
- get initialMediaIndex(): number;
37
- itemAsPostViewerModel: (item: T) => PostViewerModel | null;
33
+ itemAsPostViewerModel: (item: T & {
34
+ mediaIndex?: number;
35
+ }) => PostModel | null;
38
36
  setBackgroundImageUrl: (imageUrl: string | null) => void;
39
37
  updateMediaCenterData: (data: MediaCenterData | undefined) => void;
40
38
  }
41
- export type ContentPlayerSettings = {
42
- disableBackground?: boolean;
43
- hideCloseButton?: boolean;
44
- locale?: Locale;
45
- showStreamsCloudWatermark?: boolean;
46
- initialMediaIndex?: number;
47
- };
48
39
  export type ContentPlayerMappers<T> = {
49
- postModelFromCurrentItem: (item: T) => PostViewerModel | null;
40
+ postModelFromCurrentItem: (item: T) => IPostModel | null;
50
41
  };
51
42
  export type ContentPlayerCallbacks = {
52
43
  close?: () => void;
@@ -1,24 +1,20 @@
1
+ import { PostModel } from '../posts/model';
2
+ import { ContentPlayerSettings } from './content-player-settings';
1
3
  import { ContentPlayerUIManager } from './ui-manager.svelte';
2
4
  export class ContentPlayerConfig {
3
5
  playerBuffer = $state.raw(null);
4
- locale;
5
- disableBackground;
6
- enableCloseButton;
7
- showStreamsCloudWatermark;
6
+ settings;
8
7
  callbacks;
9
8
  playerSliderCallbacks;
10
9
  socialInteractionsHandler;
11
10
  uiManager = new ContentPlayerUIManager();
12
11
  _mediaCenterData = $state.raw(null);
13
- _initialMediaIndex = 0;
14
12
  _mappers;
13
+ mappedPostsCache = new Map();
15
14
  constructor(init) {
16
15
  const { playerBuffer, mappers, socialInteractionsHandler, mediaCenterData, settings, callbacks, playerSliderCallbacks } = init;
17
16
  this.playerBuffer = playerBuffer;
18
- this.locale = settings?.locale ?? 'en';
19
- this.disableBackground = settings?.disableBackground ?? false;
20
- this.showStreamsCloudWatermark = settings?.showStreamsCloudWatermark ?? false;
21
- this.enableCloseButton = !settings?.hideCloseButton;
17
+ this.settings = settings || new ContentPlayerSettings();
22
18
  this._mediaCenterData = mediaCenterData || null;
23
19
  this.callbacks = callbacks || null;
24
20
  this.playerSliderCallbacks = playerSliderCallbacks;
@@ -37,13 +33,18 @@ export class ContentPlayerConfig {
37
33
  get fadeContent() {
38
34
  return this._mediaCenterData?.overlayIsActive || false;
39
35
  }
40
- get initialMediaIndex() {
41
- const index = this._initialMediaIndex;
42
- this._initialMediaIndex = 0;
43
- return index;
44
- }
45
36
  itemAsPostViewerModel = (item) => {
46
- return this._mappers.postModelFromCurrentItem(item);
37
+ if (this.mappedPostsCache.has(item.id)) {
38
+ return this.mappedPostsCache.get(item.id) || null;
39
+ }
40
+ const post = this._mappers.postModelFromCurrentItem(item);
41
+ if (!post) {
42
+ this.mappedPostsCache.set(item.id, null);
43
+ return null;
44
+ }
45
+ const postModel = new PostModel(post);
46
+ this.mappedPostsCache.set(item.id, postModel);
47
+ return postModel;
47
48
  };
48
49
  setBackgroundImageUrl = (imageUrl) => {
49
50
  this.uiManager.backgroundImageUrl = imageUrl;
@@ -0,0 +1,14 @@
1
+ import type { Locale } from '../core/locale';
2
+ export declare class ContentPlayerSettings {
3
+ disableBackground: boolean;
4
+ enableCloseButton: boolean;
5
+ locale: Locale;
6
+ showStreamsCloudWatermark: boolean;
7
+ constructor(init?: IContentPlayerSettingsInitializer);
8
+ }
9
+ export interface IContentPlayerSettingsInitializer {
10
+ disableBackground?: boolean;
11
+ hideCloseButton?: boolean;
12
+ locale?: Locale;
13
+ showStreamsCloudWatermark?: boolean;
14
+ }
@@ -0,0 +1,14 @@
1
+ export class ContentPlayerSettings {
2
+ disableBackground = false;
3
+ enableCloseButton = true;
4
+ locale = 'en';
5
+ showStreamsCloudWatermark = false;
6
+ constructor(init) {
7
+ if (init) {
8
+ this.disableBackground = init.disableBackground ?? this.disableBackground;
9
+ this.enableCloseButton = init.hideCloseButton !== undefined ? !init.hideCloseButton : this.enableCloseButton;
10
+ this.locale = init.locale ?? this.locale;
11
+ this.showStreamsCloudWatermark = init.showStreamsCloudWatermark ?? this.showStreamsCloudWatermark;
12
+ }
13
+ }
14
+ }
@@ -52,7 +52,9 @@ const variables = $derived.by(() => {
52
52
  on={{ attachmentsClicked: changeShowAttachments }} />
53
53
  {/if}
54
54
  {#if config.playerBuffer}
55
- <div class="controls-and-attachments__navigation-buttons">
55
+ <div
56
+ class="controls-and-attachments__navigation-buttons"
57
+ class:controls-and-attachments__navigation-buttons--invisible={config.playerBuffer.loaded.length <= 1}>
56
58
  <PlayerButton
57
59
  icon={IconChevronUp}
58
60
  disabled={!config.playerBuffer.canLoadPrevious}
@@ -69,11 +71,11 @@ const variables = $derived.by(() => {
69
71
  <img src={config.playerLogo} class="controls-and-attachments__logo-img" alt="Player Logo" />
70
72
  </div>
71
73
  {/if}
72
- {#if currentItemPostContainer && !attachmentsCollapsed && (currentItemPostContainer.products.length || currentItemPostContainer.ads.length)}
74
+ {#if currentItemPostContainer && !attachmentsCollapsed && currentItemPostContainer.attachments}
73
75
  <div class="controls-and-attachments__post-attachments" transition:slideHorizontally|local>
74
76
  <PostAttachments
75
- container={currentItemPostContainer}
76
- locale={config.locale}
77
+ model={currentItemPostContainer}
78
+ locale={config.settings.locale}
77
79
  on={{
78
80
  productClick: (id) => config.callbacks?.productClick?.(id, currentItemPostContainer.id),
79
81
  productImpression: (id) => config.callbacks?.productImpression?.(id, currentItemPostContainer.id),
@@ -186,4 +188,7 @@ const variables = $derived.by(() => {
186
188
  flex-direction: column;
187
189
  gap: 1rem;
188
190
  z-index: 1;
191
+ }
192
+ .controls-and-attachments__navigation-buttons--invisible {
193
+ visibility: hidden;
189
194
  }</style>
@@ -78,7 +78,7 @@ const buttonVariables = $derived.by(() => {
78
78
  </div>
79
79
  {/if}
80
80
 
81
- {#if config.enableCloseButton && config.callbacks?.close}
81
+ {#if config.settings.enableCloseButton && config.callbacks?.close}
82
82
  <div class="close-button" style={buttonVariables}>
83
83
  <PlayerButton icon={IconDismiss} on={{ click: config.callbacks.close }} />
84
84
  </div>
@@ -1,2 +1,2 @@
1
1
  export { default as ContentPlayer } from './cmp.content-player.svelte';
2
- export { ContentPlayerConfig, type ContentPlayerSettings } from './content-player-config.svelte';
2
+ export { ContentPlayerConfig } from './content-player-config.svelte';
@@ -9,9 +9,35 @@ const PROFILE_ID_STORAGE_KEY = 'streamscloud_profile_id';
9
9
  export const getOrCreateProfileId = () => {
10
10
  const storedProfileId = localStorage.getItem(PROFILE_ID_STORAGE_KEY);
11
11
  if (!storedProfileId) {
12
- const newProfileId = crypto.randomUUID();
12
+ const newProfileId = safeRandomUUID();
13
13
  localStorage.setItem(PROFILE_ID_STORAGE_KEY, newProfileId);
14
14
  return newProfileId;
15
15
  }
16
16
  return storedProfileId;
17
17
  };
18
+ function safeRandomUUID() {
19
+ // 1) Native support
20
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
21
+ try {
22
+ return crypto.randomUUID();
23
+ }
24
+ catch {
25
+ // continue to fallbacks
26
+ }
27
+ }
28
+ // 2) Use crypto.getRandomValues to build UUID v4
29
+ if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
30
+ const bytes = new Uint8Array(16);
31
+ crypto.getRandomValues(bytes);
32
+ // Per RFC 4122 v4
33
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
34
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10
35
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0'));
36
+ return [hex.slice(0, 4).join(''), hex.slice(4, 6).join(''), hex.slice(6, 8).join(''), hex.slice(8, 10).join(''), hex.slice(10, 16).join('')].join('-');
37
+ }
38
+ // 3) Non-crypto fallback (not suitable for security-sensitive IDs)
39
+ // Good enough for element keys or local-only IDs
40
+ const time = Date.now().toString(16);
41
+ const rnd = Math.random().toString(16).slice(2);
42
+ return `${time.slice(-8)}-${rnd.slice(0, 4)}-4${rnd.slice(4, 7)}-8${rnd.slice(7, 10)}-${rnd.slice(10, 22)}`;
43
+ }
@@ -36,6 +36,7 @@ export declare class HtmlHelper {
36
36
  * DomHelper.insert(siblingElement, newContentField, false);
37
37
  */
38
38
  static insert(newElement: Element, siblingElement: Element, insertBefore?: boolean): void;
39
+ static sanitizeHtml(html: string): string;
39
40
  static sanitizeSvg(svg: string): string;
40
41
  static pasteIntoInput(value: string, element: HTMLInputElement | HTMLTextAreaElement): void;
41
42
  }
@@ -71,6 +71,9 @@ export class HtmlHelper {
71
71
  HtmlHelper.insertAfter(newElement, siblingElement);
72
72
  }
73
73
  }
74
+ static sanitizeHtml(html) {
75
+ return dp.sanitize(html);
76
+ }
74
77
  static sanitizeSvg(svg) {
75
78
  return dp.sanitize(svg, { USE_PROFILES: { svg: true, svgFilters: true }, ADD_ATTR: ['dominant-baseline'] });
76
79
  }
@@ -7,7 +7,6 @@
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- var _a;
11
10
  import { horizontalWheelScroll } from '../../core/actions';
12
11
  import { Utils } from '../../core/utils';
13
12
  import { default as ShortVideosPlayerView } from '../../short-videos/short-videos-player/short-videos-player-view.svelte';
@@ -29,14 +28,9 @@ import IconScreenSearch from '@fluentui/svg-icons/icons/screen_search_20_regular
29
28
  import { fade } from 'svelte/transition';
30
29
  let { config, playerProps, locale = 'en' } = $props();
31
30
  const localization = $derived(new MediaCenterLocalization(locale));
32
- let initialMediaIndex = ((_a = playerProps.props.playerSettings) === null || _a === void 0 ? void 0 : _a.initialMediaIndex) || 0;
33
- const commonPlayerSettings = () => {
34
- const mediaIndex = initialMediaIndex;
35
- initialMediaIndex = 0; // reset after first use
36
- return {
37
- playerSettings: playerProps.props.playerSettings ? Object.assign(Object.assign({}, playerProps.props.playerSettings), { initialMediaIndex: mediaIndex }) : undefined,
38
- on: playerProps.props.on
39
- };
31
+ const commonPlayerSettings = {
32
+ playerSettings: playerProps.props.playerSettings,
33
+ on: playerProps.props.on
40
34
  };
41
35
  const handler = new MediaCenterHandler(config);
42
36
  const discoverHandler = new DiscoverPanelHandler(config);
@@ -64,7 +58,7 @@ const selectCategory = (categoryId) => __awaiter(void 0, void 0, void 0, functio
64
58
  handler.selectedCategoryId = categoryId;
65
59
  computedPlayerProps = {
66
60
  mode: 'short-videos',
67
- props: Object.assign({ dataProvider: makeShortVideosProvider({ config, categoryId }), socialInteractionsHandler: (_a = config.handlers) === null || _a === void 0 ? void 0 : _a.socialInteractionsHandler, analyticsHandler: (_b = config.handlers) === null || _b === void 0 ? void 0 : _b.analyticsHandler }, commonPlayerSettings())
61
+ props: Object.assign({ dataProvider: makeShortVideosProvider({ config, categoryId }), socialInteractionsHandler: (_a = config.handlers) === null || _a === void 0 ? void 0 : _a.socialInteractionsHandler, analyticsHandler: (_b = config.handlers) === null || _b === void 0 ? void 0 : _b.analyticsHandler }, commonPlayerSettings)
68
62
  };
69
63
  discoverHandler.deactivate();
70
64
  break;
@@ -91,7 +85,7 @@ const activateSelectedShortVideoPlayer = (shortVideo) => {
91
85
  mediaCenterMode = 'short-videos';
92
86
  computedPlayerProps = {
93
87
  mode: 'short-videos',
94
- props: Object.assign({ dataProvider: makeShortVideosProvider({ config, prefetchedItems: [shortVideo] }), socialInteractionsHandler: (_a = config.handlers) === null || _a === void 0 ? void 0 : _a.socialInteractionsHandler, analyticsHandler: (_b = config.handlers) === null || _b === void 0 ? void 0 : _b.analyticsHandler }, commonPlayerSettings())
88
+ props: Object.assign({ dataProvider: makeShortVideosProvider({ config, prefetchedItems: [shortVideo] }), socialInteractionsHandler: (_a = config.handlers) === null || _a === void 0 ? void 0 : _a.socialInteractionsHandler, analyticsHandler: (_b = config.handlers) === null || _b === void 0 ? void 0 : _b.analyticsHandler }, commonPlayerSettings)
95
89
  };
96
90
  handler.selectedCategoryId = null;
97
91
  discoverHandler.deactivate();
@@ -105,7 +99,7 @@ const activateSelectedStreamPlayer = (id) => {
105
99
  mediaCenterMode = 'stream';
106
100
  computedPlayerProps = {
107
101
  mode: 'stream',
108
- props: Object.assign({ streamId: id, dataProvider: config.streamPlayer.streamPlayerDataProvider, analyticsHandler: (_a = config.handlers) === null || _a === void 0 ? void 0 : _a.analyticsHandler, postSocialInteractionsHandler: (_b = config.handlers) === null || _b === void 0 ? void 0 : _b.socialInteractionsHandler }, commonPlayerSettings())
102
+ props: Object.assign({ streamId: id, dataProvider: config.streamPlayer.streamPlayerDataProvider, analyticsHandler: (_a = config.handlers) === null || _a === void 0 ? void 0 : _a.analyticsHandler, postSocialInteractionsHandler: (_b = config.handlers) === null || _b === void 0 ? void 0 : _b.socialInteractionsHandler }, commonPlayerSettings)
109
103
  };
110
104
  handler.selectedCategoryId = null;
111
105
  discoverHandler.deactivate();
@@ -181,7 +175,12 @@ const onWidthAnchorMounted = (node) => {
181
175
  class="media-center-controls-panel__button"
182
176
  class:media-center-controls-panel__button--active={discoverHandler.activated}
183
177
  onclick={toggleDiscover}>
184
- <Icon src={IconScreenSearch} />&nbsp;{localization.discoverButton}
178
+ <span class="media-center-controls-panel__button-icon">
179
+ <Icon src={IconScreenSearch} />
180
+ </span>
181
+ <span class="media-center-controls-panel__button-value">
182
+ {localization.discoverButton}
183
+ </span>
185
184
  </button>
186
185
  <div class="media-center-controls-panel__scroll-area" use:onScrollMounted use:horizontalWheelScroll onscroll={handleScrollingAreaScroll}>
187
186
  {#each handler.categories as category (category.id)}
@@ -194,7 +193,11 @@ const onWidthAnchorMounted = (node) => {
194
193
  handler.controlsPanelSelectedCategory?.childName) ||
195
194
  undefined}
196
195
  title={category.name}
197
- onclick={() => selectCategory(category.id)}>{category.name}</button>
196
+ onclick={() => selectCategory(category.id)}>
197
+ <span class="media-center-controls-panel__button-value">
198
+ {category.name}
199
+ </span>
200
+ </button>
198
201
  {/each}
199
202
  </div>
200
203
  </div>
@@ -310,21 +313,14 @@ const onWidthAnchorMounted = (node) => {
310
313
  .media-center-controls-panel__button {
311
314
  position: relative;
312
315
  pointer-events: auto;
313
- font-size: 0.875rem;
314
- line-height: 1.0625rem;
315
316
  padding: 0.5rem 1.5rem;
316
- white-space: nowrap;
317
- width: auto;
318
- max-width: none;
319
- min-width: 0;
320
- scroll-snap-align: start;
321
- flex: 0 0 auto;
322
317
  display: flex;
323
318
  gap: 0.375rem;
324
319
  justify-content: center;
325
320
  align-items: center;
326
- --icon--color: #ffffff;
327
- --icon--size: 1.0625rem;
321
+ max-width: 12.5rem;
322
+ width: auto;
323
+ min-width: 0;
328
324
  border-radius: 0.875rem;
329
325
  background-color: rgba(0, 0, 0, 0.6);
330
326
  color: #f2f2f2;
@@ -361,6 +357,18 @@ const onWidthAnchorMounted = (node) => {
361
357
  color: #ffffff;
362
358
  pointer-events: none;
363
359
  }
360
+ .media-center-controls-panel__button-icon {
361
+ --icon--color: #ffffff;
362
+ --icon--size: 1.0625rem;
363
+ }
364
+ .media-center-controls-panel__button-value {
365
+ font-size: 0.875rem;
366
+ line-height: 1.0625rem;
367
+ text-overflow: ellipsis;
368
+ width: 100%;
369
+ white-space: nowrap;
370
+ overflow: hidden;
371
+ }
364
372
 
365
373
  .media-center-overlay {
366
374
  position: absolute;
@@ -1,4 +1,4 @@
1
- <script lang="ts">import { getPostCoverImage } from '../../posts/post-viewer';
1
+ <script lang="ts">import { getPostCoverImage } from '../../posts/model';
2
2
  import { ProductCard } from '../../products/product-card';
3
3
  import { ShortVideoCard } from '../../short-videos/short-video-card';
4
4
  import { StreamCard } from '../../streams/stream-card';
@@ -1,12 +1,13 @@
1
1
  <script lang="ts">import { AdCard } from '../../ads/ad-card';
2
+ import { PostModel } from '../model';
2
3
  import { ProductCard } from '../../products/product-card';
3
- let { container, locale = 'en', on } = $props();
4
+ let { model, locale = 'en', on } = $props();
4
5
  </script>
5
6
 
6
- {#if container.ads.length || container.products.length}
7
+ {#if model.attachments}
7
8
  <div class="post-attachments">
8
- {#if container.ads.length}
9
- {#each container.ads as ad (ad.id)}
9
+ {#if model.attachments.ads.length}
10
+ {#each model.attachments.ads as ad (ad.id)}
10
11
  <AdCard
11
12
  ad={ad}
12
13
  on={{
@@ -16,8 +17,8 @@ let { container, locale = 'en', on } = $props();
16
17
  {/each}
17
18
  {/if}
18
19
 
19
- {#if container.products.length}
20
- {#each container.products as product (product.id)}
20
+ {#if model.attachments.products.length}
21
+ {#each model.attachments.products as product (product.id)}
21
22
  <ProductCard
22
23
  product={product}
23
24
  locale={locale}
@@ -1,7 +1,7 @@
1
1
  import type { Locale } from '../../core/locale';
2
- import type { PostAttachmentsContainer } from '../model';
2
+ import { PostModel } from '../model';
3
3
  type Props = {
4
- container: PostAttachmentsContainer;
4
+ model: PostModel;
5
5
  locale?: Locale;
6
6
  on?: {
7
7
  productClick?: (productId: string) => void;
@@ -25,7 +25,7 @@ let isLikedStore = $state.raw({
25
25
  });
26
26
  const actions = $derived.by(() => {
27
27
  const result = [];
28
- if (model.products.length || model.ads.length) {
28
+ if (model.attachments) {
29
29
  result.push({ icon: IconShoppingBag, callback: onAttachmentsClicked });
30
30
  }
31
31
  if (showSocialInteractions) {
@@ -36,7 +36,7 @@ const actions = $derived.by(() => {
36
36
  });
37
37
  result.push({ icon: IconShare, callback: onShareClicked });
38
38
  }
39
- if (model.media && model.media.isMutable) {
39
+ if (model.media && !model.media.currentItem.isImage) {
40
40
  result.push({ icon: MediaVolumeManager.isMuted ? IconSpeakerMute : IconSpeaker2, callback: onMuteClicked });
41
41
  }
42
42
  return result;
@@ -1,7 +1,7 @@
1
- import type { PostContainer } from '../model';
1
+ import type { PostModel } from '../model';
2
2
  import type { IPostSocialInteractionsHandler } from '../social-interactions';
3
3
  type Props = {
4
- model: PostContainer;
4
+ model: PostModel;
5
5
  socialInteractionsHandler?: IPostSocialInteractionsHandler;
6
6
  on?: {
7
7
  attachmentsClicked?: () => void;
@@ -1 +1,2 @@
1
1
  export type { IPostSocialInteractionsHandler } from './social-interactions';
2
+ export type { IPostModel, IPostProductCardModel, IPostHeadingModel, IPostAdCardModel, IPostMediaItemModel } from './model/types';
@@ -1 +1,3 @@
1
- export type { PostContainer, PostAttachmentsContainer } from './types';
1
+ export type { IPostModel, IPostProductCardModel, IPostHeadingModel, IPostAdCardModel, IPostMediaItemModel, PostModelMediaFit } from './types';
2
+ export { PostModel } from './post-model';
3
+ export { getPostCoverImage } from './utils';
@@ -1 +1,2 @@
1
- export {};
1
+ export { PostModel } from './post-model';
2
+ export { getPostCoverImage } from './utils';
@@ -0,0 +1,20 @@
1
+ import type { IPostMediaItemModel, PostModelMediaFit } from './types';
2
+ export declare class PostViewerMediaModel {
3
+ readonly currentItem: {
4
+ isImage: false;
5
+ url: string;
6
+ thumbnailUrl: string;
7
+ } | {
8
+ isImage: boolean;
9
+ url: string;
10
+ };
11
+ currentIndex: number;
12
+ readonly isGallery: boolean;
13
+ items: IPostMediaItemModel[];
14
+ mediaFit: PostModelMediaFit;
15
+ constructor(init: {
16
+ media: IPostMediaItemModel[];
17
+ mediaFit: PostModelMediaFit;
18
+ mediaIndex?: number;
19
+ });
20
+ }