@gravity-ui/page-constructor 5.29.1 → 5.30.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +6 -0
  2. package/build/cjs/blocks/Media/Media.js +7 -2
  3. package/build/cjs/blocks/PromoFeaturesBlock/PromoFeaturesBlock.js +6 -1
  4. package/build/cjs/blocks/Tabs/Tabs.js +8 -2
  5. package/build/cjs/blocks/Tabs/TabsTextContent/TabsTextContent.d.ts +2 -2
  6. package/build/cjs/blocks/Tabs/TabsTextContent/TabsTextContent.js +12 -6
  7. package/build/cjs/components/Media/Media.js +13 -2
  8. package/build/cjs/components/Title/TitleItem.d.ts +1 -1
  9. package/build/cjs/containers/PageConstructor/PageConstructor.d.ts +3 -0
  10. package/build/cjs/containers/PageConstructor/PageConstructor.js +3 -2
  11. package/build/cjs/context/innerContext/InnerContext.d.ts +3 -0
  12. package/build/cjs/context/innerContext/InnerContext.js +1 -0
  13. package/build/cjs/grid/Col/Col.d.ts +1 -1
  14. package/build/cjs/models/constructor-items/common.d.ts +8 -0
  15. package/build/cjs/sub-blocks/LayoutItem/LayoutItem.js +7 -1
  16. package/build/cjs/utils/microdata.d.ts +3 -0
  17. package/build/cjs/utils/microdata.js +11 -0
  18. package/build/esm/blocks/Media/Media.js +7 -2
  19. package/build/esm/blocks/PromoFeaturesBlock/PromoFeaturesBlock.js +6 -1
  20. package/build/esm/blocks/Tabs/Tabs.js +8 -2
  21. package/build/esm/blocks/Tabs/TabsTextContent/TabsTextContent.d.ts +2 -2
  22. package/build/esm/blocks/Tabs/TabsTextContent/TabsTextContent.js +12 -6
  23. package/build/esm/components/Media/Media.js +14 -3
  24. package/build/esm/components/Title/TitleItem.d.ts +1 -1
  25. package/build/esm/containers/PageConstructor/PageConstructor.d.ts +3 -0
  26. package/build/esm/containers/PageConstructor/PageConstructor.js +3 -2
  27. package/build/esm/context/innerContext/InnerContext.d.ts +3 -0
  28. package/build/esm/context/innerContext/InnerContext.js +1 -0
  29. package/build/esm/grid/Col/Col.d.ts +1 -1
  30. package/build/esm/models/constructor-items/common.d.ts +8 -0
  31. package/build/esm/sub-blocks/LayoutItem/LayoutItem.js +7 -1
  32. package/build/esm/utils/microdata.d.ts +3 -0
  33. package/build/esm/utils/microdata.js +5 -0
  34. package/package.json +1 -1
  35. package/server/models/constructor-items/common.d.ts +8 -0
  36. package/styles/styles.css +0 -1
  37. package/styles/styles.scss +0 -1
  38. package/widget/index.js +1 -1
package/README.md CHANGED
@@ -165,6 +165,12 @@ To use mixins and constructor style variables when creating custom blocks, add i
165
165
  @import '~@gravity-ui/page-constructor/styles/styles.scss';
166
166
  ```
167
167
 
168
+ To use default font, add import in your file:
169
+
170
+ ```css
171
+ @import '@gravity-ui/uikit/styles/fonts.css';
172
+ ```
173
+
168
174
  ### Loadable blocks
169
175
 
170
176
  It's sometimes necessary that a block renders itself based on data to be loaded. In this case, loadable blocks are used.
@@ -8,9 +8,10 @@ const MediaBase_1 = tslib_1.__importDefault(require("../../components/MediaBase/
8
8
  const theme_1 = require("../../context/theme");
9
9
  const utils_1 = require("../../utils");
10
10
  const borderSelector_1 = require("../../utils/borderSelector");
11
+ const microdata_1 = require("../../utils/microdata");
11
12
  const b = (0, utils_1.block)('media-block');
12
13
  const MediaBlock = (props) => {
13
- const { media, border, disableShadow } = props;
14
+ const { media, border, disableShadow, title, description } = props;
14
15
  const borderSelected = (0, borderSelector_1.getMediaBorder)({
15
16
  border,
16
17
  disableShadow,
@@ -18,9 +19,13 @@ const MediaBlock = (props) => {
18
19
  const [play, setPlay] = (0, react_1.useState)(false);
19
20
  const theme = (0, theme_1.useTheme)();
20
21
  const mediaThemed = (0, utils_1.getThemedValue)(media, theme);
22
+ const mediaWithMicrodata = (0, microdata_1.mergeVideoMicrodata)(mediaThemed, {
23
+ name: title,
24
+ description,
25
+ });
21
26
  return (react_1.default.createElement(MediaBase_1.default, Object.assign({}, props, { onScroll: () => setPlay(true) }),
22
27
  react_1.default.createElement(MediaBase_1.default.Card, null,
23
- react_1.default.createElement(Media_1.default, Object.assign({ imageClassName: b('image') }, mediaThemed, { playVideo: play, className: b({ border: borderSelected }) })))));
28
+ react_1.default.createElement(Media_1.default, Object.assign({ imageClassName: b('image') }, mediaWithMicrodata, { playVideo: play, className: b({ border: borderSelected }) })))));
24
29
  };
25
30
  exports.MediaBlock = MediaBlock;
26
31
  exports.default = exports.MediaBlock;
@@ -11,6 +11,7 @@ const YFMWrapper_1 = tslib_1.__importDefault(require("../../components/YFMWrappe
11
11
  const constants_1 = require("../../constants");
12
12
  const theme_1 = require("../../context/theme");
13
13
  const utils_1 = require("../../utils");
14
+ const microdata_1 = require("../../utils/microdata");
14
15
  const b = (0, utils_1.block)('PromoFeaturesBlock');
15
16
  const breakpointColumns = {
16
17
  [constants_1.BREAKPOINTS.lg]: 3,
@@ -28,6 +29,10 @@ const PromoFeaturesBlock = (props) => {
28
29
  const blockModifier = backgroundTheme === 'default' ? 'default' : 'light';
29
30
  const themeMod = cardTheme || blockModifier || '';
30
31
  const themedMedia = (0, utils_1.getThemedValue)(media, globalTheme);
32
+ const allProps = (0, microdata_1.mergeVideoMicrodata)(themedMedia, {
33
+ name: cardTitle,
34
+ description: text,
35
+ });
31
36
  return (react_1.default.createElement("div", { key: index, className: b('card', {
32
37
  'no-media': !media,
33
38
  [themeMod]: Boolean(themeMod),
@@ -36,7 +41,7 @@ const PromoFeaturesBlock = (props) => {
36
41
  react_1.default.createElement("h3", { className: b('card-title') }, cardTitle),
37
42
  react_1.default.createElement("div", { className: b('card-text') },
38
43
  react_1.default.createElement(YFMWrapper_1.default, { content: text, modifiers: { constructor: true } }))),
39
- media && react_1.default.createElement(Media_1.default, Object.assign({ className: b('card-media') }, themedMedia))));
44
+ media && react_1.default.createElement(Media_1.default, Object.assign({ className: b('card-media') }, allProps))));
40
45
  }))));
41
46
  };
42
47
  exports.default = PromoFeaturesBlock;
@@ -15,6 +15,7 @@ const YFMWrapper_1 = tslib_1.__importDefault(require("../../components/YFMWrappe
15
15
  const theme_1 = require("../../context/theme");
16
16
  const grid_1 = require("../../grid");
17
17
  const utils_2 = require("../../utils");
18
+ const microdata_1 = require("../../utils/microdata");
18
19
  const TabsTextContent_1 = tslib_1.__importDefault(require("./TabsTextContent/TabsTextContent"));
19
20
  const b = (0, utils_2.block)('tabs-block');
20
21
  const TabsBlock = ({ items, title, description, animated, tabsColSizes, centered, direction = 'media-content', contentSize = 's', }) => {
@@ -63,14 +64,19 @@ const TabsBlock = ({ items, title, description, animated, tabsColSizes, centered
63
64
  const showMedia = Boolean((activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.media) || imageProps);
64
65
  const showText = Boolean(activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.text);
65
66
  const border = (activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.border) || 'shadow';
66
- const textContent = activeTabData && showText && (react_1.default.createElement(TabsTextContent_1.default, { showMedia: showMedia, data: activeTabData, imageProps: imageProps ? imageProps : undefined, isReverse: isReverse, contentSize: contentSize, centered: centered }));
67
+ const textContent = showText && (react_1.default.createElement(TabsTextContent_1.default, { showMedia: showMedia, data: activeTabData, imageProps: imageProps ? imageProps : undefined, isReverse: isReverse, contentSize: contentSize, centered: centered }));
67
68
  const mediaContent = showMedia && (react_1.default.createElement(grid_1.Col, { sizes: { all: 12, md: 8 }, orders: {
68
69
  all: grid_1.GridColumnOrderClasses.Last,
69
70
  md: grid_1.GridColumnOrderClasses.First,
70
71
  }, className: b('col', { centered: centered }) },
71
72
  (activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.media) && (react_1.default.createElement("div", { style: { minHeight: mediaVideoHeight || minImageHeight } },
72
73
  react_1.default.createElement("div", { ref: ref },
73
- react_1.default.createElement(Media_1.default, Object.assign({}, (0, utils_2.getThemedValue)(activeTabData.media, theme), { key: activeTab, className: b('media', { border }), playVideo: play, height: mediaVideoHeight || undefined, onImageLoad: handleImageHeight }))))),
74
+ react_1.default.createElement(Media_1.default, Object.assign({}, (0, microdata_1.mergeVideoMicrodata)((0, utils_2.getThemedValue)(activeTabData.media, theme), {
75
+ name: activeTabData.tabName,
76
+ description: activeTabData.caption
77
+ ? activeTabData.caption
78
+ : undefined,
79
+ }), { key: activeTab, className: b('media', { border }), playVideo: play, height: mediaVideoHeight || undefined, onImageLoad: handleImageHeight }))))),
74
80
  imageProps && (react_1.default.createElement(react_1.Fragment, null,
75
81
  react_1.default.createElement(FullscreenImage_1.default, Object.assign({}, imageProps, { imageClassName: b('image', { border }) })))),
76
82
  (activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.caption) && (react_1.default.createElement("p", { className: b('caption'), id: captionId },
@@ -2,9 +2,9 @@ import { ImageDeviceProps, ImageObjectProps, TabsBlockItem, TabsBlockProps } fro
2
2
  interface TextContentProps extends Pick<TabsBlockProps, 'centered' | 'contentSize'> {
3
3
  showMedia: boolean;
4
4
  isReverse: boolean;
5
- data: TabsBlockItem;
5
+ data?: TabsBlockItem;
6
6
  centered?: boolean;
7
7
  imageProps?: ImageObjectProps | ImageDeviceProps;
8
8
  }
9
- export declare const TabsTextContent: ({ centered, contentSize, showMedia, data: { media, title, text, additionalInfo, link, links, buttons, list }, imageProps, isReverse, }: TextContentProps) => JSX.Element;
9
+ export declare const TabsTextContent: ({ centered, contentSize, showMedia, data, imageProps, isReverse, }: TextContentProps) => JSX.Element | null;
10
10
  export default TabsTextContent;
@@ -7,11 +7,17 @@ const grid_1 = require("../../../grid");
7
7
  const sub_blocks_1 = require("../../../sub-blocks");
8
8
  const utils_1 = require("../../../utils");
9
9
  const b = (0, utils_1.block)('tabs-block-text-content');
10
- const TabsTextContent = ({ centered, contentSize = 's', showMedia, data: { media, title, text, additionalInfo, link, links, buttons, list }, imageProps, isReverse, }) => (react_1.default.createElement(grid_1.Col, { sizes: { all: 12, md: showMedia ? 4 : 8 }, className: b({ centered: centered }) },
11
- react_1.default.createElement("div", { className: b('wrapper', {
12
- reverse: isReverse,
13
- 'no-image': !(media || imageProps),
14
- }) },
15
- react_1.default.createElement(sub_blocks_1.Content, { title: title, text: text, additionalInfo: additionalInfo, size: contentSize, list: list, links: [...(link ? [link] : []), ...(links || [])], buttons: buttons, colSizes: { all: 12 } }))));
10
+ const TabsTextContent = ({ centered, contentSize = 's', showMedia, data, imageProps, isReverse, }) => {
11
+ if (!data) {
12
+ return null;
13
+ }
14
+ const { media, title, text, additionalInfo, link, links, buttons, list } = data;
15
+ return (react_1.default.createElement(grid_1.Col, { sizes: { all: 12, md: showMedia ? 4 : 8 }, className: b({ centered: centered }) },
16
+ react_1.default.createElement("div", { className: b('wrapper', {
17
+ reverse: isReverse,
18
+ 'no-image': !(media || imageProps),
19
+ }) },
20
+ react_1.default.createElement(sub_blocks_1.Content, { title: title, text: text, additionalInfo: additionalInfo, size: contentSize, list: list, links: [...(link ? [link] : []), ...(links || [])], buttons: buttons, colSizes: { all: 12 } }))));
21
+ };
16
22
  exports.TabsTextContent = TabsTextContent;
17
23
  exports.default = exports.TabsTextContent;
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Media = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importStar(require("react"));
6
+ const innerContext_1 = require("../../context/innerContext");
6
7
  const utils_1 = require("../../utils");
8
+ const microdata_1 = require("../../utils/microdata");
7
9
  const VideoBlock_1 = tslib_1.__importDefault(require("../VideoBlock/VideoBlock"));
8
10
  const DataLens_1 = tslib_1.__importDefault(require("./DataLens/DataLens"));
9
11
  const FullscreenVideo_1 = tslib_1.__importDefault(require("./FullscreenVideo/FullscreenVideo"));
@@ -12,8 +14,9 @@ const Image_1 = tslib_1.__importDefault(require("./Image/Image"));
12
14
  const Video_1 = tslib_1.__importDefault(require("./Video/Video"));
13
15
  const b = (0, utils_1.block)('Media');
14
16
  const Media = (props) => {
15
- const { image, video, youtube, videoIframe, dataLens, color, height, previewImg, parallax = false, fullscreen, analyticsEvents, className, imageClassName, videoClassName, youtubeClassName, disableImageSliderForArrayInput, playVideo = true, isBackground, playButton, customBarControlsClassName, qa, ratio, autoplay, onImageLoad, iframe, margins, } = props;
17
+ const { image, video, youtube, videoIframe, dataLens, color, height, previewImg, parallax = false, fullscreen, analyticsEvents, className, imageClassName, videoClassName, youtubeClassName, disableImageSliderForArrayInput, playVideo = true, isBackground, playButton, customBarControlsClassName, qa, ratio, autoplay, onImageLoad, iframe, margins, videoMicrodata, } = props;
16
18
  const [hasVideoFallback, setHasVideoFallback] = (0, react_1.useState)(false);
19
+ const { microdata } = (0, react_1.useContext)(innerContext_1.InnerContext);
17
20
  const qaAttributes = (0, utils_1.getQaAttrubutes)(qa, 'video');
18
21
  const content = (0, react_1.useMemo)(() => {
19
22
  let result = [];
@@ -80,7 +83,15 @@ const Media = (props) => {
80
83
  autoplay,
81
84
  margins,
82
85
  ]);
83
- return (react_1.default.createElement("div", { className: b(null, className), style: { backgroundColor: color }, "data-qa": qa }, content));
86
+ const videoMicrodataScript = (0, react_1.useMemo)(() => {
87
+ var _a;
88
+ const { name, description } = videoMicrodata || {};
89
+ const json = JSON.stringify(Object.assign(Object.assign({ '@context': 'http://schema.org/', '@type': 'VideoObject', uploadDate: microdata === null || microdata === void 0 ? void 0 : microdata.contentUpdatedDate, contentUrl: ((_a = video === null || video === void 0 ? void 0 : video.src) === null || _a === void 0 ? void 0 : _a[0]) || videoIframe || youtube, thumbnailUrl: previewImg }, (videoMicrodata || {})), { name: name ? (0, microdata_1.sanitizeMicrodata)(name) : name, description: description ? (0, microdata_1.sanitizeMicrodata)(description) : description }));
90
+ return video || youtube || videoIframe ? (react_1.default.createElement("script", { type: "application/ld+json" }, json)) : null;
91
+ }, [microdata === null || microdata === void 0 ? void 0 : microdata.contentUpdatedDate, previewImg, video, videoIframe, videoMicrodata, youtube]);
92
+ return (react_1.default.createElement("div", { className: b(null, className), style: { backgroundColor: color }, "data-qa": qa },
93
+ videoMicrodataScript,
94
+ content));
84
95
  };
85
96
  exports.Media = Media;
86
97
  exports.default = exports.Media;
@@ -1,5 +1,5 @@
1
1
  import { QAProps, TextSize, TitleItemProps } from '../../models';
2
- export declare function getArrowSize(size: TextSize, isMobile: boolean): 16 | 24 | 13 | 22 | 26 | 38 | 20;
2
+ export declare function getArrowSize(size: TextSize, isMobile: boolean): 20 | 24 | 16 | 13 | 22 | 26 | 38;
3
3
  export interface TitleItemFullProps extends TitleItemProps, QAProps {
4
4
  className?: string;
5
5
  onClick?: () => void;
@@ -10,6 +10,9 @@ export interface PageConstructorProps {
10
10
  custom?: CustomConfig;
11
11
  renderMenu?: () => React.ReactNode;
12
12
  navigation?: NavigationData;
13
+ microdata?: {
14
+ contentUpdatedDate?: string;
15
+ };
13
16
  }
14
17
  export declare const Constructor: (props: PageConstructorProps) => JSX.Element;
15
18
  export declare const PageConstructor: (props: PageConstructorProps) => JSX.Element;
@@ -20,7 +20,7 @@ const ConstructorItem_1 = require("./components/ConstructorItem");
20
20
  const ConstructorRow_1 = require("./components/ConstructorRow");
21
21
  const b = (0, utils_1.block)('page-constructor');
22
22
  const Constructor = (props) => {
23
- const { content: { blocks = [], background } = {}, renderMenu, shouldRenderBlock, navigation, custom, } = props;
23
+ const { content: { blocks = [], background } = {}, renderMenu, shouldRenderBlock, navigation, custom, microdata, } = props;
24
24
  const { context } = (0, react_1.useMemo)(() => ({
25
25
  context: {
26
26
  blockTypes: [...models_1.BlockTypes, ...(0, utils_1.getCustomTypes)(['blocks', 'headers'], custom)],
@@ -37,8 +37,9 @@ const Constructor = (props) => {
37
37
  customization: {
38
38
  decorators: custom === null || custom === void 0 ? void 0 : custom.decorators,
39
39
  },
40
+ microdata,
40
41
  },
41
- }), [custom, shouldRenderBlock]);
42
+ }), [custom, shouldRenderBlock, microdata]);
42
43
  const theme = (0, theme_1.useTheme)();
43
44
  const header = (0, utils_1.getHeaderBlock)(blocks, context.headerBlockTypes);
44
45
  const restBlocks = (0, utils_1.getOrderedBlocks)(blocks, context.headerBlockTypes);
@@ -11,5 +11,8 @@ export interface InnerContextType {
11
11
  loadables?: LoadableConfig;
12
12
  shouldRenderBlock?: ShouldRenderBlock;
13
13
  customization?: Pick<CustomConfig, 'decorators'>;
14
+ microdata?: {
15
+ contentUpdatedDate?: string;
16
+ };
14
17
  }
15
18
  export declare const InnerContext: React.Context<InnerContextType>;
@@ -10,4 +10,5 @@ exports.InnerContext = react_1.default.createContext({
10
10
  navigationBlockTypes: [],
11
11
  itemMap: {},
12
12
  navItemMap: {},
13
+ microdata: {},
13
14
  });
@@ -5,4 +5,4 @@ export interface GridColumnProps extends GridColumnClassParams, Refable<HTMLDivE
5
5
  style?: CSSProperties;
6
6
  children?: React.ReactNode;
7
7
  }
8
- export declare const Col: React.ForwardRefExoticComponent<Pick<React.PropsWithChildren<GridColumnProps>, "style" | "children" | "sizes" | "className" | "hidden" | "role" | "qa" | "reset" | "visible" | "offsets" | "orders" | "alignSelf" | "justifyContent"> & React.RefAttributes<HTMLDivElement>>;
8
+ export declare const Col: React.ForwardRefExoticComponent<Pick<React.PropsWithChildren<GridColumnProps>, "style" | "children" | "sizes" | "className" | "hidden" | "role" | "qa" | "offsets" | "orders" | "visible" | "alignSelf" | "justifyContent" | "reset"> & React.RefAttributes<HTMLDivElement>>;
@@ -207,6 +207,14 @@ export interface MediaComponentIframeProps {
207
207
  }
208
208
  export interface MediaProps extends Animatable, Partial<MediaComponentDataLensProps>, Partial<MediaComponentYoutubeProps>, Partial<MediaComponentVideoIframeProps>, Partial<MediaComponentImageProps>, Partial<MediaComponentIframeProps>, Partial<MediaComponentVideoProps> {
209
209
  color?: string;
210
+ videoMicrodata?: {
211
+ name?: string;
212
+ description?: string;
213
+ duration?: string;
214
+ uploadDate?: string;
215
+ contentUrl?: string;
216
+ thumbnailUrl?: string;
217
+ };
210
218
  }
211
219
  export interface BackgroundMediaProps extends MediaProps, Animatable, QAProps {
212
220
  fullWidthMedia?: boolean;
@@ -6,6 +6,7 @@ const uikit_1 = require("@gravity-ui/uikit");
6
6
  const components_1 = require("../../components");
7
7
  const theme_1 = require("../../context/theme");
8
8
  const utils_1 = require("../../utils");
9
+ const microdata_1 = require("../../utils/microdata");
9
10
  const Content_1 = tslib_1.__importDefault(require("../Content/Content"));
10
11
  const utils_2 = require("./utils");
11
12
  const b = (0, utils_1.block)('layout-item');
@@ -22,9 +23,14 @@ const LayoutItem = (_a) => {
22
23
  return null;
23
24
  }
24
25
  const themedMedia = (0, utils_1.getThemedValue)(media, theme);
26
+ const { title } = content;
27
+ const mediaWithMicrodata = (0, microdata_1.mergeVideoMicrodata)(themedMedia, {
28
+ name: typeof title === 'string' ? title : title === null || title === void 0 ? void 0 : title.text,
29
+ description: content.text,
30
+ });
25
31
  return fullscreen && (0, utils_2.hasFullscreen)(themedMedia) ? (react_1.default.createElement(components_1.FullscreenMedia, { showFullscreenIcon: (0, utils_2.showFullscreenIcon)(themedMedia) }, (_a = {}) => {
26
32
  var { className: mediaClassName, fullscreen: _fullscreen } = _a, fullscreenMediaProps = tslib_1.__rest(_a, ["className", "fullscreen"]);
27
- return (react_1.default.createElement(components_1.Media, Object.assign({}, themedMedia, fullscreenMediaProps, { className: b('media', { border }, mediaClassName), analyticsEvents: analyticsEvents })));
33
+ return (react_1.default.createElement(components_1.Media, Object.assign({}, mediaWithMicrodata, fullscreenMediaProps, { className: b('media', { border }, mediaClassName), analyticsEvents: analyticsEvents })));
28
34
  })) : (react_1.default.createElement(components_1.Media, Object.assign({}, themedMedia, { className: b('media', { border }), analyticsEvents: analyticsEvents })));
29
35
  };
30
36
  return (react_1.default.createElement("div", { className: b(null, className) },
@@ -0,0 +1,3 @@
1
+ import { MediaProps } from '../models';
2
+ export declare const mergeVideoMicrodata: (values?: MediaProps, newValues?: MediaProps['videoMicrodata']) => MediaProps;
3
+ export declare function sanitizeMicrodata(html: string): string;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeMicrodata = exports.mergeVideoMicrodata = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const sanitize_html_1 = tslib_1.__importDefault(require("sanitize-html"));
6
+ const mergeVideoMicrodata = (values = {}, newValues = {}) => (Object.assign(Object.assign({}, values), { videoMicrodata: Object.assign(Object.assign({}, newValues), (values.videoMicrodata || {})) }));
7
+ exports.mergeVideoMicrodata = mergeVideoMicrodata;
8
+ function sanitizeMicrodata(html) {
9
+ return html && (0, sanitize_html_1.default)(html, { allowedTags: [], allowedAttributes: {} });
10
+ }
11
+ exports.sanitizeMicrodata = sanitizeMicrodata;
@@ -4,10 +4,11 @@ import MediaBase from '../../components/MediaBase/MediaBase';
4
4
  import { useTheme } from '../../context/theme';
5
5
  import { block, getThemedValue } from '../../utils';
6
6
  import { getMediaBorder } from '../../utils/borderSelector';
7
+ import { mergeVideoMicrodata } from '../../utils/microdata';
7
8
  import './Media.css';
8
9
  const b = block('media-block');
9
10
  export const MediaBlock = (props) => {
10
- const { media, border, disableShadow } = props;
11
+ const { media, border, disableShadow, title, description } = props;
11
12
  const borderSelected = getMediaBorder({
12
13
  border,
13
14
  disableShadow,
@@ -15,8 +16,12 @@ export const MediaBlock = (props) => {
15
16
  const [play, setPlay] = useState(false);
16
17
  const theme = useTheme();
17
18
  const mediaThemed = getThemedValue(media, theme);
19
+ const mediaWithMicrodata = mergeVideoMicrodata(mediaThemed, {
20
+ name: title,
21
+ description,
22
+ });
18
23
  return (React.createElement(MediaBase, Object.assign({}, props, { onScroll: () => setPlay(true) }),
19
24
  React.createElement(MediaBase.Card, null,
20
- React.createElement(Media, Object.assign({ imageClassName: b('image') }, mediaThemed, { playVideo: play, className: b({ border: borderSelected }) })))));
25
+ React.createElement(Media, Object.assign({ imageClassName: b('image') }, mediaWithMicrodata, { playVideo: play, className: b({ border: borderSelected }) })))));
21
26
  };
22
27
  export default MediaBlock;
@@ -8,6 +8,7 @@ import YFMWrapper from '../../components/YFMWrapper/YFMWrapper';
8
8
  import { BREAKPOINTS } from '../../constants';
9
9
  import { useTheme } from '../../context/theme';
10
10
  import { block, getThemedValue } from '../../utils';
11
+ import { mergeVideoMicrodata } from '../../utils/microdata';
11
12
  import './PromoFeaturesBlock.css';
12
13
  const b = block('PromoFeaturesBlock');
13
14
  const breakpointColumns = {
@@ -26,6 +27,10 @@ const PromoFeaturesBlock = (props) => {
26
27
  const blockModifier = backgroundTheme === 'default' ? 'default' : 'light';
27
28
  const themeMod = cardTheme || blockModifier || '';
28
29
  const themedMedia = getThemedValue(media, globalTheme);
30
+ const allProps = mergeVideoMicrodata(themedMedia, {
31
+ name: cardTitle,
32
+ description: text,
33
+ });
29
34
  return (React.createElement("div", { key: index, className: b('card', {
30
35
  'no-media': !media,
31
36
  [themeMod]: Boolean(themeMod),
@@ -34,7 +39,7 @@ const PromoFeaturesBlock = (props) => {
34
39
  React.createElement("h3", { className: b('card-title') }, cardTitle),
35
40
  React.createElement("div", { className: b('card-text') },
36
41
  React.createElement(YFMWrapper, { content: text, modifiers: { constructor: true } }))),
37
- media && React.createElement(Media, Object.assign({ className: b('card-media') }, themedMedia))));
42
+ media && React.createElement(Media, Object.assign({ className: b('card-media') }, allProps))));
38
43
  }))));
39
44
  };
40
45
  export default PromoFeaturesBlock;
@@ -11,6 +11,7 @@ import YFMWrapper from '../../components/YFMWrapper/YFMWrapper';
11
11
  import { useTheme } from '../../context/theme';
12
12
  import { Col, GridColumnOrderClasses, Row } from '../../grid';
13
13
  import { block, getThemedValue } from '../../utils';
14
+ import { mergeVideoMicrodata } from '../../utils/microdata';
14
15
  import TabsTextContent from './TabsTextContent/TabsTextContent';
15
16
  import './Tabs.css';
16
17
  const b = block('tabs-block');
@@ -60,14 +61,19 @@ export const TabsBlock = ({ items, title, description, animated, tabsColSizes, c
60
61
  const showMedia = Boolean((activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.media) || imageProps);
61
62
  const showText = Boolean(activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.text);
62
63
  const border = (activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.border) || 'shadow';
63
- const textContent = activeTabData && showText && (React.createElement(TabsTextContent, { showMedia: showMedia, data: activeTabData, imageProps: imageProps ? imageProps : undefined, isReverse: isReverse, contentSize: contentSize, centered: centered }));
64
+ const textContent = showText && (React.createElement(TabsTextContent, { showMedia: showMedia, data: activeTabData, imageProps: imageProps ? imageProps : undefined, isReverse: isReverse, contentSize: contentSize, centered: centered }));
64
65
  const mediaContent = showMedia && (React.createElement(Col, { sizes: { all: 12, md: 8 }, orders: {
65
66
  all: GridColumnOrderClasses.Last,
66
67
  md: GridColumnOrderClasses.First,
67
68
  }, className: b('col', { centered: centered }) },
68
69
  (activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.media) && (React.createElement("div", { style: { minHeight: mediaVideoHeight || minImageHeight } },
69
70
  React.createElement("div", { ref: ref },
70
- React.createElement(Media, Object.assign({}, getThemedValue(activeTabData.media, theme), { key: activeTab, className: b('media', { border }), playVideo: play, height: mediaVideoHeight || undefined, onImageLoad: handleImageHeight }))))),
71
+ React.createElement(Media, Object.assign({}, mergeVideoMicrodata(getThemedValue(activeTabData.media, theme), {
72
+ name: activeTabData.tabName,
73
+ description: activeTabData.caption
74
+ ? activeTabData.caption
75
+ : undefined,
76
+ }), { key: activeTab, className: b('media', { border }), playVideo: play, height: mediaVideoHeight || undefined, onImageLoad: handleImageHeight }))))),
71
77
  imageProps && (React.createElement(Fragment, null,
72
78
  React.createElement(FullscreenImage, Object.assign({}, imageProps, { imageClassName: b('image', { border }) })))),
73
79
  (activeTabData === null || activeTabData === void 0 ? void 0 : activeTabData.caption) && (React.createElement("p", { className: b('caption'), id: captionId },
@@ -3,9 +3,9 @@ import './TabsTextContent.css';
3
3
  interface TextContentProps extends Pick<TabsBlockProps, 'centered' | 'contentSize'> {
4
4
  showMedia: boolean;
5
5
  isReverse: boolean;
6
- data: TabsBlockItem;
6
+ data?: TabsBlockItem;
7
7
  centered?: boolean;
8
8
  imageProps?: ImageObjectProps | ImageDeviceProps;
9
9
  }
10
- export declare const TabsTextContent: ({ centered, contentSize, showMedia, data: { media, title, text, additionalInfo, link, links, buttons, list }, imageProps, isReverse, }: TextContentProps) => JSX.Element;
10
+ export declare const TabsTextContent: ({ centered, contentSize, showMedia, data, imageProps, isReverse, }: TextContentProps) => JSX.Element | null;
11
11
  export default TabsTextContent;
@@ -4,10 +4,16 @@ import { Content } from '../../../sub-blocks';
4
4
  import { block } from '../../../utils';
5
5
  import './TabsTextContent.css';
6
6
  const b = block('tabs-block-text-content');
7
- export const TabsTextContent = ({ centered, contentSize = 's', showMedia, data: { media, title, text, additionalInfo, link, links, buttons, list }, imageProps, isReverse, }) => (React.createElement(Col, { sizes: { all: 12, md: showMedia ? 4 : 8 }, className: b({ centered: centered }) },
8
- React.createElement("div", { className: b('wrapper', {
9
- reverse: isReverse,
10
- 'no-image': !(media || imageProps),
11
- }) },
12
- React.createElement(Content, { title: title, text: text, additionalInfo: additionalInfo, size: contentSize, list: list, links: [...(link ? [link] : []), ...(links || [])], buttons: buttons, colSizes: { all: 12 } }))));
7
+ export const TabsTextContent = ({ centered, contentSize = 's', showMedia, data, imageProps, isReverse, }) => {
8
+ if (!data) {
9
+ return null;
10
+ }
11
+ const { media, title, text, additionalInfo, link, links, buttons, list } = data;
12
+ return (React.createElement(Col, { sizes: { all: 12, md: showMedia ? 4 : 8 }, className: b({ centered: centered }) },
13
+ React.createElement("div", { className: b('wrapper', {
14
+ reverse: isReverse,
15
+ 'no-image': !(media || imageProps),
16
+ }) },
17
+ React.createElement(Content, { title: title, text: text, additionalInfo: additionalInfo, size: contentSize, list: list, links: [...(link ? [link] : []), ...(links || [])], buttons: buttons, colSizes: { all: 12 } }))));
18
+ };
13
19
  export default TabsTextContent;
@@ -1,5 +1,7 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useContext, useMemo, useState } from 'react';
2
+ import { InnerContext } from '../../context/innerContext';
2
3
  import { block, getQaAttrubutes } from '../../utils';
4
+ import { sanitizeMicrodata } from '../../utils/microdata';
3
5
  import IframeVideoBlock from '../VideoBlock/VideoBlock';
4
6
  import DataLens from './DataLens/DataLens';
5
7
  import FullscreenVideo from './FullscreenVideo/FullscreenVideo';
@@ -9,8 +11,9 @@ import Video from './Video/Video';
9
11
  import './Media.css';
10
12
  const b = block('Media');
11
13
  export const Media = (props) => {
12
- const { image, video, youtube, videoIframe, dataLens, color, height, previewImg, parallax = false, fullscreen, analyticsEvents, className, imageClassName, videoClassName, youtubeClassName, disableImageSliderForArrayInput, playVideo = true, isBackground, playButton, customBarControlsClassName, qa, ratio, autoplay, onImageLoad, iframe, margins, } = props;
14
+ const { image, video, youtube, videoIframe, dataLens, color, height, previewImg, parallax = false, fullscreen, analyticsEvents, className, imageClassName, videoClassName, youtubeClassName, disableImageSliderForArrayInput, playVideo = true, isBackground, playButton, customBarControlsClassName, qa, ratio, autoplay, onImageLoad, iframe, margins, videoMicrodata, } = props;
13
15
  const [hasVideoFallback, setHasVideoFallback] = useState(false);
16
+ const { microdata } = useContext(InnerContext);
14
17
  const qaAttributes = getQaAttrubutes(qa, 'video');
15
18
  const content = useMemo(() => {
16
19
  let result = [];
@@ -77,6 +80,14 @@ export const Media = (props) => {
77
80
  autoplay,
78
81
  margins,
79
82
  ]);
80
- return (React.createElement("div", { className: b(null, className), style: { backgroundColor: color }, "data-qa": qa }, content));
83
+ const videoMicrodataScript = useMemo(() => {
84
+ var _a;
85
+ const { name, description } = videoMicrodata || {};
86
+ const json = JSON.stringify(Object.assign(Object.assign({ '@context': 'http://schema.org/', '@type': 'VideoObject', uploadDate: microdata === null || microdata === void 0 ? void 0 : microdata.contentUpdatedDate, contentUrl: ((_a = video === null || video === void 0 ? void 0 : video.src) === null || _a === void 0 ? void 0 : _a[0]) || videoIframe || youtube, thumbnailUrl: previewImg }, (videoMicrodata || {})), { name: name ? sanitizeMicrodata(name) : name, description: description ? sanitizeMicrodata(description) : description }));
87
+ return video || youtube || videoIframe ? (React.createElement("script", { type: "application/ld+json" }, json)) : null;
88
+ }, [microdata === null || microdata === void 0 ? void 0 : microdata.contentUpdatedDate, previewImg, video, videoIframe, videoMicrodata, youtube]);
89
+ return (React.createElement("div", { className: b(null, className), style: { backgroundColor: color }, "data-qa": qa },
90
+ videoMicrodataScript,
91
+ content));
81
92
  };
82
93
  export default Media;
@@ -1,6 +1,6 @@
1
1
  import { QAProps, TextSize, TitleItemProps } from '../../models';
2
2
  import './TitleItem.css';
3
- export declare function getArrowSize(size: TextSize, isMobile: boolean): 16 | 24 | 13 | 22 | 26 | 38 | 20;
3
+ export declare function getArrowSize(size: TextSize, isMobile: boolean): 20 | 24 | 16 | 13 | 22 | 26 | 38;
4
4
  export interface TitleItemFullProps extends TitleItemProps, QAProps {
5
5
  className?: string;
6
6
  onClick?: () => void;
@@ -11,6 +11,9 @@ export interface PageConstructorProps {
11
11
  custom?: CustomConfig;
12
12
  renderMenu?: () => React.ReactNode;
13
13
  navigation?: NavigationData;
14
+ microdata?: {
15
+ contentUpdatedDate?: string;
16
+ };
14
17
  }
15
18
  export declare const Constructor: (props: PageConstructorProps) => JSX.Element;
16
19
  export declare const PageConstructor: (props: PageConstructorProps) => JSX.Element;
@@ -18,7 +18,7 @@ import { ConstructorRow } from './components/ConstructorRow';
18
18
  import './PageConstructor.css';
19
19
  const b = cnBlock('page-constructor');
20
20
  export const Constructor = (props) => {
21
- const { content: { blocks = [], background } = {}, renderMenu, shouldRenderBlock, navigation, custom, } = props;
21
+ const { content: { blocks = [], background } = {}, renderMenu, shouldRenderBlock, navigation, custom, microdata, } = props;
22
22
  const { context } = useMemo(() => ({
23
23
  context: {
24
24
  blockTypes: [...BlockTypes, ...getCustomTypes(['blocks', 'headers'], custom)],
@@ -35,8 +35,9 @@ export const Constructor = (props) => {
35
35
  customization: {
36
36
  decorators: custom === null || custom === void 0 ? void 0 : custom.decorators,
37
37
  },
38
+ microdata,
38
39
  },
39
- }), [custom, shouldRenderBlock]);
40
+ }), [custom, shouldRenderBlock, microdata]);
40
41
  const theme = useTheme();
41
42
  const header = getHeaderBlock(blocks, context.headerBlockTypes);
42
43
  const restBlocks = getOrderedBlocks(blocks, context.headerBlockTypes);
@@ -11,5 +11,8 @@ export interface InnerContextType {
11
11
  loadables?: LoadableConfig;
12
12
  shouldRenderBlock?: ShouldRenderBlock;
13
13
  customization?: Pick<CustomConfig, 'decorators'>;
14
+ microdata?: {
15
+ contentUpdatedDate?: string;
16
+ };
14
17
  }
15
18
  export declare const InnerContext: React.Context<InnerContextType>;
@@ -6,4 +6,5 @@ export const InnerContext = React.createContext({
6
6
  navigationBlockTypes: [],
7
7
  itemMap: {},
8
8
  navItemMap: {},
9
+ microdata: {},
9
10
  });
@@ -5,4 +5,4 @@ export interface GridColumnProps extends GridColumnClassParams, Refable<HTMLDivE
5
5
  style?: CSSProperties;
6
6
  children?: React.ReactNode;
7
7
  }
8
- export declare const Col: React.ForwardRefExoticComponent<Pick<React.PropsWithChildren<GridColumnProps>, "style" | "children" | "sizes" | "className" | "hidden" | "role" | "qa" | "reset" | "visible" | "offsets" | "orders" | "alignSelf" | "justifyContent"> & React.RefAttributes<HTMLDivElement>>;
8
+ export declare const Col: React.ForwardRefExoticComponent<Pick<React.PropsWithChildren<GridColumnProps>, "style" | "children" | "sizes" | "className" | "hidden" | "role" | "qa" | "offsets" | "orders" | "visible" | "alignSelf" | "justifyContent" | "reset"> & React.RefAttributes<HTMLDivElement>>;
@@ -207,6 +207,14 @@ export interface MediaComponentIframeProps {
207
207
  }
208
208
  export interface MediaProps extends Animatable, Partial<MediaComponentDataLensProps>, Partial<MediaComponentYoutubeProps>, Partial<MediaComponentVideoIframeProps>, Partial<MediaComponentImageProps>, Partial<MediaComponentIframeProps>, Partial<MediaComponentVideoProps> {
209
209
  color?: string;
210
+ videoMicrodata?: {
211
+ name?: string;
212
+ description?: string;
213
+ duration?: string;
214
+ uploadDate?: string;
215
+ contentUrl?: string;
216
+ thumbnailUrl?: string;
217
+ };
210
218
  }
211
219
  export interface BackgroundMediaProps extends MediaProps, Animatable, QAProps {
212
220
  fullWidthMedia?: boolean;
@@ -4,6 +4,7 @@ import { useUniqId } from '@gravity-ui/uikit';
4
4
  import { FullscreenMedia, IconWrapper, Media, MetaInfo } from '../../components';
5
5
  import { useTheme } from '../../context/theme';
6
6
  import { block, getThemedValue } from '../../utils';
7
+ import { mergeVideoMicrodata } from '../../utils/microdata';
7
8
  import Content from '../Content/Content';
8
9
  import { getLayoutItemLinks, hasFullscreen, showFullscreenIcon } from './utils';
9
10
  import './LayoutItem.css';
@@ -21,9 +22,14 @@ const LayoutItem = (_a) => {
21
22
  return null;
22
23
  }
23
24
  const themedMedia = getThemedValue(media, theme);
25
+ const { title } = content;
26
+ const mediaWithMicrodata = mergeVideoMicrodata(themedMedia, {
27
+ name: typeof title === 'string' ? title : title === null || title === void 0 ? void 0 : title.text,
28
+ description: content.text,
29
+ });
24
30
  return fullscreen && hasFullscreen(themedMedia) ? (React.createElement(FullscreenMedia, { showFullscreenIcon: showFullscreenIcon(themedMedia) }, (_a = {}) => {
25
31
  var { className: mediaClassName, fullscreen: _fullscreen } = _a, fullscreenMediaProps = __rest(_a, ["className", "fullscreen"]);
26
- return (React.createElement(Media, Object.assign({}, themedMedia, fullscreenMediaProps, { className: b('media', { border }, mediaClassName), analyticsEvents: analyticsEvents })));
32
+ return (React.createElement(Media, Object.assign({}, mediaWithMicrodata, fullscreenMediaProps, { className: b('media', { border }, mediaClassName), analyticsEvents: analyticsEvents })));
27
33
  })) : (React.createElement(Media, Object.assign({}, themedMedia, { className: b('media', { border }), analyticsEvents: analyticsEvents })));
28
34
  };
29
35
  return (React.createElement("div", { className: b(null, className) },
@@ -0,0 +1,3 @@
1
+ import { MediaProps } from '../models';
2
+ export declare const mergeVideoMicrodata: (values?: MediaProps, newValues?: MediaProps['videoMicrodata']) => MediaProps;
3
+ export declare function sanitizeMicrodata(html: string): string;
@@ -0,0 +1,5 @@
1
+ import sanitize from 'sanitize-html';
2
+ export const mergeVideoMicrodata = (values = {}, newValues = {}) => (Object.assign(Object.assign({}, values), { videoMicrodata: Object.assign(Object.assign({}, newValues), (values.videoMicrodata || {})) }));
3
+ export function sanitizeMicrodata(html) {
4
+ return html && sanitize(html, { allowedTags: [], allowedAttributes: {} });
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/page-constructor",
3
- "version": "5.29.1",
3
+ "version": "5.30.0-alpha.0",
4
4
  "description": "Gravity UI Page Constructor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -207,6 +207,14 @@ export interface MediaComponentIframeProps {
207
207
  }
208
208
  export interface MediaProps extends Animatable, Partial<MediaComponentDataLensProps>, Partial<MediaComponentYoutubeProps>, Partial<MediaComponentVideoIframeProps>, Partial<MediaComponentImageProps>, Partial<MediaComponentIframeProps>, Partial<MediaComponentVideoProps> {
209
209
  color?: string;
210
+ videoMicrodata?: {
211
+ name?: string;
212
+ description?: string;
213
+ duration?: string;
214
+ uploadDate?: string;
215
+ contentUrl?: string;
216
+ thumbnailUrl?: string;
217
+ };
210
218
  }
211
219
  export interface BackgroundMediaProps extends MediaProps, Animatable, QAProps {
212
220
  fullWidthMedia?: boolean;