@streamscloud/embeddable 16.0.7-1772112133122 → 16.0.7-1772189105748
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/articles/article/index.d.ts +2 -1
- package/dist/articles/article/types.d.ts +5 -0
- package/dist/articles/article-dialog/article-dialog-localization.d.ts +3 -0
- package/dist/articles/article-dialog/article-dialog-localization.js +9 -0
- package/dist/articles/article-dialog/cmp.article-dialog.svelte +98 -0
- package/dist/articles/article-dialog/cmp.article-dialog.svelte.d.ts +9 -0
- package/dist/articles/article-dialog/index.d.ts +6 -0
- package/dist/articles/article-dialog/index.js +10 -0
- package/dist/articles/article-dialog/types.d.ts +6 -0
- package/dist/articles/article-dialog/types.js +1 -0
- package/dist/articles/data-providers/index.d.ts +1 -0
- package/dist/articles/data-providers/index.js +1 -0
- package/dist/articles/data-providers/types.d.ts +14 -0
- package/dist/articles/data-providers/types.js +1 -0
- package/dist/external-api/article/cmp.embed-article.svelte +8 -27
- package/dist/external-api/data-providers/article/article-data-provider.d.ts +4 -0
- package/dist/external-api/data-providers/article/article-data-provider.js +32 -0
- package/dist/external-api/data-providers/article/index.d.ts +1 -0
- package/dist/external-api/data-providers/article/index.js +1 -0
- package/dist/external-api/{article → data-providers/article}/operations.generated.d.ts +1 -1
- package/dist/external-api/data-providers/internal-media-center-data-provider.svelte.js +3 -1
- package/dist/media-center/config/types.d.ts +3 -0
- package/dist/media-center/media-center/posts-feed/posts-feed-handler.svelte.js +1 -0
- package/dist/posts/model/post-model.d.ts +1 -0
- package/dist/posts/model/post-model.js +2 -0
- package/dist/posts/post-viewer/cmp.post-viewer.svelte +10 -2
- package/dist/posts/post-viewer/cmp.post-viewer.svelte.d.ts +1 -0
- package/dist/posts/post-viewer/post-texts.svelte +8 -1
- package/dist/posts/post-viewer/post-texts.svelte.d.ts +3 -0
- package/dist/posts/posts-player/cmp.posts-player.svelte +4 -1
- package/dist/posts/posts-player/cmp.posts-player.svelte.d.ts +2 -0
- package/dist/posts/posts-player/index.d.ts +8 -1
- package/dist/posts/posts-player/index.js +7 -1
- package/dist/posts/posts-player/posts-player-proxy.svelte +2 -1
- package/dist/posts/posts-player/posts-player-proxy.svelte.d.ts +2 -0
- package/dist/posts/posts-player/posts-player-view.svelte +18 -4
- package/dist/posts/posts-player/types.d.ts +3 -0
- package/dist/ui/shadow-dom/cmp.shadow-root.svelte +1 -0
- package/dist/ui/shadow-dom/index.d.ts +1 -1
- package/dist/ui/shadow-dom/index.js +1 -1
- package/dist/ui/shadow-dom/shadow-root-service.d.ts +1 -0
- package/dist/ui/shadow-dom/shadow-root-service.js +8 -0
- package/package.json +1 -1
- /package/dist/external-api/{article → data-providers/article}/operations.generated.js +0 -0
- /package/dist/external-api/{article → data-providers/article}/operations.graphql +0 -0
|
@@ -3,5 +3,6 @@ export { default as ArticleView } from './article-view.svelte';
|
|
|
3
3
|
export { default as FactsContainer } from './cmp.facts-container.svelte';
|
|
4
4
|
export { fieldIsFilled, layoutIsFilled, sectionIsFilled } from './helpers';
|
|
5
5
|
export { ArticleStylesHelper } from './styles-transformer';
|
|
6
|
-
export type {
|
|
6
|
+
export type { ArticleData, ArticleDataProvider } from '../data-providers';
|
|
7
|
+
export type { ArticleLayoutModel, ArticleLayoutStylesModel, ArticleMetadata, ArticleSectionModel, ArticleSectionStylesModel, ArticleSeo } from './types';
|
|
7
8
|
export type { ArticleFieldDataModel, ArticleFieldModel, ArticleFieldStylesModel, BylineFieldDataModel, FieldMetadata, ImageFieldDataModel, MediaFieldDataModel, MediaGalleryFieldDataModel, RichTextFieldDataModel, TextFieldDataModel, VideoFieldDataModel } from './fields/types';
|
|
@@ -2,6 +2,11 @@ import type { ArticleFieldModel } from './fields/types';
|
|
|
2
2
|
export type ArticleMetadata = {
|
|
3
3
|
displayDate: string;
|
|
4
4
|
};
|
|
5
|
+
export type ArticleSeo = {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
keywords: string[];
|
|
9
|
+
};
|
|
5
10
|
export type ArticleSectionModel = {
|
|
6
11
|
id: string;
|
|
7
12
|
styles: ArticleSectionStylesModel | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AppLocale } from '@streamscloud/kit/core/locale';
|
|
2
|
+
export class ArticleDialogLocalization {
|
|
3
|
+
get failedToLoad() {
|
|
4
|
+
return loc.failedToLoad[AppLocale.current];
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
const loc = {
|
|
8
|
+
failedToLoad: { en: 'Failed to load article', no: 'Kunne ikke laste artikkelen' }
|
|
9
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script lang="ts">import { default as ArticleView } from '../article/article-view.svelte';
|
|
2
|
+
import { ArticleDialogLocalization } from './article-dialog-localization';
|
|
3
|
+
import { DialogCloseButton } from '@streamscloud/kit/ui/dialog';
|
|
4
|
+
import { Loading } from '@streamscloud/kit/ui/loading';
|
|
5
|
+
import { untrack } from 'svelte';
|
|
6
|
+
let { controller, data } = $props();
|
|
7
|
+
const localization = new ArticleDialogLocalization();
|
|
8
|
+
$effect(() => untrack(() => {
|
|
9
|
+
controller.updateSettings({ closeOnEsc: true, closeOnClickOutside: true });
|
|
10
|
+
controller.updateContainerSettings({ position: 'full-screen' });
|
|
11
|
+
}));
|
|
12
|
+
let articleData = $state.raw(null);
|
|
13
|
+
let loading = $state(true);
|
|
14
|
+
let failed = $state(false);
|
|
15
|
+
const load = async () => {
|
|
16
|
+
loading = true;
|
|
17
|
+
failed = false;
|
|
18
|
+
const result = await data.fetchArticle({ id: data.articleId });
|
|
19
|
+
if (result) {
|
|
20
|
+
articleData = result;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
failed = true;
|
|
24
|
+
}
|
|
25
|
+
loading = false;
|
|
26
|
+
};
|
|
27
|
+
void load();
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<div class="article-dialog">
|
|
31
|
+
<div class="article-dialog__close">
|
|
32
|
+
<DialogCloseButton controller={controller} />
|
|
33
|
+
</div>
|
|
34
|
+
<div class="article-dialog__content">
|
|
35
|
+
{#if loading}
|
|
36
|
+
<div class="article-dialog__loading">
|
|
37
|
+
<Loading positionAbsoluteCenter timeout={400} />
|
|
38
|
+
</div>
|
|
39
|
+
{:else if failed}
|
|
40
|
+
<div class="article-dialog__error">{localization.failedToLoad}</div>
|
|
41
|
+
{:else if articleData}
|
|
42
|
+
<ArticleView sections={articleData.sections} metadata={articleData.metadata} />
|
|
43
|
+
{/if}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<style>.article-dialog {
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: 100%;
|
|
50
|
+
position: relative;
|
|
51
|
+
}
|
|
52
|
+
.article-dialog__close {
|
|
53
|
+
--sc-kit--dialog-close-button--color: #fff;
|
|
54
|
+
--sc-kit--icon--filter: drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.2));
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: 0.75rem;
|
|
57
|
+
right: 1rem;
|
|
58
|
+
z-index: 10;
|
|
59
|
+
}
|
|
60
|
+
.article-dialog__content {
|
|
61
|
+
--article--max-width: 56.25rem;
|
|
62
|
+
--article--container-background: light-dark(rgba(255, 255, 255, 0.8), rgba(0, 0, 0, 0.8));
|
|
63
|
+
width: 100%;
|
|
64
|
+
height: 100%;
|
|
65
|
+
overflow-y: auto;
|
|
66
|
+
--_cross-browser-scrollbar--thumb-color: var(--scrollbar--thumb-color, #7d7d7d);
|
|
67
|
+
--_cross-browser-scrollbar--track-color: var(--scrollbar--track-color, transparent);
|
|
68
|
+
}
|
|
69
|
+
.article-dialog__content::-webkit-scrollbar {
|
|
70
|
+
width: 6px;
|
|
71
|
+
height: 6px;
|
|
72
|
+
}
|
|
73
|
+
.article-dialog__content::-webkit-scrollbar-track {
|
|
74
|
+
background: var(--_cross-browser-scrollbar--track-color);
|
|
75
|
+
border-radius: 100vw;
|
|
76
|
+
}
|
|
77
|
+
.article-dialog__content::-webkit-scrollbar-thumb {
|
|
78
|
+
background: var(--_cross-browser-scrollbar--thumb-color);
|
|
79
|
+
border-radius: 100vw;
|
|
80
|
+
}
|
|
81
|
+
@supports (scrollbar-color: transparent transparent) {
|
|
82
|
+
.article-dialog__content {
|
|
83
|
+
scrollbar-color: var(--_cross-browser-scrollbar--thumb-color) var(--_cross-browser-scrollbar--track-color);
|
|
84
|
+
scrollbar-width: thin;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
.article-dialog__loading {
|
|
88
|
+
position: relative;
|
|
89
|
+
min-height: 18.75rem;
|
|
90
|
+
}
|
|
91
|
+
.article-dialog__error {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
min-height: 12.5rem;
|
|
96
|
+
color: light-dark(#9ca3af, #6b7280);
|
|
97
|
+
font-size: 1rem;
|
|
98
|
+
}</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ArticleDialogViewData } from './types';
|
|
2
|
+
import { type DialogController } from '@streamscloud/kit/ui/dialog';
|
|
3
|
+
type Props = {
|
|
4
|
+
controller: DialogController;
|
|
5
|
+
data: ArticleDialogViewData;
|
|
6
|
+
};
|
|
7
|
+
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
9
|
+
export default Cmp;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { default as ArticleDialog } from './cmp.article-dialog.svelte';
|
|
2
|
+
import { Dialogs } from '@streamscloud/kit/ui/dialog';
|
|
3
|
+
export const openArticleDialog = (data) => {
|
|
4
|
+
const { anchor, articleId, fetchArticle } = data;
|
|
5
|
+
void Dialogs.open({
|
|
6
|
+
view: ArticleDialog,
|
|
7
|
+
data: { articleId, fetchArticle },
|
|
8
|
+
host: anchor
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { ArticleData, ArticleDataProvider } from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ArticleMetadata, ArticleSectionModel, ArticleSeo } from '../article';
|
|
2
|
+
/** Article data returned by a data provider. */
|
|
3
|
+
export type ArticleData = {
|
|
4
|
+
sections: ArticleSectionModel[];
|
|
5
|
+
metadata: ArticleMetadata;
|
|
6
|
+
seo: ArticleSeo;
|
|
7
|
+
};
|
|
8
|
+
/** Provider for fetching article content by ID. */
|
|
9
|
+
export type ArticleDataProvider = {
|
|
10
|
+
fetchArticle: (input: {
|
|
11
|
+
id?: string;
|
|
12
|
+
slug?: string;
|
|
13
|
+
}) => Promise<ArticleData | null>;
|
|
14
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
<script lang="ts">import { Article } from '../../articles/article';
|
|
2
2
|
import { createLocalGQLClient } from '../../core/graphql';
|
|
3
|
-
import {
|
|
3
|
+
import { createArticleDataProvider } from '../data-providers/article/article-data-provider';
|
|
4
4
|
import { Loading } from '@streamscloud/kit/ui/loading';
|
|
5
5
|
import { untrack } from 'svelte';
|
|
6
6
|
let { id, slug, initiator, graphqlOrigin, theme, on } = $props();
|
|
7
|
-
let
|
|
8
|
-
let metadata = $state.raw(null);
|
|
7
|
+
let data = $state.raw(null);
|
|
9
8
|
let notFound = $state(false);
|
|
10
|
-
const mapSections = (article) => article.sections.map((section) => ({
|
|
11
|
-
id: section.id,
|
|
12
|
-
facts: section.facts,
|
|
13
|
-
styles: section.styles,
|
|
14
|
-
layouts: section.layouts.map((layout) => ({
|
|
15
|
-
id: layout.id,
|
|
16
|
-
styles: layout.styles,
|
|
17
|
-
fields: layout.fields.map((field) => ({
|
|
18
|
-
id: field.id,
|
|
19
|
-
name: field.name,
|
|
20
|
-
description: field.description,
|
|
21
|
-
styles: field.styles,
|
|
22
|
-
fieldData: field.fieldData
|
|
23
|
-
}))
|
|
24
|
-
}))
|
|
25
|
-
}));
|
|
26
9
|
const extractCoverImageUrl = (article) => {
|
|
27
10
|
for (const section of article.sections) {
|
|
28
11
|
for (const layout of section.layouts) {
|
|
@@ -39,15 +22,13 @@ const extractCoverImageUrl = (article) => {
|
|
|
39
22
|
};
|
|
40
23
|
const load = async () => {
|
|
41
24
|
const graphql = createLocalGQLClient(graphqlOrigin, { initiator });
|
|
42
|
-
const
|
|
43
|
-
|
|
25
|
+
const dataProvider = createArticleDataProvider(graphql);
|
|
26
|
+
data = await dataProvider.fetchArticle({ id, slug });
|
|
27
|
+
if (!data) {
|
|
44
28
|
notFound = true;
|
|
45
29
|
return;
|
|
46
30
|
}
|
|
47
|
-
|
|
48
|
-
sections = mapSections(article);
|
|
49
|
-
metadata = { displayDate: article.publishedAt ?? '' };
|
|
50
|
-
on?.seoLoaded?.({ ...article.seo, imageUrl: extractCoverImageUrl(article) });
|
|
31
|
+
on?.seoLoaded?.({ ...data.seo, imageUrl: extractCoverImageUrl(data) });
|
|
51
32
|
};
|
|
52
33
|
$effect(() => {
|
|
53
34
|
untrack(() => {
|
|
@@ -58,8 +39,8 @@ $effect(() => {
|
|
|
58
39
|
|
|
59
40
|
{#if notFound}
|
|
60
41
|
<div class="embed-article__not-found">¯\_(ツ)_/¯</div>
|
|
61
|
-
{:else if
|
|
62
|
-
<Article sections={sections} metadata={metadata} theme={theme} />
|
|
42
|
+
{:else if data}
|
|
43
|
+
<Article sections={data.sections} metadata={data.metadata} theme={theme} />
|
|
63
44
|
{:else}
|
|
64
45
|
<div class="embed-article__loading">
|
|
65
46
|
<Loading positionAbsoluteCenter timeout={600} />
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ArticleDataProvider } from '../../../articles/data-providers';
|
|
2
|
+
import type { Client } from '@urql/core';
|
|
3
|
+
/** Creates an ArticleDataProvider that fetches articles via the GetEmbedArticle GraphQL query. */
|
|
4
|
+
export declare const createArticleDataProvider: (graphql: Client) => ArticleDataProvider;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { GetEmbedArticleDocument } from './operations.generated';
|
|
2
|
+
/** Creates an ArticleDataProvider that fetches articles via the GetEmbedArticle GraphQL query. */
|
|
3
|
+
export const createArticleDataProvider = (graphql) => ({
|
|
4
|
+
fetchArticle: async (input) => {
|
|
5
|
+
const { id, slug } = input;
|
|
6
|
+
const result = await graphql.query(GetEmbedArticleDocument, { input: { id, slug } }).toPromise();
|
|
7
|
+
const article = result.data?.article;
|
|
8
|
+
if (!article) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
sections: article.sections.map((section) => ({
|
|
13
|
+
id: section.id,
|
|
14
|
+
facts: section.facts,
|
|
15
|
+
styles: section.styles,
|
|
16
|
+
layouts: section.layouts.map((layout) => ({
|
|
17
|
+
id: layout.id,
|
|
18
|
+
styles: layout.styles,
|
|
19
|
+
fields: layout.fields.map((field) => ({
|
|
20
|
+
id: field.id,
|
|
21
|
+
name: field.name,
|
|
22
|
+
description: field.description,
|
|
23
|
+
styles: field.styles,
|
|
24
|
+
fieldData: field.fieldData
|
|
25
|
+
}))
|
|
26
|
+
}))
|
|
27
|
+
})),
|
|
28
|
+
metadata: { displayDate: article.publishedAt ?? '' },
|
|
29
|
+
seo: article.seo
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createArticleDataProvider } from './article-data-provider';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createArticleDataProvider } from './article-data-provider';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type * as SchemaTypes from '
|
|
1
|
+
import type * as SchemaTypes from '../../../../gql/types';
|
|
2
2
|
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
|
3
3
|
export type GetEmbedArticleQueryVariables = SchemaTypes.Exact<{
|
|
4
4
|
input: SchemaTypes.EmbedArticleInput;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createLocalGQLClient } from '../../core/graphql';
|
|
2
|
+
import { createArticleDataProvider } from './article';
|
|
2
3
|
import { InternalMediaCenterAnalyticsHandler } from './internal-media-center-analytics-handler';
|
|
3
4
|
import { MockCategoryFollowingProvider, MockMediaCenterContentManagementHandler, MockMembershipHandler, MockNavigationHandler, MockPostSocialInteractionsHandler } from './mocks';
|
|
4
5
|
import { GetMediaPageConfigDocument } from './operations.generated';
|
|
@@ -57,7 +58,8 @@ export class InternalMediaCenterDataProvider {
|
|
|
57
58
|
limit,
|
|
58
59
|
graphql: this.graphql
|
|
59
60
|
});
|
|
60
|
-
}
|
|
61
|
+
},
|
|
62
|
+
fetchArticle: createArticleDataProvider(this.graphql).fetchArticle
|
|
61
63
|
};
|
|
62
64
|
this.streamPlayer = {
|
|
63
65
|
getStreamsCursor: async ({ filter, limit, continuationToken }) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ArticleDataProvider } from '../../articles/data-providers';
|
|
1
2
|
import type { PostType } from '../../core/enums';
|
|
2
3
|
import type { IContentCategoryFollowingHandler } from '../categories-following';
|
|
3
4
|
import type { IMediaCenterContentManagementHandler } from '../content-management';
|
|
@@ -36,6 +37,8 @@ export interface IMediaCenterDataProvider {
|
|
|
36
37
|
items: PostPlayerModel[];
|
|
37
38
|
continuationToken: string | null;
|
|
38
39
|
}>;
|
|
40
|
+
/** Provider for fetching article content. Enables "Read more" button that opens an article dialog in posts player. */
|
|
41
|
+
fetchArticle?: ArticleDataProvider['fetchArticle'];
|
|
39
42
|
};
|
|
40
43
|
streamPlayer: {
|
|
41
44
|
getStreamsCursor: (input: {
|
|
@@ -175,6 +175,7 @@ export class PostsFeedHandler {
|
|
|
175
175
|
socialInteractionsHandler: this._dataProvider.handlers?.postSocialInteractionsHandler,
|
|
176
176
|
sharingHandler: this._dataProvider.handlers?.postSharingHandler,
|
|
177
177
|
analyticsHandler: this._dataProvider.handlers?.analyticsHandler,
|
|
178
|
+
articleDataProvider: this._dataProvider.postsPlayer.fetchArticle ? { fetchArticle: this._dataProvider.postsPlayer.fetchArticle } : undefined,
|
|
178
179
|
playerSettings: this._mediaCenterSettingsHandler.playerSettings,
|
|
179
180
|
closeOrchestrator: this._closeOrchestrator,
|
|
180
181
|
on: {
|
|
@@ -3,6 +3,7 @@ export class PostModel {
|
|
|
3
3
|
id;
|
|
4
4
|
media;
|
|
5
5
|
postType;
|
|
6
|
+
articleId;
|
|
6
7
|
texts;
|
|
7
8
|
heading;
|
|
8
9
|
enableSocialInteractions;
|
|
@@ -10,6 +11,7 @@ export class PostModel {
|
|
|
10
11
|
constructor(init) {
|
|
11
12
|
this.id = init.id;
|
|
12
13
|
this.postType = init.postType;
|
|
14
|
+
this.articleId = init.articleId;
|
|
13
15
|
this.media = new PostViewerMediaModel({ media: init.media, mediaFit: init.mediaFit, mediaIndex: init.mediaIndex });
|
|
14
16
|
this.texts = {
|
|
15
17
|
kicker: init.kicker,
|
|
@@ -45,6 +45,14 @@ const trackControlsPanelSize = (node) => {
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
|
+
const handleArticleReadMore = $derived.by(() => {
|
|
49
|
+
const articleId = model.articleId;
|
|
50
|
+
const handler = on?.articleReadMore;
|
|
51
|
+
if (!articleId || !handler) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return () => handler(articleId);
|
|
55
|
+
});
|
|
48
56
|
const variables = $derived.by(() => {
|
|
49
57
|
const values = [];
|
|
50
58
|
if (model.media.isGallery) {
|
|
@@ -56,12 +64,12 @@ const variables = $derived.by(() => {
|
|
|
56
64
|
|
|
57
65
|
<div class="post-viewer" style={variables} use:viewerMounted>
|
|
58
66
|
<PostMedia id={model.id} media={model.media} autoplay={autoplay} on={{ videoProgress: on?.progress }} />
|
|
59
|
-
{#if (uiManager.showAttachments && model.attachments) || model.heading || model.texts.kicker || model.texts.title || model.texts.text || model.texts.readMoreUrl}
|
|
67
|
+
{#if (uiManager.showAttachments && model.attachments) || model.heading || model.texts.kicker || model.texts.title || model.texts.text || model.texts.readMoreUrl || handleArticleReadMore}
|
|
60
68
|
<div class="post-viewer__information">
|
|
61
69
|
{#if model.heading}
|
|
62
70
|
<Heading model={model.heading} uiManager={uiManager} localization={localization} />
|
|
63
71
|
{/if}
|
|
64
|
-
<Texts model={model.texts} uiManager={uiManager} localization={localization} />
|
|
72
|
+
<Texts model={model.texts} uiManager={uiManager} localization={localization} on={{ readMore: handleArticleReadMore }} />
|
|
65
73
|
|
|
66
74
|
{#if uiManager.showAttachments && model.attachments}
|
|
67
75
|
<AttachmentsHorizontal
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">import { PostViewerLocalization } from './post-viewer-localization';
|
|
2
2
|
import { PostViewerUiManager } from './ui-manager.svelte';
|
|
3
3
|
import { LineClamp } from '@streamscloud/kit/ui/line-clamp';
|
|
4
|
-
let { model, uiManager, localization } = $props();
|
|
4
|
+
let { model, uiManager, localization, on } = $props();
|
|
5
5
|
const variables = $derived.by(() => {
|
|
6
6
|
const values = [
|
|
7
7
|
`--_post-viewer-texts--padding: 0 ${uiManager.controlsPanelWidth ? uiManager.controlsPanelWidth : uiManager.infoPaddingInline}px 0 ${uiManager.infoPaddingInline}px`
|
|
@@ -30,6 +30,10 @@ const variables = $derived.by(() => {
|
|
|
30
30
|
<a class="post-viewer-texts__read-more" href={model.readMoreUrl} target="_blank" rel="noopener noreferrer">
|
|
31
31
|
{localization.readMore}
|
|
32
32
|
</a>
|
|
33
|
+
{:else if on?.readMore}
|
|
34
|
+
<button type="button" class="post-viewer-texts__read-more" onclick={on.readMore}>
|
|
35
|
+
{localization.readMore}
|
|
36
|
+
</button>
|
|
33
37
|
{/if}
|
|
34
38
|
</div>
|
|
35
39
|
|
|
@@ -83,6 +87,9 @@ const variables = $derived.by(() => {
|
|
|
83
87
|
pointer-events: auto;
|
|
84
88
|
font-size: 1rem;
|
|
85
89
|
font-weight: 400;
|
|
90
|
+
color: inherit;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
text-shadow: inherit;
|
|
86
93
|
/* Set 'container-type: inline-size;' to reference container*/
|
|
87
94
|
}
|
|
88
95
|
@container (width < 576px) {
|
|
@@ -5,6 +5,9 @@ type Props = {
|
|
|
5
5
|
model: PostModel['texts'];
|
|
6
6
|
uiManager: PostViewerUiManager;
|
|
7
7
|
localization: PostViewerLocalization;
|
|
8
|
+
on?: {
|
|
9
|
+
readMore?: () => void;
|
|
10
|
+
};
|
|
8
11
|
};
|
|
9
12
|
declare const PostTexts: import("svelte").Component<Props, {}, "">;
|
|
10
13
|
type PostTexts = ReturnType<typeof PostTexts>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createShadowRoot } from '../../ui/shadow-dom';
|
|
3
3
|
import { default as PostsPlayerProxy } from './posts-player-proxy.svelte';
|
|
4
4
|
import { mount, unmount } from 'svelte';
|
|
5
|
-
let { dataProvider, socialInteractionsHandler, sharingHandler, playerSettings, analyticsHandler, on } = $props();
|
|
5
|
+
let { dataProvider, socialInteractionsHandler, sharingHandler, playerSettings, analyticsHandler, articleDataProvider, on } = $props();
|
|
6
6
|
const initHost = (node) => {
|
|
7
7
|
const shadowRoot = createShadowRoot(node);
|
|
8
8
|
const mounted = mount(PostsPlayerProxy, {
|
|
@@ -23,6 +23,9 @@ const initHost = (node) => {
|
|
|
23
23
|
get analyticsHandler() {
|
|
24
24
|
return analyticsHandler;
|
|
25
25
|
},
|
|
26
|
+
get articleDataProvider() {
|
|
27
|
+
return articleDataProvider;
|
|
28
|
+
},
|
|
26
29
|
closeOrchestrator: new CloseOrchestrator({
|
|
27
30
|
closeFn: async () => {
|
|
28
31
|
await unmount(mounted);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ArticleDataProvider } from '../../articles/data-providers';
|
|
1
2
|
import type { IPostSocialInteractionsHandler } from '..';
|
|
2
3
|
import type { IPostSharingHandler } from '../sharing';
|
|
3
4
|
import type { IPostAnalyticsHandler, PostPlayerModel } from './types';
|
|
@@ -8,6 +9,7 @@ type Props = {
|
|
|
8
9
|
socialInteractionsHandler?: IPostSocialInteractionsHandler;
|
|
9
10
|
sharingHandler?: IPostSharingHandler;
|
|
10
11
|
analyticsHandler?: IPostAnalyticsHandler;
|
|
12
|
+
articleDataProvider?: ArticleDataProvider;
|
|
11
13
|
playerSettings?: {
|
|
12
14
|
locale?: AppLocaleValue;
|
|
13
15
|
showStreamsCloudWatermark?: boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ArticleDataProvider } from '../../articles/data-providers';
|
|
1
2
|
import { type IMediaCenterDataProvider } from '../../media-center/config/types';
|
|
2
3
|
import type { IPostSharingHandler } from '../sharing';
|
|
3
4
|
import type { IPostSocialInteractionsHandler } from '../social-interactions';
|
|
@@ -5,7 +6,7 @@ import { default as PostsPlayer } from './cmp.posts-player.svelte';
|
|
|
5
6
|
import type { IPostAnalyticsHandler, PostPlayerModel } from './types';
|
|
6
7
|
import type { AppLocaleValue } from '@streamscloud/kit/core/locale';
|
|
7
8
|
export { PostsPlayer, type PostPlayerModel };
|
|
8
|
-
export type { IMediaCenterDataProvider, IPostAnalyticsHandler };
|
|
9
|
+
export type { ArticleDataProvider, IMediaCenterDataProvider, IPostAnalyticsHandler };
|
|
9
10
|
/**
|
|
10
11
|
* Opens the posts player modal.
|
|
11
12
|
*
|
|
@@ -23,6 +24,11 @@ export type { IMediaCenterDataProvider, IPostAnalyticsHandler };
|
|
|
23
24
|
* @param {IPostSocialInteractionsHandler} [init.socialInteractionsHandler]
|
|
24
25
|
* Handler for social interactions (like, share, etc.).
|
|
25
26
|
*
|
|
27
|
+
* Article data provider (optional)
|
|
28
|
+
* @param {ArticleDataProvider} [init.articleDataProvider]
|
|
29
|
+
* Provider for fetching article content. When provided, article posts will show
|
|
30
|
+
* a "Read more" button that opens an article dialog instead of navigating to a URL.
|
|
31
|
+
*
|
|
26
32
|
* Player settings (optional)
|
|
27
33
|
* @param {object} [init.playerSettings]
|
|
28
34
|
* Player UI and behavior settings.
|
|
@@ -87,6 +93,7 @@ export declare function openPostsPlayer(init: {
|
|
|
87
93
|
analyticsHandler?: IPostAnalyticsHandler;
|
|
88
94
|
socialInteractionsHandler?: IPostSocialInteractionsHandler;
|
|
89
95
|
sharingHandler?: IPostSharingHandler;
|
|
96
|
+
articleDataProvider?: ArticleDataProvider;
|
|
90
97
|
playerSettings?: {
|
|
91
98
|
disableBackground?: boolean;
|
|
92
99
|
locale?: AppLocaleValue;
|
|
@@ -22,6 +22,11 @@ export { PostsPlayer };
|
|
|
22
22
|
* @param {IPostSocialInteractionsHandler} [init.socialInteractionsHandler]
|
|
23
23
|
* Handler for social interactions (like, share, etc.).
|
|
24
24
|
*
|
|
25
|
+
* Article data provider (optional)
|
|
26
|
+
* @param {ArticleDataProvider} [init.articleDataProvider]
|
|
27
|
+
* Provider for fetching article content. When provided, article posts will show
|
|
28
|
+
* a "Read more" button that opens an article dialog instead of navigating to a URL.
|
|
29
|
+
*
|
|
25
30
|
* Player settings (optional)
|
|
26
31
|
* @param {object} [init.playerSettings]
|
|
27
32
|
* Player UI and behavior settings.
|
|
@@ -82,7 +87,7 @@ export { PostsPlayer };
|
|
|
82
87
|
* ```
|
|
83
88
|
*/
|
|
84
89
|
export function openPostsPlayer(init) {
|
|
85
|
-
const { postsProvider: dataProvider, analyticsHandler, socialInteractionsHandler, sharingHandler, playerSettings, on } = init;
|
|
90
|
+
const { postsProvider: dataProvider, analyticsHandler, socialInteractionsHandler, sharingHandler, articleDataProvider, playerSettings, on } = init;
|
|
86
91
|
const shadowHost = new ModalShadowHost();
|
|
87
92
|
let mounted = null;
|
|
88
93
|
const makeCloseOrchestrator = () => new CloseOrchestrator({
|
|
@@ -102,6 +107,7 @@ export function openPostsPlayer(init) {
|
|
|
102
107
|
socialInteractionsHandler,
|
|
103
108
|
sharingHandler,
|
|
104
109
|
analyticsHandler,
|
|
110
|
+
articleDataProvider,
|
|
105
111
|
playerSettings,
|
|
106
112
|
closeOrchestrator: makeCloseOrchestrator(),
|
|
107
113
|
on: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">import {} from '../../ui/player/close-orchestrator';
|
|
2
2
|
import { ShadowRoot } from '../../ui/shadow-dom';
|
|
3
3
|
import { default as PostsPlayerView } from './posts-player-view.svelte';
|
|
4
|
-
let { dataProvider, socialInteractionsHandler, sharingHandler, closeOrchestrator, playerSettings, analyticsHandler, on } = $props();
|
|
4
|
+
let { dataProvider, socialInteractionsHandler, sharingHandler, closeOrchestrator, playerSettings, analyticsHandler, articleDataProvider, on } = $props();
|
|
5
5
|
let backgroundImageUrl = $state(null);
|
|
6
6
|
const handleBackgroundImagedLoaded = (url) => {
|
|
7
7
|
backgroundImageUrl = url;
|
|
@@ -19,6 +19,7 @@ const handleBackgroundImagedLoaded = (url) => {
|
|
|
19
19
|
sharingHandler={sharingHandler}
|
|
20
20
|
playerSettings={playerSettings}
|
|
21
21
|
analyticsHandler={analyticsHandler}
|
|
22
|
+
articleDataProvider={articleDataProvider}
|
|
22
23
|
closeOrchestrator={closeOrchestrator}
|
|
23
24
|
on={{ postActivated: on?.postActivated, backgroundImageLoaded: playerSettings?.disableBackground === true ? undefined : handleBackgroundImagedLoaded }} />
|
|
24
25
|
</ShadowRoot>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ArticleDataProvider } from '../../articles/data-providers';
|
|
1
2
|
import type { IPostSharingHandler } from '../sharing';
|
|
2
3
|
import type { IPostSocialInteractionsHandler } from '../social-interactions';
|
|
3
4
|
import { type ICloseOrchestrator } from '../../ui/player/close-orchestrator';
|
|
@@ -9,6 +10,7 @@ type Props = {
|
|
|
9
10
|
socialInteractionsHandler?: IPostSocialInteractionsHandler;
|
|
10
11
|
sharingHandler?: IPostSharingHandler;
|
|
11
12
|
analyticsHandler?: IPostAnalyticsHandler;
|
|
13
|
+
articleDataProvider?: ArticleDataProvider;
|
|
12
14
|
closeOrchestrator: ICloseOrchestrator;
|
|
13
15
|
playerSettings?: {
|
|
14
16
|
locale?: AppLocaleValue;
|
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
<script lang="ts">import {
|
|
1
|
+
<script lang="ts">import { openArticleDialog } from '../../articles/article-dialog';
|
|
2
|
+
import { PostAttachments } from '../attachments';
|
|
2
3
|
import { PostActionsGenerator } from '../controls';
|
|
3
4
|
import { getPostCoverImage, PostModel } from '../model';
|
|
4
5
|
import { PostViewer } from '../post-viewer';
|
|
5
6
|
import { Player, PlayerConfig, PlayerSettings } from '../../ui/player';
|
|
7
|
+
import { getDialogAnchor } from '../../ui/shadow-dom';
|
|
6
8
|
import IconDelete from '@fluentui/svg-icons/icons/delete_32_regular.svg?raw';
|
|
7
9
|
import IconEdit from '@fluentui/svg-icons/icons/edit_32_regular.svg?raw';
|
|
8
10
|
import { preloadImage } from '@streamscloud/kit/core/utils';
|
|
9
11
|
import { initBufferFromProvider } from '@streamscloud/kit/ui/player/providers';
|
|
10
12
|
import { untrack } from 'svelte';
|
|
11
|
-
let { dataProvider, socialInteractionsHandler, sharingHandler, playerSettings, analyticsHandler, managementActions, closeOrchestrator, on } = $props();
|
|
13
|
+
let { dataProvider, socialInteractionsHandler, sharingHandler, playerSettings, analyticsHandler, managementActions, articleDataProvider, closeOrchestrator, on } = $props();
|
|
14
|
+
let playerHostElement = $state();
|
|
15
|
+
const handleArticleReadMore = (articleId) => {
|
|
16
|
+
if (!articleDataProvider || !playerHostElement) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const anchor = getDialogAnchor(playerHostElement);
|
|
20
|
+
if (!anchor) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
openArticleDialog({ anchor, articleId, fetchArticle: articleDataProvider.fetchArticle });
|
|
24
|
+
};
|
|
12
25
|
let buffer = $state.raw(null);
|
|
13
26
|
let mappedPostsCache = {};
|
|
14
27
|
$effect(() => {
|
|
@@ -145,7 +158,7 @@ const currentItemActions = $derived.by(() => {
|
|
|
145
158
|
adImpression: (id) => onAdImpression(id)
|
|
146
159
|
}} />
|
|
147
160
|
{/snippet}
|
|
148
|
-
<div class="player-host">
|
|
161
|
+
<div class="player-host" bind:this={playerHostElement}>
|
|
149
162
|
<Player
|
|
150
163
|
config={contentPlayerConfig}
|
|
151
164
|
itemActions={currentItemActions}
|
|
@@ -164,7 +177,8 @@ const currentItemActions = $derived.by(() => {
|
|
|
164
177
|
productClick: (productId) => onProductClick(productId, postModel.id),
|
|
165
178
|
productImpression: (productId) => onProductImpression(productId, postModel.id),
|
|
166
179
|
adClick: (adId) => onAdClick(adId),
|
|
167
|
-
adImpression: (adId) => onAdImpression(adId)
|
|
180
|
+
adImpression: (adId) => onAdImpression(adId),
|
|
181
|
+
articleReadMore: articleDataProvider ? handleArticleReadMore : undefined
|
|
168
182
|
}} />
|
|
169
183
|
{/snippet}
|
|
170
184
|
</Player>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ArticleDataProvider } from '../../articles/data-providers';
|
|
1
2
|
import type { PostType } from '../../core/enums';
|
|
2
3
|
import type { IPostModel } from '..';
|
|
3
4
|
import type { IPostSharingHandler } from '../sharing';
|
|
@@ -26,6 +27,8 @@ export type PostsPlayerProps = {
|
|
|
26
27
|
sharingHandler?: IPostSharingHandler;
|
|
27
28
|
analyticsHandler?: IPostAnalyticsHandler;
|
|
28
29
|
managementActions?: PostManagementActions;
|
|
30
|
+
/** Provider for fetching article content. Enables "Read more" button that opens an article dialog. */
|
|
31
|
+
articleDataProvider?: ArticleDataProvider;
|
|
29
32
|
playerSettings?: PostPlayerSettings;
|
|
30
33
|
closeOrchestrator: ICloseOrchestrator;
|
|
31
34
|
on?: {
|
|
@@ -11,3 +11,11 @@ export const createShadowRoot = (host) => {
|
|
|
11
11
|
const prepareShadowRootHost = (host) => {
|
|
12
12
|
host.style.all = 'unset';
|
|
13
13
|
};
|
|
14
|
+
const DIALOG_ANCHOR_SELECTOR = '[data-dialog-anchor]';
|
|
15
|
+
export const getDialogAnchor = (element) => {
|
|
16
|
+
const root = element.getRootNode();
|
|
17
|
+
if (root instanceof ShadowRoot) {
|
|
18
|
+
return root.querySelector(DIALOG_ANCHOR_SELECTOR);
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|