@streamscloud/streams-analytics-collector 2.0.0 → 2.0.2
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.
- package/dist/adcampaignsquery.graphql.js +3 -0
- package/dist/analytics/ViewportVisibilityTracker.d.ts +46 -0
- package/dist/analytics/ViewportVisibilityTracker.js +117 -0
- package/dist/analytics/app-events-tracker.d.ts +29 -1
- package/dist/analytics/app-events-tracker.js +51 -4
- package/dist/analytics/index.d.ts +1 -0
- package/dist/analytics/index.js +1 -0
- package/dist/analytics/types.d.ts +3 -1
- package/dist/analytics/types.js +2 -0
- package/dist/analytics.graphql.js +2 -2
- package/dist/articlequery.graphql.js +3 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +1 -0
- package/dist/components/types.d.ts +19 -0
- package/dist/components/types.js +12 -0
- package/dist/components-data/component-data-provider.service.d.ts +3 -0
- package/dist/components-data/component-data-provider.service.js +35 -0
- package/dist/components-data/components-data-fetcher.service.d.ts +5 -0
- package/dist/components-data/components-data-fetcher.service.js +35 -0
- package/dist/components-data/components-data-parameters-reader.service.d.ts +2 -0
- package/dist/components-data/components-data-parameters-reader.service.js +20 -0
- package/dist/components-data/index.d.ts +4 -0
- package/dist/components-data/index.js +3 -0
- package/dist/components-data/types.d.ts +24 -0
- package/dist/components-data/types.js +6 -0
- package/dist/contentlistsquery.graphql.js +3 -0
- package/dist/data-loaders/index.d.ts +2 -0
- package/dist/data-loaders/index.js +2 -0
- package/dist/data-loaders/short-videos-data-loader/index.d.ts +2 -0
- package/dist/data-loaders/short-videos-data-loader/loader.d.ts +4 -0
- package/dist/data-loaders/short-videos-data-loader/loader.js +14 -0
- package/dist/data-loaders/short-videos-data-loader/mapper.d.ts +3 -0
- package/dist/data-loaders/short-videos-data-loader/mapper.js +27 -0
- package/dist/data-loaders/short-videos-data-loader/types.d.ts +14 -0
- package/dist/data-loaders/streams-data-loader/index.d.ts +2 -0
- package/dist/data-loaders/streams-data-loader/loader.d.ts +4 -0
- package/dist/data-loaders/streams-data-loader/loader.js +14 -0
- package/dist/data-loaders/streams-data-loader/mapper.d.ts +3 -0
- package/dist/data-loaders/streams-data-loader/mapper.js +11 -0
- package/dist/data-loaders/streams-data-loader/types.d.ts +4 -0
- package/dist/data-loaders/types.d.ts +3 -0
- package/dist/postsquery.graphql.js +3 -0
- package/dist/services/embed-content-api-client.d.ts +8 -0
- package/dist/services/embed-content-api-client.js +48 -0
- package/dist/services/embed-content-client.service.d.ts +2 -0
- package/dist/services/embed-content-client.service.js +5 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +2 -0
- package/dist/services/operations/posts-query.graphql.js +3 -0
- package/dist/services/operations/streams-query.graphql.js +3 -0
- package/dist/services/types.d.ts +303 -0
- package/dist/streams-api-client-model.d.ts +5 -0
- package/dist/streams-content-api-client.d.ts +14 -0
- package/dist/streams-content-api-client.js +89 -0
- package/package.json +1 -1
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var SiteActiveCampaignsQuery = "query SiteActiveCampaigns($filter: SiteActiveCampaignsFilterInput!, $pageQuery: PaginationOfAdCampaignsOrderByInput!) {\n siteActiveCampaigns(filter: $filter, pagination: $pageQuery) {\n total\n items {\n id\n name\n status\n adGroupIds\n adGroups {\n name\n ads {\n ..._adForGridPayloadFragment\n }\n adPlacement {\n name\n }\n }\n status\n updatedAt\n editorProfile {\n id\n name\n handle\n image\n }\n }\n }\n}\n\nfragment _adForGridPayloadFragment on Ad {\n id\n media {\n type\n url\n thumbnailUrl\n }\n name\n title\n description\n status\n updatedAt\n}\n";
|
|
2
|
+
|
|
3
|
+
export { SiteActiveCampaignsQuery as default };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViewportVisibilityTracker
|
|
3
|
+
*
|
|
4
|
+
* Utility for tracking when DOM elements (e.g., stream tiles) become visible in the viewport.
|
|
5
|
+
* Fires impression events only once per unique element (by streamId) per session/visit.
|
|
6
|
+
* Uses IntersectionObserver for efficient observation, with a fallback to manual visibility checks.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ViewportVisibilityTracker.registerTile(element, streamId, callback?);
|
|
10
|
+
*
|
|
11
|
+
* - The element must have a unique streamId (e.g., from data-stream-id attribute).
|
|
12
|
+
* - The impression event is fired only once per streamId per session.
|
|
13
|
+
* - Handles rapid scrolling, re-renders, and element replacement.
|
|
14
|
+
* - You can provide a custom callback to be called when the element becomes visible.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ViewportVisibilityTracker {
|
|
17
|
+
private static trackedStreamIds;
|
|
18
|
+
private static observedElements;
|
|
19
|
+
private static callbacks;
|
|
20
|
+
private static observer;
|
|
21
|
+
/**
|
|
22
|
+
* Register a tile element for visibility tracking.
|
|
23
|
+
* @param el The DOM element to observe
|
|
24
|
+
* @param streamId The unique stream ID for this tile
|
|
25
|
+
* @param callback Optional callback to call when the element becomes visible (defaults to AppEventsTracker.trackStreamTileImpression)
|
|
26
|
+
*/
|
|
27
|
+
static registerTile(el: HTMLElement, streamId: string, callback?: (streamId: string) => void): void;
|
|
28
|
+
/**
|
|
29
|
+
* Internal: Get or create the IntersectionObserver instance
|
|
30
|
+
*/
|
|
31
|
+
private static getObserver;
|
|
32
|
+
/**
|
|
33
|
+
* Internal: IntersectionObserver callback
|
|
34
|
+
*/
|
|
35
|
+
private static handleIntersections;
|
|
36
|
+
/**
|
|
37
|
+
* Fallback: Check if an element is in the viewport (at least 50% visible)
|
|
38
|
+
* @param el The DOM element
|
|
39
|
+
* @returns true if at least 50% of the element is visible
|
|
40
|
+
*/
|
|
41
|
+
static isElementInViewport(el: HTMLElement): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Reset tracked state (for testing or new session)
|
|
44
|
+
*/
|
|
45
|
+
static reset(): void;
|
|
46
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { AppEventsTracker } from './app-events-tracker.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ViewportVisibilityTracker
|
|
5
|
+
*
|
|
6
|
+
* Utility for tracking when DOM elements (e.g., stream tiles) become visible in the viewport.
|
|
7
|
+
* Fires impression events only once per unique element (by streamId) per session/visit.
|
|
8
|
+
* Uses IntersectionObserver for efficient observation, with a fallback to manual visibility checks.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* ViewportVisibilityTracker.registerTile(element, streamId, callback?);
|
|
12
|
+
*
|
|
13
|
+
* - The element must have a unique streamId (e.g., from data-stream-id attribute).
|
|
14
|
+
* - The impression event is fired only once per streamId per session.
|
|
15
|
+
* - Handles rapid scrolling, re-renders, and element replacement.
|
|
16
|
+
* - You can provide a custom callback to be called when the element becomes visible.
|
|
17
|
+
*/
|
|
18
|
+
class ViewportVisibilityTracker {
|
|
19
|
+
// Set of streamIds that have already been tracked for impression
|
|
20
|
+
static trackedStreamIds = new Set();
|
|
21
|
+
// Map from streamId to the currently observed element
|
|
22
|
+
static observedElements = new Map();
|
|
23
|
+
// Map from streamId to the callback to call when visible
|
|
24
|
+
static callbacks = new Map();
|
|
25
|
+
// Singleton IntersectionObserver instance
|
|
26
|
+
static observer = null;
|
|
27
|
+
/**
|
|
28
|
+
* Register a tile element for visibility tracking.
|
|
29
|
+
* @param el The DOM element to observe
|
|
30
|
+
* @param streamId The unique stream ID for this tile
|
|
31
|
+
* @param callback Optional callback to call when the element becomes visible (defaults to AppEventsTracker.trackStreamTileImpression)
|
|
32
|
+
*/
|
|
33
|
+
static registerTile(el, streamId, callback = AppEventsTracker.trackStreamTileImpression.bind(AppEventsTracker)) {
|
|
34
|
+
if (!el || !streamId)
|
|
35
|
+
return;
|
|
36
|
+
// If already tracked, do nothing
|
|
37
|
+
if (this.trackedStreamIds.has(streamId))
|
|
38
|
+
return;
|
|
39
|
+
// If an element for this streamId is already being observed, unobserve it (handles re-renders)
|
|
40
|
+
const prevEl = this.observedElements.get(streamId);
|
|
41
|
+
if (prevEl && prevEl !== el) {
|
|
42
|
+
this.getObserver().unobserve(prevEl);
|
|
43
|
+
}
|
|
44
|
+
this.observedElements.set(streamId, el);
|
|
45
|
+
this.callbacks.set(streamId, callback);
|
|
46
|
+
this.getObserver().observe(el);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Internal: Get or create the IntersectionObserver instance
|
|
50
|
+
*/
|
|
51
|
+
static getObserver() {
|
|
52
|
+
if (!this.observer) {
|
|
53
|
+
this.observer = new window.IntersectionObserver(this.handleIntersections.bind(this), {
|
|
54
|
+
threshold: 0.5, // At least 50% of the element must be visible
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return this.observer;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Internal: IntersectionObserver callback
|
|
61
|
+
*/
|
|
62
|
+
static handleIntersections(entries) {
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const el = entry.target;
|
|
65
|
+
// Try to get streamId from map or from data attribute
|
|
66
|
+
let streamId = Array.from(this.observedElements.entries()).find(([, v]) => v === el)?.[0];
|
|
67
|
+
if (!streamId) {
|
|
68
|
+
streamId = el.getAttribute('data-stream-id') || undefined;
|
|
69
|
+
}
|
|
70
|
+
if (!streamId)
|
|
71
|
+
continue;
|
|
72
|
+
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
73
|
+
// Only fire once per streamId per session
|
|
74
|
+
if (!this.trackedStreamIds.has(streamId)) {
|
|
75
|
+
this.trackedStreamIds.add(streamId);
|
|
76
|
+
const callback = this.callbacks.get(streamId);
|
|
77
|
+
if (callback) {
|
|
78
|
+
callback(streamId);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Once tracked, stop observing this element
|
|
82
|
+
this.getObserver().unobserve(el);
|
|
83
|
+
this.observedElements.delete(streamId);
|
|
84
|
+
this.callbacks.delete(streamId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Fallback: Check if an element is in the viewport (at least 50% visible)
|
|
90
|
+
* @param el The DOM element
|
|
91
|
+
* @returns true if at least 50% of the element is visible
|
|
92
|
+
*/
|
|
93
|
+
static isElementInViewport(el) {
|
|
94
|
+
if (!el)
|
|
95
|
+
return false;
|
|
96
|
+
const rect = el.getBoundingClientRect();
|
|
97
|
+
const windowHeight = (window.innerHeight || document.documentElement.clientHeight);
|
|
98
|
+
const windowWidth = (window.innerWidth || document.documentElement.clientWidth);
|
|
99
|
+
const vertInView = (rect.top <= windowHeight * 0.5) && ((rect.top + rect.height * 0.5) >= 0);
|
|
100
|
+
const horInView = (rect.left <= windowWidth * 0.5) && ((rect.left + rect.width * 0.5) >= 0);
|
|
101
|
+
return vertInView && horInView;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Reset tracked state (for testing or new session)
|
|
105
|
+
*/
|
|
106
|
+
static reset() {
|
|
107
|
+
this.trackedStreamIds.clear();
|
|
108
|
+
this.observedElements.clear();
|
|
109
|
+
this.callbacks.clear();
|
|
110
|
+
if (this.observer) {
|
|
111
|
+
this.observer.disconnect();
|
|
112
|
+
this.observer = null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { ViewportVisibilityTracker };
|
|
@@ -66,10 +66,19 @@ export declare class AppEventsTracker {
|
|
|
66
66
|
*/
|
|
67
67
|
static trackCommunityMessageOpened(messageId: string, status: string): void;
|
|
68
68
|
/**
|
|
69
|
-
* Track when a stream tile is shown to the user
|
|
69
|
+
* Track when a stream tile is shown to the user (basic, immediate call).
|
|
70
70
|
* @param streamId - The ID of the stream
|
|
71
71
|
*/
|
|
72
72
|
static trackStreamTileImpression(streamId: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Track when a stream tile is shown to the user, using viewport visibility tracking.
|
|
75
|
+
* The impression event will only be fired if the element becomes visible (at least 50% in viewport).
|
|
76
|
+
* The event is only fired once per streamId per session/visit.
|
|
77
|
+
*
|
|
78
|
+
* @param el - The DOM element to observe (e.g., the stream tile)
|
|
79
|
+
* @param streamId - The unique stream ID for this tile
|
|
80
|
+
*/
|
|
81
|
+
static trackStreamTileImpressionWithVisibility(el: HTMLElement, streamId: string): void;
|
|
73
82
|
/**
|
|
74
83
|
* Track when a stream tile is clicked
|
|
75
84
|
* @param streamId - The ID of the stream
|
|
@@ -111,6 +120,25 @@ export declare class AppEventsTracker {
|
|
|
111
120
|
* @deprecated Consider using trackShortVideoProgress and reportPageVideoViews for better tracking of multiple videos
|
|
112
121
|
*/
|
|
113
122
|
static trackClosed(postId: string, streamId: string): void;
|
|
123
|
+
/**
|
|
124
|
+
* Track when an ad is shown to the user
|
|
125
|
+
* @param adId - The ID of the ad
|
|
126
|
+
*/
|
|
127
|
+
static trackAdImpression(adId: string): void;
|
|
128
|
+
/**
|
|
129
|
+
* Track when an ad is clicked by the user
|
|
130
|
+
* @param adId - The ID of the ad
|
|
131
|
+
*/
|
|
132
|
+
static trackAdClick(adId: string): void;
|
|
133
|
+
/**
|
|
134
|
+
* Track when an ad is shown to the user, using viewport visibility tracking.
|
|
135
|
+
* The impression event will only be fired if the element becomes visible (at least 50% in viewport).
|
|
136
|
+
* The event is only fired once per adId per session/visit.
|
|
137
|
+
*
|
|
138
|
+
* @param el - The DOM element to observe (e.g., the ad element)
|
|
139
|
+
* @param adId - The unique ad ID for this element
|
|
140
|
+
*/
|
|
141
|
+
static trackAdImpressionWithVisibility(el: HTMLElement, adId: string): void;
|
|
114
142
|
/**
|
|
115
143
|
* Report an app event to the API
|
|
116
144
|
* @private
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AppEventType, CommunityMessageStatus } from './types.js';
|
|
2
|
-
import
|
|
2
|
+
import ExternalTrackAppEvent from '../analytics.graphql.js';
|
|
3
|
+
import { ViewportVisibilityTracker } from './ViewportVisibilityTracker.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* AppEventsTracker is a utility class for tracking various user events in StreamsCloud applications
|
|
@@ -114,12 +115,28 @@ class AppEventsTracker {
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
/**
|
|
117
|
-
* Track when a stream tile is shown to the user
|
|
118
|
+
* Track when a stream tile is shown to the user (basic, immediate call).
|
|
118
119
|
* @param streamId - The ID of the stream
|
|
119
120
|
*/
|
|
120
121
|
static trackStreamTileImpression(streamId) {
|
|
121
122
|
this.reportAppEvent(streamId, AppEventType.StreamTileImpression);
|
|
122
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Track when a stream tile is shown to the user, using viewport visibility tracking.
|
|
126
|
+
* The impression event will only be fired if the element becomes visible (at least 50% in viewport).
|
|
127
|
+
* The event is only fired once per streamId per session/visit.
|
|
128
|
+
*
|
|
129
|
+
* @param el - The DOM element to observe (e.g., the stream tile)
|
|
130
|
+
* @param streamId - The unique stream ID for this tile
|
|
131
|
+
*/
|
|
132
|
+
static trackStreamTileImpressionWithVisibility(el, streamId) {
|
|
133
|
+
if (!el || !streamId)
|
|
134
|
+
return;
|
|
135
|
+
ViewportVisibilityTracker.registerTile(el, streamId, (sid) => {
|
|
136
|
+
this.reportAppEvent(sid, AppEventType.StreamTileImpression);
|
|
137
|
+
});
|
|
138
|
+
// The ViewportVisibilityTracker will call the callback when visible
|
|
139
|
+
}
|
|
123
140
|
/**
|
|
124
141
|
* Track when a stream tile is clicked
|
|
125
142
|
* @param streamId - The ID of the stream
|
|
@@ -186,6 +203,36 @@ class AppEventsTracker {
|
|
|
186
203
|
this.reported.splice(postIndex, 1);
|
|
187
204
|
}
|
|
188
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Track when an ad is shown to the user
|
|
208
|
+
* @param adId - The ID of the ad
|
|
209
|
+
*/
|
|
210
|
+
static trackAdImpression(adId) {
|
|
211
|
+
this.reportAppEvent(adId, AppEventType.AdImpression);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Track when an ad is clicked by the user
|
|
215
|
+
* @param adId - The ID of the ad
|
|
216
|
+
*/
|
|
217
|
+
static trackAdClick(adId) {
|
|
218
|
+
this.reportAppEvent(adId, AppEventType.AdClick);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Track when an ad is shown to the user, using viewport visibility tracking.
|
|
222
|
+
* The impression event will only be fired if the element becomes visible (at least 50% in viewport).
|
|
223
|
+
* The event is only fired once per adId per session/visit.
|
|
224
|
+
*
|
|
225
|
+
* @param el - The DOM element to observe (e.g., the ad element)
|
|
226
|
+
* @param adId - The unique ad ID for this element
|
|
227
|
+
*/
|
|
228
|
+
static trackAdImpressionWithVisibility(el, adId) {
|
|
229
|
+
if (!el || !adId)
|
|
230
|
+
return;
|
|
231
|
+
ViewportVisibilityTracker.registerTile(el, adId, (aid) => {
|
|
232
|
+
this.reportAppEvent(aid, AppEventType.AdImpression);
|
|
233
|
+
});
|
|
234
|
+
// The ViewportVisibilityTracker will call the callback when visible
|
|
235
|
+
}
|
|
189
236
|
/**
|
|
190
237
|
* Report an app event to the API
|
|
191
238
|
* @private
|
|
@@ -230,7 +277,7 @@ class AppEventsTracker {
|
|
|
230
277
|
return;
|
|
231
278
|
}
|
|
232
279
|
await this.client
|
|
233
|
-
.mutation(
|
|
280
|
+
.mutation(ExternalTrackAppEvent, { input })
|
|
234
281
|
.toPromise();
|
|
235
282
|
}
|
|
236
283
|
else {
|
|
@@ -238,7 +285,7 @@ class AppEventsTracker {
|
|
|
238
285
|
console.warn('An attempt to save app event without initializing GraphQL endpoint detected');
|
|
239
286
|
return;
|
|
240
287
|
}
|
|
241
|
-
await this.queryGql(
|
|
288
|
+
await this.queryGql(ExternalTrackAppEvent, { input });
|
|
242
289
|
}
|
|
243
290
|
}
|
|
244
291
|
static queryGql = async (query, variables) => {
|
package/dist/analytics/index.js
CHANGED
|
@@ -8,7 +8,9 @@ export declare enum AppEventType {
|
|
|
8
8
|
StreamEngagementTime = "STREAM_ENGAGEMENT_TIME",
|
|
9
9
|
StreamScrollDepth = "STREAM_SCROLL_DEPTH",
|
|
10
10
|
StreamPageView = "STREAM_PAGE_VIEW",
|
|
11
|
-
StreamProductClick = "STREAM_PRODUCT_CLICK"
|
|
11
|
+
StreamProductClick = "STREAM_PRODUCT_CLICK",
|
|
12
|
+
AdImpression = "AD_IMPRESSION",
|
|
13
|
+
AdClick = "AD_CLICK"
|
|
12
14
|
}
|
|
13
15
|
export declare enum CommunityMessageStatus {
|
|
14
16
|
Sent = "SENT",
|
package/dist/analytics/types.js
CHANGED
|
@@ -10,6 +10,8 @@ var AppEventType;
|
|
|
10
10
|
AppEventType["StreamScrollDepth"] = "STREAM_SCROLL_DEPTH";
|
|
11
11
|
AppEventType["StreamPageView"] = "STREAM_PAGE_VIEW";
|
|
12
12
|
AppEventType["StreamProductClick"] = "STREAM_PRODUCT_CLICK";
|
|
13
|
+
AppEventType["AdImpression"] = "AD_IMPRESSION";
|
|
14
|
+
AppEventType["AdClick"] = "AD_CLICK";
|
|
13
15
|
})(AppEventType || (AppEventType = {}));
|
|
14
16
|
var CommunityMessageStatus;
|
|
15
17
|
(function (CommunityMessageStatus) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var ExternalTrackAppEvent = "mutation ExternalTrackAppEvent($input: ExternalTrackAppEventInput!) {\n externalTrackAppEvent(input: $input) {\n void\n }\n}\n\nmutation TrackAppEvent($input: TrackAppEventInput!) {\n trackAppEvent(input: $input) {\n void\n }\n}\n";
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { ExternalTrackAppEvent as default };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var SiteArticleQuery = "query SiteArticle($organizationId: String!, $slug: String!) {\n siteArticle(filter: { organizationId: $organizationId, slug: $slug }) {\n ...ArticleFragment\n }\n}\n\nfragment ArticleFragment on Article {\n id\n postId\n slug\n publishedAt\n mainImage(scale: ORIGINAL_ENCODED)\n sections {\n ...ArticleSectionPayloadFragment\n }\n}\n\nfragment ArticleSectionPayloadFragment on ArticleSection {\n id\n isContentEditable\n styles {\n paddingBottom\n paddingTop\n paddingLeft\n paddingRight\n }\n layouts {\n ..._ArticleLayoutPayloadFragment\n }\n}\n\nfragment _ArticleLayoutPayloadFragment on ArticleLayout {\n id\n type\n styles {\n paddingBottom\n paddingTop\n paddingLeft\n paddingRight\n }\n fields {\n ..._ArticleFieldPayloadFragment\n }\n}\n\nfragment _ArticleFieldPayloadFragment on ArticleField {\n id\n name\n description\n fieldData {\n type\n bylineData {\n ..._BylineFieldDataPayloadFragment\n }\n imageData {\n ..._ImageFieldDataPayloadFragment\n }\n mediaData {\n ..._MediaFieldDataPayloadFragment\n }\n mediaGalleryData {\n ..._MediaGalleryFieldDataPayloadFragment\n }\n richTextData {\n ..._RichTextFieldDataPayloadFragment\n }\n textData {\n ..._TextFieldDataPayloadFragment\n }\n videoData {\n ..._VideoFieldDataPayloadFragment\n }\n }\n}\n\nfragment _BylineFieldDataPayloadFragment on BylineFieldData {\n authorName\n}\n\nfragment _ImageFieldDataPayloadFragment on ImageFieldData {\n image {\n ...MediaItemPayloadFragment\n }\n preferredMediaFormat\n}\n\nfragment _MediaFieldDataPayloadFragment on MediaFieldData {\n media {\n ...MediaItemPayloadFragment\n }\n preferredMediaFormat\n}\n\nfragment _MediaGalleryFieldDataPayloadFragment on MediaGalleryFieldData {\n media {\n ...MediaItemPayloadFragment\n }\n galleryMode\n preferredMediaFormat\n}\n\nfragment _RichTextFieldDataPayloadFragment on RichTextFieldData {\n text\n textSize\n}\n\nfragment _TextFieldDataPayloadFragment on TextFieldData {\n text\n textMode\n textSize\n textWeight\n}\n\nfragment _VideoFieldDataPayloadFragment on VideoFieldData {\n video {\n ...MediaItemPayloadFragment\n }\n preferredMediaFormat\n}\n\nfragment MediaItemPayloadFragment on MediaItem {\n url(scale: ORIGINAL_ENCODED)\n thumbnailUrl(scale: ORIGINAL_ENCODED)\n type\n metadata {\n ...MediaItemMetadataPayloadFragment\n }\n}\n\nfragment MediaItemMetadataPayloadFragment on MediaItemMetadata {\n fileName\n width\n height\n durationSec\n}\n";
|
|
2
|
+
|
|
3
|
+
export { SiteArticleQuery as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DataType, ParameterType } from './types.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComponentData } from '../components-data';
|
|
2
|
+
export declare const DataType: {
|
|
3
|
+
Stream: string;
|
|
4
|
+
ShortVideo: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const ParameterType: {
|
|
7
|
+
String: string;
|
|
8
|
+
Text: string;
|
|
9
|
+
Number: string;
|
|
10
|
+
Color: string;
|
|
11
|
+
};
|
|
12
|
+
export type ParameterValue = unknown;
|
|
13
|
+
export type Parameters = {
|
|
14
|
+
[key: string]: ParameterValue;
|
|
15
|
+
};
|
|
16
|
+
export type ComponentProps = {
|
|
17
|
+
parameters?: Parameters;
|
|
18
|
+
data?: ComponentData;
|
|
19
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ComponentData, ComponentDataProvider, ComponentsData, DataParameters } from './types';
|
|
2
|
+
export declare const createComponentDataProvider: (componentsData?: ComponentsData, preserveOriginalData?: boolean) => ComponentDataProvider;
|
|
3
|
+
export declare const getComponentData: (componentsData?: ComponentsData, parameters?: DataParameters) => ComponentData;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const createComponentDataProvider = (componentsData, preserveOriginalData = false) => {
|
|
2
|
+
if (preserveOriginalData) {
|
|
3
|
+
componentsData = structuredClone(componentsData);
|
|
4
|
+
}
|
|
5
|
+
return {
|
|
6
|
+
getData: (parameters) => getComponentData(componentsData, parameters)
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
const getComponentData = (componentsData, parameters) => {
|
|
10
|
+
const result = {};
|
|
11
|
+
if (!componentsData) {
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
if (!parameters) {
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
for (const [dataType, params] of Object.entries(parameters)) {
|
|
18
|
+
if (!params) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const data = componentsData[dataType];
|
|
22
|
+
if (!data) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
result[dataType] = getModels(data, params);
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
const getModels = (content, ids) => ids.length
|
|
30
|
+
? content
|
|
31
|
+
.filter((x) => ids.includes(x.id))
|
|
32
|
+
.sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id))
|
|
33
|
+
: [];
|
|
34
|
+
|
|
35
|
+
export { createComponentDataProvider, getComponentData };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { EmbedContentApiClient } from '../services';
|
|
2
|
+
import { ComponentsData, ComponentsDataParameters } from './types';
|
|
3
|
+
export declare const createComponentsDataFetcher: (client: EmbedContentApiClient) => {
|
|
4
|
+
fetch: (dataParameters: ComponentsDataParameters) => Promise<ComponentsData>;
|
|
5
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getShortVideosDataLoader } from '../data-loaders/short-videos-data-loader/loader.js';
|
|
2
|
+
import { getStreamsDataLoader } from '../data-loaders/streams-data-loader/loader.js';
|
|
3
|
+
import { DataType } from './types.js';
|
|
4
|
+
|
|
5
|
+
const createComponentsDataFetcher = (client) => {
|
|
6
|
+
const loadersMap = new Map([
|
|
7
|
+
[DataType.ShortVideo, getShortVideosDataLoader(client)],
|
|
8
|
+
[DataType.Stream, getStreamsDataLoader(client)]
|
|
9
|
+
]);
|
|
10
|
+
return {
|
|
11
|
+
fetch: async (dataParameters) => {
|
|
12
|
+
const result = {};
|
|
13
|
+
const fetchActions = [];
|
|
14
|
+
for (const [dataType, params] of Object.entries(dataParameters)) {
|
|
15
|
+
if (!params) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const loader = loadersMap.get(dataType);
|
|
19
|
+
if (!loader) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
fetchActions.push((async () => {
|
|
23
|
+
result[dataType] = await loadContent(loader, params);
|
|
24
|
+
})());
|
|
25
|
+
}
|
|
26
|
+
await Promise.all(fetchActions);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const loadContent = async (dataLoader, contentParameters) => {
|
|
32
|
+
return contentParameters.ids.length ? await dataLoader.load(contentParameters.ids) : [];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { createComponentsDataFetcher };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const getComponentsDataParameters = (parameters) => {
|
|
2
|
+
const result = {};
|
|
3
|
+
const params = parameters
|
|
4
|
+
.flatMap((x) => Object.entries(x).filter(([_, v]) => Array.isArray(v)).map(([t, v]) => ({ type: t, value: v })));
|
|
5
|
+
const dataTypes = [...new Set(params.map(x => x.type))];
|
|
6
|
+
for (const dataType of dataTypes) {
|
|
7
|
+
result[dataType] = {
|
|
8
|
+
ids: getIds(params, dataType),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
};
|
|
13
|
+
const getIds = (parameters, dataType) => [
|
|
14
|
+
...new Set(parameters
|
|
15
|
+
.filter((x) => x.type === dataType && typeof Array.isArray(x.value))
|
|
16
|
+
.map((x) => x.value)
|
|
17
|
+
.flatMap((x) => x))
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export { getComponentsDataParameters };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { ComponentDataProvider, ComponentsData, ComponentsDataParameters, ContentParameters, ComponentData, ComponentDataModel, DataParameters } from './types';
|
|
2
|
+
export { createComponentDataProvider } from './component-data-provider.service';
|
|
3
|
+
export { createComponentsDataFetcher } from './components-data-fetcher.service';
|
|
4
|
+
export { getComponentsDataParameters } from './components-data-parameters-reader.service';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ShortVideoModel, StreamModel } from '../data-loaders';
|
|
2
|
+
export declare const DataType: {
|
|
3
|
+
ShortVideo: string;
|
|
4
|
+
Stream: string;
|
|
5
|
+
};
|
|
6
|
+
export type ComponentsDataParameters = {
|
|
7
|
+
[key: string]: ContentParameters | undefined;
|
|
8
|
+
};
|
|
9
|
+
export type ContentParameters = {
|
|
10
|
+
ids: string[];
|
|
11
|
+
};
|
|
12
|
+
export type ComponentsData = {
|
|
13
|
+
[key: string]: ComponentDataModel[];
|
|
14
|
+
};
|
|
15
|
+
export type ComponentDataModel = ShortVideoModel | StreamModel;
|
|
16
|
+
export type DataParameters = {
|
|
17
|
+
[key: string]: string[] | undefined;
|
|
18
|
+
};
|
|
19
|
+
export type ComponentData = {
|
|
20
|
+
[key: string]: ComponentDataModel[] | undefined;
|
|
21
|
+
};
|
|
22
|
+
export type ComponentDataProvider = {
|
|
23
|
+
getData: (parameters?: DataParameters) => ComponentData;
|
|
24
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var SiteContentListsQuery = "query SiteContentLists($cursor: CursorOfSiteContentListsOrderByInput!, $filter: SiteContentListsFilterInput!) {\n siteContentLists(cursor: $cursor, filter: $filter) {\n items {\n ...ContentListFragment\n }\n }\n}\n\nfragment ContentListFragment on ContentList {\n id\n name\n}\n";
|
|
2
|
+
|
|
3
|
+
export { SiteContentListsQuery as default };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { mapToSiteShortVideoModels } from './mapper.js';
|
|
2
|
+
|
|
3
|
+
const getShortVideosDataLoader = (client) => ({
|
|
4
|
+
async load(ids) {
|
|
5
|
+
const posts = await client.getPosts(ids);
|
|
6
|
+
if (!posts) {
|
|
7
|
+
console.error('Failed to fetch site short video posts: Null-payload received', posts);
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return mapToSiteShortVideoModels(posts);
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export { getShortVideosDataLoader };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const mapToSiteShortVideoModels = (posts) => {
|
|
2
|
+
return posts.map((post) => {
|
|
3
|
+
const shortVideo = {
|
|
4
|
+
id: post.id,
|
|
5
|
+
displayDate: post.postHeading.postDisplayDate,
|
|
6
|
+
authorName: post.postHeading.sourceName,
|
|
7
|
+
authorImage: post.postHeading.sourceImage ?? '',
|
|
8
|
+
authorType: post.postHeading.sourceType,
|
|
9
|
+
viewsCount: post.postHeading.postViewsCount,
|
|
10
|
+
reactionsCount: post.social?.reactions.total ?? 0,
|
|
11
|
+
commentsCount: post.social?.commentsCount ?? 0,
|
|
12
|
+
sharesCount: post.social?.sharesCount ?? 0,
|
|
13
|
+
text: post.postData.shortVideoData.text ?? '',
|
|
14
|
+
media: post.postData.media.map((x) => {
|
|
15
|
+
const media = {
|
|
16
|
+
type: x.type,
|
|
17
|
+
url: x.url,
|
|
18
|
+
thumbnailUrl: x.thumbnailUrl
|
|
19
|
+
};
|
|
20
|
+
return media;
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
return shortVideo;
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { mapToSiteShortVideoModels };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Media } from '../../services/types';
|
|
2
|
+
export interface ShortVideoModel {
|
|
3
|
+
id: string;
|
|
4
|
+
text: string;
|
|
5
|
+
authorName: string;
|
|
6
|
+
authorImage: string;
|
|
7
|
+
authorType: string;
|
|
8
|
+
displayDate: string;
|
|
9
|
+
viewsCount: number;
|
|
10
|
+
reactionsCount: number;
|
|
11
|
+
commentsCount: number;
|
|
12
|
+
sharesCount: number;
|
|
13
|
+
media: Media[];
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { mapToStreamModels } from './mapper.js';
|
|
2
|
+
|
|
3
|
+
const getStreamsDataLoader = (client) => ({
|
|
4
|
+
async load(ids) {
|
|
5
|
+
const streams = await client.getStreams(ids);
|
|
6
|
+
if (!streams) {
|
|
7
|
+
console.error('Failed to fetch site short video posts: Null-payload received', streams);
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return mapToStreamModels(streams);
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export { getStreamsDataLoader };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var SitePostsQuery = "query SitePosts($cursor: CursorOfSitePostsOrderByInput!, $filter: SitePostsFilterInput!) {\n sitePosts(cursor: $cursor, filter: $filter) {\n items {\n ...PostItemFragment\n }\n }\n}\n\nfragment PostItemFragment on Post {\n id\n postType\n postData {\n media {\n ...ContentMediaItemPayloadFragment\n ...ContentMediaItemTagsPayloadFragment\n }\n audioData {\n text\n }\n eventData {\n title\n text\n location {\n latitude\n longitude\n name\n }\n startDateTime\n endDateTime\n }\n mediaData {\n text\n }\n momentData {\n text\n }\n linkData {\n text\n linkData {\n ...LinkViewPayloadFragment\n }\n }\n shortVideoData {\n text\n }\n articleData {\n title\n kicker\n article {\n slug\n }\n }\n textData {\n text\n }\n videoData {\n title\n text\n }\n }\n postHeading {\n ...PostHeadingPayloadFragment\n }\n}\n\nfragment ContentMediaItemPayloadFragment on ContentMediaItem {\n url(scale: ORIGINAL_ENCODED)\n thumbnailUrl(scale: ORIGINAL_ENCODED)\n type\n metadata {\n ...MediaItemMetadataPayloadFragment\n }\n}\n\nfragment MediaItemMetadataPayloadFragment on MediaItemMetadata {\n fileName\n width\n height\n durationSec\n}\n\nfragment ContentMediaItemTagsPayloadFragment on ContentMediaItem {\n productTags {\n ...ProductTagPositionPayloadFragment\n }\n userTags {\n ...UserTagPositionPayloadFragment\n }\n mediaPageTags {\n ...MediaPageTagPositionPayloadFragment\n }\n}\n\nfragment ProductTagPositionPayloadFragment on ProductTagPosition {\n itemId\n leftPCT\n topPCT\n product {\n id\n name\n image\n price\n currency\n }\n}\n\nfragment UserTagPositionPayloadFragment on UserTagPosition {\n itemId\n leftPCT\n topPCT\n user {\n id\n name\n image\n username\n }\n}\n\nfragment MediaPageTagPositionPayloadFragment on MediaPageTagPosition {\n itemId\n leftPCT\n topPCT\n mediaPage {\n id\n name\n image\n handle\n }\n}\n\nfragment LinkViewPayloadFragment on LinkData {\n hideImage\n host\n text\n title\n url\n media {\n url\n thumbnailUrl\n type\n }\n}\n\nfragment PostHeadingPayloadFragment on PostHeading {\n sourceId\n sourceName\n sourceImage\n sourceHandle\n sourceType\n postDisplayDate\n postViewsCount\n}\n";
|
|
2
|
+
|
|
3
|
+
export { SitePostsQuery as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Post, Stream } from './types';
|
|
2
|
+
export declare class EmbedContentApiClient {
|
|
3
|
+
private readonly gqlEndpoint;
|
|
4
|
+
constructor(gqlEndpoint: string);
|
|
5
|
+
getPosts(ids: string[]): Promise<Post[] | undefined>;
|
|
6
|
+
getStreams(ids: string[]): Promise<Stream[] | undefined>;
|
|
7
|
+
private queryGql;
|
|
8
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import EmbedPostsQuery from './operations/posts-query.graphql.js';
|
|
2
|
+
import EmbedStreamsQuery from './operations/streams-query.graphql.js';
|
|
3
|
+
|
|
4
|
+
class EmbedContentApiClient {
|
|
5
|
+
gqlEndpoint;
|
|
6
|
+
constructor(gqlEndpoint) {
|
|
7
|
+
this.gqlEndpoint = gqlEndpoint;
|
|
8
|
+
}
|
|
9
|
+
async getPosts(ids) {
|
|
10
|
+
const response = await this.queryGql(EmbedPostsQuery, {
|
|
11
|
+
input: {
|
|
12
|
+
filter: {
|
|
13
|
+
ids: ids
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return response.embedPosts?.items;
|
|
18
|
+
}
|
|
19
|
+
async getStreams(ids) {
|
|
20
|
+
const response = await this.queryGql(EmbedStreamsQuery, {
|
|
21
|
+
input: {
|
|
22
|
+
filter: {
|
|
23
|
+
ids: ids
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return response.embedStreams?.items;
|
|
28
|
+
}
|
|
29
|
+
queryGql = async (query, variables) => {
|
|
30
|
+
const response = await fetch(this.gqlEndpoint, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json'
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
query,
|
|
37
|
+
variables
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
const gql = await response.json();
|
|
41
|
+
if (!gql.data) {
|
|
42
|
+
throw new Error(JSON.stringify(gql.errors));
|
|
43
|
+
}
|
|
44
|
+
return gql.data;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { EmbedContentApiClient };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var EmbedPostsQuery = "query EmbedPosts($input: EmbedPostsInput!) {\n embedPosts(input: $input) {\n items {\n id\n postHeading {\n postDisplayDate\n postViewsCount\n sourceId\n sourceImage\n sourceName\n sourceType\n }\n social {\n commentsCount\n sharesCount\n reactions {\n total\n }\n }\n postData {\n postType\n media {\n thumbnailUrl(scale: ORIGINAL_ENCODED)\n url(scale: ORIGINAL_ENCODED)\n type\n }\n shortVideoData {\n text\n }\n }\n }\n }\n}";
|
|
2
|
+
|
|
3
|
+
export { EmbedPostsQuery as default };
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { MediaItem } from '../types';
|
|
2
|
+
export type EmbedContentApiClientModel = {
|
|
3
|
+
streamsGqlEndpoint: string;
|
|
4
|
+
};
|
|
5
|
+
export type EmbedPostsInput = {
|
|
6
|
+
filter: EmbedPostsFilterInput;
|
|
7
|
+
};
|
|
8
|
+
export type EmbedStreamsInput = {
|
|
9
|
+
filter: EmbedPostsFilterInput;
|
|
10
|
+
};
|
|
11
|
+
export interface Media {
|
|
12
|
+
type: MediaType;
|
|
13
|
+
url: string;
|
|
14
|
+
thumbnailUrl: string | null;
|
|
15
|
+
}
|
|
16
|
+
export type EmbedPostsFilterInput = {
|
|
17
|
+
ids: string[];
|
|
18
|
+
};
|
|
19
|
+
export type EmbedPostsData = {
|
|
20
|
+
embedPosts: CursorResultOfPost;
|
|
21
|
+
};
|
|
22
|
+
export type EmbedStreamsFilterInput = {
|
|
23
|
+
ids: string[];
|
|
24
|
+
};
|
|
25
|
+
export type EmbedStreamsData = {
|
|
26
|
+
embedStreams: CursorResultOfStreams;
|
|
27
|
+
};
|
|
28
|
+
export type GqlResponse<T> = {
|
|
29
|
+
data?: T;
|
|
30
|
+
errors?: unknown;
|
|
31
|
+
};
|
|
32
|
+
export type ContentMediaItem = IMediaItem & {
|
|
33
|
+
blobId: string;
|
|
34
|
+
mediaPageTags?: MediaPageTagPosition[];
|
|
35
|
+
metadata: MediaItemMetadata;
|
|
36
|
+
thumbnailBlobId?: string;
|
|
37
|
+
thumbnailUrl: string | null;
|
|
38
|
+
type: MediaType;
|
|
39
|
+
url: string;
|
|
40
|
+
userTags?: UserTagPosition[];
|
|
41
|
+
};
|
|
42
|
+
export type CtaButton = {
|
|
43
|
+
background: string;
|
|
44
|
+
border: string;
|
|
45
|
+
text: string;
|
|
46
|
+
textColor: string;
|
|
47
|
+
url: string;
|
|
48
|
+
};
|
|
49
|
+
export type IMediaItem = {
|
|
50
|
+
blobId: string;
|
|
51
|
+
metadata: MediaItemMetadata;
|
|
52
|
+
thumbnailBlobId?: string;
|
|
53
|
+
thumbnailUrl?: string;
|
|
54
|
+
type: MediaType;
|
|
55
|
+
url: string;
|
|
56
|
+
};
|
|
57
|
+
export declare enum MediaFormat {
|
|
58
|
+
W4H3 = "W4H3",
|
|
59
|
+
W16H9 = "W16H9"
|
|
60
|
+
}
|
|
61
|
+
export type MediaItemMetadata = {
|
|
62
|
+
durationSec?: number;
|
|
63
|
+
fileName?: string;
|
|
64
|
+
height: number;
|
|
65
|
+
width: number;
|
|
66
|
+
};
|
|
67
|
+
export type MediaPageTag = {
|
|
68
|
+
handle: string;
|
|
69
|
+
id: string;
|
|
70
|
+
image?: string;
|
|
71
|
+
name: string;
|
|
72
|
+
};
|
|
73
|
+
export type MediaPageTagPosition = TagPosition & {
|
|
74
|
+
itemId: string;
|
|
75
|
+
leftPCT: number;
|
|
76
|
+
mediaPage?: MediaPageTag;
|
|
77
|
+
topPCT: number;
|
|
78
|
+
};
|
|
79
|
+
export type MediaPostData = {
|
|
80
|
+
ctaButton?: CtaButton;
|
|
81
|
+
preferredMediaFormat: MediaFormat;
|
|
82
|
+
text?: string;
|
|
83
|
+
};
|
|
84
|
+
export declare enum MediaType {
|
|
85
|
+
Audio = "AUDIO",
|
|
86
|
+
Image = "IMAGE",
|
|
87
|
+
ShortVideo = "SHORT_VIDEO",
|
|
88
|
+
Video = "VIDEO"
|
|
89
|
+
}
|
|
90
|
+
export type MomentInfo = {
|
|
91
|
+
author: Profile;
|
|
92
|
+
cover: string;
|
|
93
|
+
displayDate: Date;
|
|
94
|
+
id: string;
|
|
95
|
+
isSeen: boolean;
|
|
96
|
+
text?: string;
|
|
97
|
+
};
|
|
98
|
+
export declare enum MomentLifetime {
|
|
99
|
+
Day = "DAY",
|
|
100
|
+
Week = "WEEK"
|
|
101
|
+
}
|
|
102
|
+
export type MomentPostData = {
|
|
103
|
+
lifetime: MomentLifetime;
|
|
104
|
+
text?: string;
|
|
105
|
+
};
|
|
106
|
+
export type MomentsContainerInfo = {
|
|
107
|
+
hideIfNoMoments: boolean;
|
|
108
|
+
image?: ScaledImageBlob;
|
|
109
|
+
momentsEnabled: boolean;
|
|
110
|
+
primaryColor?: string;
|
|
111
|
+
targetId: string;
|
|
112
|
+
targetType: PublishTargetType;
|
|
113
|
+
useImageAsMomentsCover: boolean;
|
|
114
|
+
};
|
|
115
|
+
export type MomentsInfo = {
|
|
116
|
+
containerInfo: MomentsContainerInfo;
|
|
117
|
+
moments: MomentInfo[];
|
|
118
|
+
unseenMomentsCount: number;
|
|
119
|
+
};
|
|
120
|
+
export type CursorResultOfStreams = {
|
|
121
|
+
continuationToken?: string;
|
|
122
|
+
items: Stream[];
|
|
123
|
+
};
|
|
124
|
+
export type CursorResultOfPost = {
|
|
125
|
+
continuationToken?: string;
|
|
126
|
+
items: Post[];
|
|
127
|
+
};
|
|
128
|
+
export type Post = {
|
|
129
|
+
authorProfile: Profile;
|
|
130
|
+
createdAt: Date;
|
|
131
|
+
createdBy: string;
|
|
132
|
+
displayDate: Date;
|
|
133
|
+
editorProfile: Profile;
|
|
134
|
+
enableSocialInteractions: boolean;
|
|
135
|
+
expiresOn?: Date;
|
|
136
|
+
generatedName: string;
|
|
137
|
+
id: string;
|
|
138
|
+
isPinned: boolean;
|
|
139
|
+
isSeen: boolean;
|
|
140
|
+
language: string;
|
|
141
|
+
mainImage?: string;
|
|
142
|
+
ownerProfile: Profile;
|
|
143
|
+
postData: PostData;
|
|
144
|
+
postHeading: PostHeading;
|
|
145
|
+
postProfile: Profile;
|
|
146
|
+
postType: PostType;
|
|
147
|
+
publishedTo?: PublishTarget;
|
|
148
|
+
scheduledOn?: Date;
|
|
149
|
+
sharesCount: number;
|
|
150
|
+
showInFeed: boolean;
|
|
151
|
+
social?: PostSocialInteractions;
|
|
152
|
+
status: Status;
|
|
153
|
+
updatedAt: Date;
|
|
154
|
+
updatedBy: string;
|
|
155
|
+
userReaction?: Reaction;
|
|
156
|
+
viewsCount: number;
|
|
157
|
+
};
|
|
158
|
+
export type PostData = {
|
|
159
|
+
media: ContentMediaItem[];
|
|
160
|
+
mediaData?: MediaPostData;
|
|
161
|
+
momentData?: MomentPostData;
|
|
162
|
+
postType: PostType;
|
|
163
|
+
shortVideoData?: ShortVideoPostData;
|
|
164
|
+
textData?: TextPostData;
|
|
165
|
+
videoData?: VideoPostData;
|
|
166
|
+
};
|
|
167
|
+
export type PostHeading = {
|
|
168
|
+
networkData?: PostHeadingNetworkTargetData;
|
|
169
|
+
postDisplayDate: string;
|
|
170
|
+
postViewsCount: number;
|
|
171
|
+
sourceHandle: string;
|
|
172
|
+
sourceId: string;
|
|
173
|
+
sourceImage?: string;
|
|
174
|
+
sourceIsRemoved: boolean;
|
|
175
|
+
sourceName: string;
|
|
176
|
+
sourceType: PostSourceType;
|
|
177
|
+
wasModified: boolean;
|
|
178
|
+
};
|
|
179
|
+
export type PostHeadingNetworkTargetData = {
|
|
180
|
+
momentsInfo: MomentsInfo;
|
|
181
|
+
primaryColor?: string;
|
|
182
|
+
};
|
|
183
|
+
export type PostSocialInteractions = {
|
|
184
|
+
commentsCount: number;
|
|
185
|
+
reactions: ReactionsCount;
|
|
186
|
+
sharesCount: number;
|
|
187
|
+
};
|
|
188
|
+
export declare enum PostSourceType {
|
|
189
|
+
Channel = "CHANNEL",
|
|
190
|
+
Group = "GROUP",
|
|
191
|
+
MediaPage = "MEDIA_PAGE",
|
|
192
|
+
Organization = "ORGANIZATION",
|
|
193
|
+
UserProfile = "USER_PROFILE"
|
|
194
|
+
}
|
|
195
|
+
export declare enum PostType {
|
|
196
|
+
Article = "ARTICLE",
|
|
197
|
+
Audio = "AUDIO",
|
|
198
|
+
Event = "EVENT",
|
|
199
|
+
Link = "LINK",
|
|
200
|
+
Media = "MEDIA",
|
|
201
|
+
Moment = "MOMENT",
|
|
202
|
+
ShortVideo = "SHORT_VIDEO",
|
|
203
|
+
Text = "TEXT",
|
|
204
|
+
Video = "VIDEO"
|
|
205
|
+
}
|
|
206
|
+
export type Profile = {
|
|
207
|
+
__typename?: 'Profile';
|
|
208
|
+
handle: string;
|
|
209
|
+
id: string;
|
|
210
|
+
image?: string;
|
|
211
|
+
isRemoved: boolean;
|
|
212
|
+
name: string;
|
|
213
|
+
type: ProfileType;
|
|
214
|
+
};
|
|
215
|
+
export declare enum ProfileType {
|
|
216
|
+
Channel = "CHANNEL",
|
|
217
|
+
Group = "GROUP",
|
|
218
|
+
Organization = "ORGANIZATION",
|
|
219
|
+
User = "USER"
|
|
220
|
+
}
|
|
221
|
+
export type PublishTarget = {
|
|
222
|
+
enableMoments: boolean;
|
|
223
|
+
enablePostsSocialInteractionsByDefault: boolean;
|
|
224
|
+
id: string;
|
|
225
|
+
image?: string;
|
|
226
|
+
isRemoved: boolean;
|
|
227
|
+
momentsLifetime: MomentLifetime;
|
|
228
|
+
name: string;
|
|
229
|
+
primaryColor?: string;
|
|
230
|
+
targetContentOwnerProfile?: Profile;
|
|
231
|
+
type: PublishTargetType;
|
|
232
|
+
};
|
|
233
|
+
export declare enum PublishTargetType {
|
|
234
|
+
Channel = "CHANNEL",
|
|
235
|
+
Group = "GROUP",
|
|
236
|
+
MediaPage = "MEDIA_PAGE",
|
|
237
|
+
User = "USER"
|
|
238
|
+
}
|
|
239
|
+
export declare enum ReactableType {
|
|
240
|
+
ChatMessage = "CHAT_MESSAGE",
|
|
241
|
+
Comment = "COMMENT",
|
|
242
|
+
CommunityMessage = "COMMUNITY_MESSAGE",
|
|
243
|
+
Post = "POST"
|
|
244
|
+
}
|
|
245
|
+
export type Reaction = {
|
|
246
|
+
code: string;
|
|
247
|
+
id: string;
|
|
248
|
+
reactableId: string;
|
|
249
|
+
reactableType: ReactableType;
|
|
250
|
+
userProfile: Profile;
|
|
251
|
+
};
|
|
252
|
+
export type ReactionCodeCount = {
|
|
253
|
+
code: string;
|
|
254
|
+
count: number;
|
|
255
|
+
};
|
|
256
|
+
export type ReactionsCount = {
|
|
257
|
+
perCode: ReactionCodeCount[];
|
|
258
|
+
total: number;
|
|
259
|
+
};
|
|
260
|
+
export type ScaledImageBlob = {
|
|
261
|
+
id: string;
|
|
262
|
+
url: string;
|
|
263
|
+
};
|
|
264
|
+
export type ShortVideoPostData = {
|
|
265
|
+
text?: string;
|
|
266
|
+
};
|
|
267
|
+
export declare enum Status {
|
|
268
|
+
Archived = "ARCHIVED",
|
|
269
|
+
Draft = "DRAFT",
|
|
270
|
+
Published = "PUBLISHED",
|
|
271
|
+
Scheduled = "SCHEDULED"
|
|
272
|
+
}
|
|
273
|
+
export type Stream = {
|
|
274
|
+
id: string;
|
|
275
|
+
cover: MediaItem;
|
|
276
|
+
};
|
|
277
|
+
export type TagPosition = {
|
|
278
|
+
itemId: string;
|
|
279
|
+
leftPCT: number;
|
|
280
|
+
topPCT: number;
|
|
281
|
+
};
|
|
282
|
+
export type TextPostData = {
|
|
283
|
+
ctaButton?: CtaButton;
|
|
284
|
+
text: string;
|
|
285
|
+
};
|
|
286
|
+
export type UserTag = {
|
|
287
|
+
id: string;
|
|
288
|
+
image: string;
|
|
289
|
+
name: string;
|
|
290
|
+
username?: string;
|
|
291
|
+
};
|
|
292
|
+
export type UserTagPosition = TagPosition & {
|
|
293
|
+
itemId: string;
|
|
294
|
+
leftPCT: number;
|
|
295
|
+
topPCT: number;
|
|
296
|
+
user?: UserTag;
|
|
297
|
+
};
|
|
298
|
+
export type VideoPostData = {
|
|
299
|
+
preferredMediaFormat: MediaFormat;
|
|
300
|
+
text?: string;
|
|
301
|
+
title?: string;
|
|
302
|
+
viewsCount?: number;
|
|
303
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AdCampaign, Article, ContentList, Post } from './types';
|
|
2
|
+
export declare class StreamsContentApiClient {
|
|
3
|
+
private readonly gqlEndpoint;
|
|
4
|
+
private readonly organizationId;
|
|
5
|
+
private readonly siteId;
|
|
6
|
+
private readonly postStatus;
|
|
7
|
+
constructor(gqlEndpoint: string, organizationId: string, siteId: string);
|
|
8
|
+
getArticle(slug: string): Promise<Article | null>;
|
|
9
|
+
getPost(id: string): Promise<Post | undefined>;
|
|
10
|
+
getPosts(ids: string[] | null, types?: string[] | null, contentListId?: string | null): Promise<Post[] | undefined>;
|
|
11
|
+
getContentLists(): Promise<ContentList[] | undefined>;
|
|
12
|
+
getAdCampaigns(page: number, perPage: number): Promise<AdCampaign[] | undefined>;
|
|
13
|
+
private queryGql;
|
|
14
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import SiteActiveCampaignsQuery from './adcampaignsquery.graphql.js';
|
|
2
|
+
import SiteArticleQuery from './articlequery.graphql.js';
|
|
3
|
+
import SiteContentListsQuery from './contentlistsquery.graphql.js';
|
|
4
|
+
import SitePostsQuery from './postsquery.graphql.js';
|
|
5
|
+
import { SitePostsOrderBy, SiteContentListsOrderBy, SiteAdCampaignsOrderBy } from './types.js';
|
|
6
|
+
|
|
7
|
+
class StreamsContentApiClient {
|
|
8
|
+
gqlEndpoint = '{{streamsGqlEndpoint}}';
|
|
9
|
+
organizationId = '{{organizationId}}';
|
|
10
|
+
siteId = '{{siteId}}';
|
|
11
|
+
postStatus = 'PUBLISHED';
|
|
12
|
+
constructor(gqlEndpoint, organizationId, siteId) {
|
|
13
|
+
this.gqlEndpoint = gqlEndpoint;
|
|
14
|
+
this.organizationId = organizationId;
|
|
15
|
+
this.siteId = siteId;
|
|
16
|
+
}
|
|
17
|
+
async getArticle(slug) {
|
|
18
|
+
const response = await this.queryGql(SiteArticleQuery, { organizationId: this.organizationId, slug });
|
|
19
|
+
return response.siteArticle;
|
|
20
|
+
}
|
|
21
|
+
async getPost(id) {
|
|
22
|
+
const response = await this.getPosts([id]);
|
|
23
|
+
return response?.pop();
|
|
24
|
+
}
|
|
25
|
+
async getPosts(ids, types = null, contentListId = null) {
|
|
26
|
+
const response = await this.queryGql(SitePostsQuery, {
|
|
27
|
+
cursor: {
|
|
28
|
+
sorting: {
|
|
29
|
+
orderBy: SitePostsOrderBy.Published
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
filter: {
|
|
33
|
+
ids: ids,
|
|
34
|
+
organizationId: this.organizationId,
|
|
35
|
+
postTypes: types,
|
|
36
|
+
statuses: [this.postStatus],
|
|
37
|
+
contentListId: contentListId
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return response.sitePosts?.items;
|
|
41
|
+
}
|
|
42
|
+
async getContentLists() {
|
|
43
|
+
const response = await this.queryGql(SiteContentListsQuery, {
|
|
44
|
+
cursor: {
|
|
45
|
+
sorting: {
|
|
46
|
+
orderBy: SiteContentListsOrderBy.Name
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
filter: {
|
|
50
|
+
organizationId: this.organizationId
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return response.siteContentLists?.items;
|
|
54
|
+
}
|
|
55
|
+
async getAdCampaigns(page, perPage) {
|
|
56
|
+
const response = await this.queryGql(SiteActiveCampaignsQuery, {
|
|
57
|
+
pageQuery: {
|
|
58
|
+
orderBy: SiteAdCampaignsOrderBy.Modified,
|
|
59
|
+
fetchTotal: false,
|
|
60
|
+
page: page,
|
|
61
|
+
perPage: perPage
|
|
62
|
+
},
|
|
63
|
+
filter: {
|
|
64
|
+
organizationId: this.organizationId,
|
|
65
|
+
siteId: this.siteId
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return response.siteActiveCampaigns?.items;
|
|
69
|
+
}
|
|
70
|
+
queryGql = async (query, variables) => {
|
|
71
|
+
const response = await fetch(this.gqlEndpoint, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json'
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
query,
|
|
78
|
+
variables
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
const gql = await response.json();
|
|
82
|
+
if (!gql.data) {
|
|
83
|
+
throw new Error(JSON.stringify(gql.errors));
|
|
84
|
+
}
|
|
85
|
+
return gql.data;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { StreamsContentApiClient };
|