@gravity-ui/page-constructor 1.16.2 → 1.16.4

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 (35) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/build/cjs/components/Foldable/Foldable.css +4 -1
  3. package/build/cjs/components/Foldable/Foldable.js +7 -10
  4. package/build/cjs/components/HeightCalculator/HeightCalculator.d.ts +4 -0
  5. package/build/cjs/components/HeightCalculator/HeightCalculator.js +4 -0
  6. package/build/cjs/hooks/useHeightCalculator.d.ts +6 -0
  7. package/build/cjs/hooks/useHeightCalculator.js +27 -0
  8. package/build/cjs/models/constructor-items/sub-blocks.d.ts +1 -0
  9. package/build/cjs/sub-blocks/BannerCard/BannerCard.js +2 -1
  10. package/build/cjs/sub-blocks/BasicCard/BasicCard.js +2 -2
  11. package/build/cjs/sub-blocks/BasicCard/schema.d.ts +4 -0
  12. package/build/cjs/sub-blocks/BasicCard/schema.js +4 -1
  13. package/build/cjs/utils/url.d.ts +1 -0
  14. package/build/cjs/utils/url.js +13 -10
  15. package/build/cjs/utils/url.test.d.ts +1 -0
  16. package/build/cjs/utils/url.test.js +76 -0
  17. package/build/esm/components/Foldable/Foldable.css +4 -1
  18. package/build/esm/components/Foldable/Foldable.js +8 -11
  19. package/build/esm/components/HeightCalculator/HeightCalculator.d.ts +4 -0
  20. package/build/esm/components/HeightCalculator/HeightCalculator.js +4 -0
  21. package/build/esm/hooks/useHeightCalculator.d.ts +6 -0
  22. package/build/esm/hooks/useHeightCalculator.js +24 -0
  23. package/build/esm/models/constructor-items/sub-blocks.d.ts +1 -0
  24. package/build/esm/sub-blocks/BannerCard/BannerCard.js +3 -2
  25. package/build/esm/sub-blocks/BasicCard/BasicCard.js +3 -2
  26. package/build/esm/sub-blocks/BasicCard/schema.d.ts +4 -0
  27. package/build/esm/sub-blocks/BasicCard/schema.js +4 -1
  28. package/build/esm/utils/url.d.ts +1 -0
  29. package/build/esm/utils/url.js +11 -9
  30. package/build/esm/utils/url.test.d.ts +1 -0
  31. package/build/esm/utils/url.test.js +74 -0
  32. package/package.json +1 -1
  33. package/server/models/constructor-items/sub-blocks.d.ts +1 -0
  34. package/server/utils/url.d.ts +1 -0
  35. package/server/utils/url.js +13 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.16.4](https://github.com/gravity-ui/page-constructor/compare/v1.16.3...v1.16.4) (2023-02-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **BasicCard:** add target ([#162](https://github.com/gravity-ui/page-constructor/issues/162)) ([569038c](https://github.com/gravity-ui/page-constructor/commit/569038c8ef1c43b7c8b9d6462126997155605b4c))
9
+ * do not set "rel: 'noopener noreferrer'" for local links ([#160](https://github.com/gravity-ui/page-constructor/issues/160)) ([81173a7](https://github.com/gravity-ui/page-constructor/commit/81173a7e237104463971a80e386f5bad37caa551))
10
+
11
+ ## [1.16.3](https://github.com/gravity-ui/page-constructor/compare/v1.16.2...v1.16.3) (2023-02-17)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * add router link for banner button ([#158](https://github.com/gravity-ui/page-constructor/issues/158)) ([a194a40](https://github.com/gravity-ui/page-constructor/commit/a194a40bae0486277d0a862e3a9b415404d75d0b))
17
+ * **Foldable:** fix issues with content height calculation on resize ([#156](https://github.com/gravity-ui/page-constructor/issues/156)) ([e20709e](https://github.com/gravity-ui/page-constructor/commit/e20709e4eb84e7eb620ff2e42dd3e5010d1066a2))
18
+
3
19
  ## [1.16.2](https://github.com/gravity-ui/page-constructor/compare/v1.16.1...v1.16.2) (2023-02-16)
4
20
 
5
21
 
@@ -1,7 +1,10 @@
1
1
  /* use this for style redefinitions to awoid problems with
2
2
  unpredictable css rules order in build */
3
- .pc-foldable-block__content-container {
3
+ .pc-foldable-block {
4
4
  height: 0;
5
5
  overflow-y: hidden;
6
6
  transition: height 300ms, margin-bottom 300ms;
7
+ }
8
+ .pc-foldable-block__content-container {
9
+ overflow: auto;
7
10
  }
@@ -3,21 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const react_1 = tslib_1.__importStar(require("react"));
5
5
  const utils_1 = require("../../utils");
6
- const HeightCalculator_1 = tslib_1.__importDefault(require("../../components/HeightCalculator/HeightCalculator"));
6
+ const useHeightCalculator_1 = tslib_1.__importDefault(require("../../hooks/useHeightCalculator"));
7
7
  const b = (0, utils_1.block)('foldable-block');
8
8
  const Foldable = ({ isOpened, children, className }) => {
9
+ const blockRef = (0, react_1.useRef)(null);
9
10
  const contentRef = (0, react_1.useRef)(null);
10
- const [contentHeight, setContentHeight] = (0, react_1.useState)();
11
- const onHeightCalculation = (0, react_1.useCallback)((height) => {
12
- setContentHeight(height);
13
- }, []);
11
+ const contentHeight = (0, useHeightCalculator_1.default)(contentRef);
14
12
  (0, react_1.useEffect)(() => {
15
- if (contentRef && contentRef.current) {
16
- contentRef.current.style.height = isOpened ? `${contentHeight}px` : '0';
13
+ if (blockRef && blockRef.current) {
14
+ blockRef.current.style.height = isOpened && contentHeight ? `${contentHeight}px` : '0';
17
15
  }
18
16
  }, [isOpened, contentHeight]);
19
- return (react_1.default.createElement("div", { className: b(null, className) },
20
- react_1.default.createElement("div", { ref: contentRef, className: b('content-container', { open: isOpened }) }, children),
21
- react_1.default.createElement(HeightCalculator_1.default, { onCalculate: onHeightCalculation }, children)));
17
+ return (react_1.default.createElement("div", { ref: blockRef, className: b({ open: isOpened }, className) },
18
+ react_1.default.createElement("div", { ref: contentRef, className: b('content-container') }, children)));
22
19
  };
23
20
  exports.default = Foldable;
@@ -2,5 +2,9 @@ import { WithChildren } from '../../models';
2
2
  export interface HeightCalculatorProps {
3
3
  onCalculate: (height: number) => void;
4
4
  }
5
+ /**
6
+ * @deprecated Will be removed, use the useHeightCalculator hook instead.
7
+ * @returns The HeightCalculator component.
8
+ */
5
9
  declare const HeightCalculator: ({ onCalculate, children }: WithChildren<HeightCalculatorProps>) => JSX.Element | null;
6
10
  export default HeightCalculator;
@@ -5,6 +5,10 @@ const react_1 = tslib_1.__importStar(require("react"));
5
5
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
6
  const utils_1 = require("../../utils");
7
7
  const b = (0, utils_1.block)('height-calculator');
8
+ /**
9
+ * @deprecated Will be removed, use the useHeightCalculator hook instead.
10
+ * @returns The HeightCalculator component.
11
+ */
8
12
  const HeightCalculator = ({ onCalculate, children }) => {
9
13
  const [isCalculating, setIsCalculating] = (0, react_1.useState)(true);
10
14
  const container = (0, react_1.useRef)(null);
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type HeightCalculatorOptions = {
3
+ recalculateOnResizeDelay: number;
4
+ };
5
+ declare const useHeightCalculator: (containerRef: React.RefObject<HTMLElement>, options?: HeightCalculatorOptions) => number | undefined;
6
+ export default useHeightCalculator;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const react_1 = require("react");
5
+ const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
+ const DEFAULT_RECALCULATE_ON_RESIZE_DELAY = 1000;
7
+ const DEFAULT_OPTIONS = {
8
+ recalculateOnResizeDelay: DEFAULT_RECALCULATE_ON_RESIZE_DELAY,
9
+ };
10
+ const useHeightCalculator = (containerRef, options = DEFAULT_OPTIONS) => {
11
+ const recalculateOnResizeDelay = options.recalculateOnResizeDelay;
12
+ const [containerHeight, setContainerHeight] = (0, react_1.useState)(undefined);
13
+ const calculateContainerHeight = (0, react_1.useCallback)(() => {
14
+ if (containerRef.current && containerRef.current.offsetHeight !== containerHeight)
15
+ setContainerHeight(containerRef.current.offsetHeight);
16
+ }, [containerRef, containerHeight, setContainerHeight]);
17
+ (0, react_1.useEffect)(() => {
18
+ const handleResize = lodash_1.default.debounce(calculateContainerHeight, recalculateOnResizeDelay);
19
+ calculateContainerHeight();
20
+ window.addEventListener('resize', handleResize);
21
+ return () => {
22
+ window.removeEventListener('resize', handleResize);
23
+ };
24
+ }, [calculateContainerHeight, recalculateOnResizeDelay]);
25
+ return containerHeight;
26
+ };
27
+ exports.default = useHeightCalculator;
@@ -106,6 +106,7 @@ export interface BackgroundCardProps extends CardBaseProps, Omit<ContentBlockPro
106
106
  export interface BasicCardProps extends CardBaseProps, Omit<ContentBlockProps, 'colSizes' | 'centered' | 'size' | 'theme'> {
107
107
  url: string;
108
108
  icon?: ImageProps;
109
+ target?: string;
109
110
  }
110
111
  export interface BannerCardProps {
111
112
  title: string;
@@ -21,7 +21,8 @@ const BannerCard = (props) => {
21
21
  react_1.default.createElement("h2", { className: b('title') },
22
22
  react_1.default.createElement(components_1.HTML, null, title)),
23
23
  subtitle && (react_1.default.createElement(components_1.YFMWrapper, { className: b('subtitle'), content: subtitle, modifiers: { constructor: true } }))),
24
- react_1.default.createElement(components_1.Button, { className: b('button'), theme: "raised", size: "xl", text: text, url: url, target: target })),
24
+ react_1.default.createElement(components_1.RouterLink, { href: url },
25
+ react_1.default.createElement(components_1.Button, { className: b('button'), theme: "raised", size: "xl", text: text, url: url, target: target }))),
25
26
  react_1.default.createElement(components_1.BackgroundImage, { className: b('image'), src: (0, utils_1.getThemedValue)(image, theme), disableCompress: disableCompress }))));
26
27
  };
27
28
  exports.BannerCard = BannerCard;
@@ -9,9 +9,9 @@ const __1 = require("../");
9
9
  const utils_2 = require("../../components/Media/Image/utils");
10
10
  const b = (0, utils_1.block)('basic-card');
11
11
  const BasicCard = (props) => {
12
- const { url, title, text, border, icon, additionalInfo, links, buttons } = props;
12
+ const { title, text, icon, additionalInfo, links, buttons } = props, cardParams = tslib_1.__rest(props, ["title", "text", "icon", "additionalInfo", "links", "buttons"]);
13
13
  const iconProps = icon && (0, utils_2.getMediaImage)(icon);
14
- return (react_1.default.createElement(CardBase_1.default, { className: b(), url: url, border: border },
14
+ return (react_1.default.createElement(CardBase_1.default, Object.assign({ className: b() }, cardParams),
15
15
  react_1.default.createElement(CardBase_1.default.Content, null,
16
16
  iconProps && react_1.default.createElement(Image_1.default, Object.assign({}, iconProps, { className: b('icon') })),
17
17
  react_1.default.createElement(__1.Content, { title: title, text: text, additionalInfo: additionalInfo, links: links, buttons: buttons, colSizes: { all: 12, md: 12 }, size: "s" }))));
@@ -19,6 +19,10 @@ export declare const BasicCard: {
19
19
  pattern: string;
20
20
  })[];
21
21
  };
22
+ target: {
23
+ type: string;
24
+ enum: string[];
25
+ };
22
26
  title: {
23
27
  oneOf: ({
24
28
  type: string;
@@ -13,6 +13,9 @@ exports.BasicCard = {
13
13
  required: ['url'],
14
14
  properties: Object.assign(Object.assign(Object.assign(Object.assign({}, common_1.BaseProps), common_1.CardBase), BasicCardContentProps), { url: {
15
15
  type: 'string',
16
- }, icon: schema_2.ImageProps }),
16
+ }, icon: schema_2.ImageProps, target: {
17
+ type: 'string',
18
+ enum: ['_blank', '_parent', '_top', '_self'],
19
+ } }),
17
20
  },
18
21
  };
@@ -6,6 +6,7 @@ export declare const EXTERNAL_LINK_PROPS: {
6
6
  export declare function getLinkProps(url: string, hostname?: string, target?: string): {
7
7
  target: string | undefined;
8
8
  };
9
+ export declare function isAbsoluteUrl(url: string | URL): boolean;
9
10
  export declare function isLinkExternal(url: string, routerHostname?: string): boolean;
10
11
  export declare function getNonLocaleHostName(hostname: string): string;
11
12
  export declare function setUrlTld(url: string, tld?: string): string;
@@ -1,25 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getAbsolutePath = exports.getPageSearchParams = exports.setUrlTld = exports.getNonLocaleHostName = exports.isLinkExternal = exports.getLinkProps = exports.EXTERNAL_LINK_PROPS = void 0;
3
+ exports.getAbsolutePath = exports.getPageSearchParams = exports.setUrlTld = exports.getNonLocaleHostName = exports.isLinkExternal = exports.isAbsoluteUrl = exports.getLinkProps = exports.EXTERNAL_LINK_PROPS = void 0;
4
4
  const url_1 = require("url");
5
+ const EXAMPLE_URL = 'https://example.org';
5
6
  exports.EXTERNAL_LINK_PROPS = { target: '_blank', rel: 'noopener noreferrer' };
6
7
  function getLinkProps(url, hostname, target) {
7
8
  let linkProps = { target };
8
- if (target === '_blank' || isLinkExternal(url, hostname)) {
9
+ if (isLinkExternal(url, hostname)) {
9
10
  linkProps = Object.assign(Object.assign({}, linkProps), exports.EXTERNAL_LINK_PROPS);
10
11
  }
11
12
  return linkProps;
12
13
  }
13
14
  exports.getLinkProps = getLinkProps;
15
+ function isAbsoluteUrl(url) {
16
+ // Using example URL as base for relative links
17
+ const urlObj = new URL(url, EXAMPLE_URL);
18
+ return (
19
+ // Compare url origin with example and check that original url was not example one
20
+ urlObj.origin !== EXAMPLE_URL || (typeof url === 'string' && url.startsWith(EXAMPLE_URL)));
21
+ }
22
+ exports.isAbsoluteUrl = isAbsoluteUrl;
14
23
  function isLinkExternal(url, routerHostname) {
15
- if (!routerHostname) {
16
- return true;
17
- }
18
- const { hostname } = (0, url_1.parse)(url);
19
- if (!hostname) {
20
- return false;
21
- }
22
- return getNonLocaleHostName(hostname) !== getNonLocaleHostName(routerHostname);
24
+ return (isAbsoluteUrl(url) &&
25
+ getNonLocaleHostName(new URL(url).hostname) !== getNonLocaleHostName(routerHostname !== null && routerHostname !== void 0 ? routerHostname : ''));
23
26
  }
24
27
  exports.isLinkExternal = isLinkExternal;
25
28
  function getNonLocaleHostName(hostname) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const url_1 = require("./url");
4
+ describe('URL utils check', () => {
5
+ test.each([
6
+ ['https://user:pass@sub.example.com:8080/p/a/t/h?query=string&query2=1#hash', true],
7
+ ['http://example.net/path', true],
8
+ ['/p/a/t/h?query=string&query2=1#hash', false],
9
+ ['/path', false],
10
+ ['path', false],
11
+ ])("isAbsoluteUrl('%s') should return '%s'", (url, result) => {
12
+ expect((0, url_1.isAbsoluteUrl)(url)).toEqual(result);
13
+ });
14
+ test.each([
15
+ [
16
+ 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string&query2=1#hash',
17
+ 'example.net',
18
+ true,
19
+ ],
20
+ [
21
+ 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string&query2=1#hash',
22
+ 'sub.example.com',
23
+ false,
24
+ ],
25
+ [
26
+ 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string&query2=1#hash',
27
+ undefined,
28
+ true,
29
+ ],
30
+ ['http://example.net/path', 'example.net', false],
31
+ ['http://example.net/path', 'sub.example.com', true],
32
+ ['http://example.net/path', undefined, true],
33
+ ['/p/a/t/h?query=string&query2=1#hash', 'example.net', false],
34
+ ['/p/a/t/h?query=string&query2=1#hash', undefined, false],
35
+ ['/path', 'example.net', false],
36
+ ['/path', undefined, false],
37
+ ['path', 'example.net', false],
38
+ ['path', undefined, false],
39
+ ])("isLinkExternal('%s', '%s') should return '%s'", (url, hostname, result) => {
40
+ expect((0, url_1.isLinkExternal)(url, hostname)).toEqual(result);
41
+ });
42
+ test.each([
43
+ ['http://example.net/path', 'example.net', '_blank', { target: '_blank' }],
44
+ ['http://example.net/path', 'example.net', undefined, {}],
45
+ [
46
+ 'http://example.net/path',
47
+ 'example.com',
48
+ '_blank',
49
+ { target: '_blank', rel: 'noopener noreferrer' },
50
+ ],
51
+ [
52
+ 'http://example.net/path',
53
+ 'example.com',
54
+ undefined,
55
+ { target: '_blank', rel: 'noopener noreferrer' },
56
+ ],
57
+ [
58
+ 'http://example.net/path',
59
+ undefined,
60
+ '_blank',
61
+ { target: '_blank', rel: 'noopener noreferrer' },
62
+ ],
63
+ [
64
+ 'http://example.net/path',
65
+ undefined,
66
+ undefined,
67
+ { target: '_blank', rel: 'noopener noreferrer' },
68
+ ],
69
+ ['/path', 'example.net', '_blank', { target: '_blank' }],
70
+ ['/path', 'example.net', undefined, {}],
71
+ ['/path', undefined, '_blank', { target: '_blank' }],
72
+ ['/path', undefined, undefined, {}],
73
+ ])("getLinkProps('%s', '%s', '%s') should return '%s'", (url, hostname, target, result) => {
74
+ expect((0, url_1.getLinkProps)(url, hostname, target)).toEqual(result);
75
+ });
76
+ });
@@ -1,7 +1,10 @@
1
1
  /* use this for style redefinitions to awoid problems with
2
2
  unpredictable css rules order in build */
3
- .pc-foldable-block__content-container {
3
+ .pc-foldable-block {
4
4
  height: 0;
5
5
  overflow-y: hidden;
6
6
  transition: height 300ms, margin-bottom 300ms;
7
+ }
8
+ .pc-foldable-block__content-container {
9
+ overflow: auto;
7
10
  }
@@ -1,21 +1,18 @@
1
- import React, { useRef, useState, useCallback, useEffect } from 'react';
1
+ import React, { useRef, useEffect } from 'react';
2
2
  import { block } from '../../utils';
3
- import HeightCalculator from '../../components/HeightCalculator/HeightCalculator';
3
+ import useHeightCalculator from '../../hooks/useHeightCalculator';
4
4
  import './Foldable.css';
5
5
  const b = block('foldable-block');
6
6
  const Foldable = ({ isOpened, children, className }) => {
7
+ const blockRef = useRef(null);
7
8
  const contentRef = useRef(null);
8
- const [contentHeight, setContentHeight] = useState();
9
- const onHeightCalculation = useCallback((height) => {
10
- setContentHeight(height);
11
- }, []);
9
+ const contentHeight = useHeightCalculator(contentRef);
12
10
  useEffect(() => {
13
- if (contentRef && contentRef.current) {
14
- contentRef.current.style.height = isOpened ? `${contentHeight}px` : '0';
11
+ if (blockRef && blockRef.current) {
12
+ blockRef.current.style.height = isOpened && contentHeight ? `${contentHeight}px` : '0';
15
13
  }
16
14
  }, [isOpened, contentHeight]);
17
- return (React.createElement("div", { className: b(null, className) },
18
- React.createElement("div", { ref: contentRef, className: b('content-container', { open: isOpened }) }, children),
19
- React.createElement(HeightCalculator, { onCalculate: onHeightCalculation }, children)));
15
+ return (React.createElement("div", { ref: blockRef, className: b({ open: isOpened }, className) },
16
+ React.createElement("div", { ref: contentRef, className: b('content-container') }, children)));
20
17
  };
21
18
  export default Foldable;
@@ -3,5 +3,9 @@ import './HeightCalculator.css';
3
3
  export interface HeightCalculatorProps {
4
4
  onCalculate: (height: number) => void;
5
5
  }
6
+ /**
7
+ * @deprecated Will be removed, use the useHeightCalculator hook instead.
8
+ * @returns The HeightCalculator component.
9
+ */
6
10
  declare const HeightCalculator: ({ onCalculate, children }: WithChildren<HeightCalculatorProps>) => JSX.Element | null;
7
11
  export default HeightCalculator;
@@ -3,6 +3,10 @@ import _ from 'lodash';
3
3
  import { block } from '../../utils';
4
4
  import './HeightCalculator.css';
5
5
  const b = block('height-calculator');
6
+ /**
7
+ * @deprecated Will be removed, use the useHeightCalculator hook instead.
8
+ * @returns The HeightCalculator component.
9
+ */
6
10
  const HeightCalculator = ({ onCalculate, children }) => {
7
11
  const [isCalculating, setIsCalculating] = useState(true);
8
12
  const container = useRef(null);
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type HeightCalculatorOptions = {
3
+ recalculateOnResizeDelay: number;
4
+ };
5
+ declare const useHeightCalculator: (containerRef: React.RefObject<HTMLElement>, options?: HeightCalculatorOptions) => number | undefined;
6
+ export default useHeightCalculator;
@@ -0,0 +1,24 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import _ from 'lodash';
3
+ const DEFAULT_RECALCULATE_ON_RESIZE_DELAY = 1000;
4
+ const DEFAULT_OPTIONS = {
5
+ recalculateOnResizeDelay: DEFAULT_RECALCULATE_ON_RESIZE_DELAY,
6
+ };
7
+ const useHeightCalculator = (containerRef, options = DEFAULT_OPTIONS) => {
8
+ const recalculateOnResizeDelay = options.recalculateOnResizeDelay;
9
+ const [containerHeight, setContainerHeight] = useState(undefined);
10
+ const calculateContainerHeight = useCallback(() => {
11
+ if (containerRef.current && containerRef.current.offsetHeight !== containerHeight)
12
+ setContainerHeight(containerRef.current.offsetHeight);
13
+ }, [containerRef, containerHeight, setContainerHeight]);
14
+ useEffect(() => {
15
+ const handleResize = _.debounce(calculateContainerHeight, recalculateOnResizeDelay);
16
+ calculateContainerHeight();
17
+ window.addEventListener('resize', handleResize);
18
+ return () => {
19
+ window.removeEventListener('resize', handleResize);
20
+ };
21
+ }, [calculateContainerHeight, recalculateOnResizeDelay]);
22
+ return containerHeight;
23
+ };
24
+ export default useHeightCalculator;
@@ -106,6 +106,7 @@ export interface BackgroundCardProps extends CardBaseProps, Omit<ContentBlockPro
106
106
  export interface BasicCardProps extends CardBaseProps, Omit<ContentBlockProps, 'colSizes' | 'centered' | 'size' | 'theme'> {
107
107
  url: string;
108
108
  icon?: ImageProps;
109
+ target?: string;
109
110
  }
110
111
  export interface BannerCardProps {
111
112
  title: string;
@@ -1,6 +1,6 @@
1
1
  import React, { useContext } from 'react';
2
2
  import { block, getThemedValue } from '../../utils';
3
- import { Button, YFMWrapper, BackgroundImage, HTML } from '../../components';
3
+ import { Button, YFMWrapper, BackgroundImage, HTML, RouterLink } from '../../components';
4
4
  import { ThemeValueContext } from '../../context/theme/ThemeValueContext';
5
5
  import './BannerCard.css';
6
6
  const b = block('banner-card');
@@ -18,7 +18,8 @@ export const BannerCard = (props) => {
18
18
  React.createElement("h2", { className: b('title') },
19
19
  React.createElement(HTML, null, title)),
20
20
  subtitle && (React.createElement(YFMWrapper, { className: b('subtitle'), content: subtitle, modifiers: { constructor: true } }))),
21
- React.createElement(Button, { className: b('button'), theme: "raised", size: "xl", text: text, url: url, target: target })),
21
+ React.createElement(RouterLink, { href: url },
22
+ React.createElement(Button, { className: b('button'), theme: "raised", size: "xl", text: text, url: url, target: target }))),
22
23
  React.createElement(BackgroundImage, { className: b('image'), src: getThemedValue(image, theme), disableCompress: disableCompress }))));
23
24
  };
24
25
  export default BannerCard;
@@ -1,3 +1,4 @@
1
+ import { __rest } from "tslib";
1
2
  import React from 'react';
2
3
  import { block } from '../../utils';
3
4
  import CardBase from '../../components/CardBase/CardBase';
@@ -7,9 +8,9 @@ import { getMediaImage } from '../../components/Media/Image/utils';
7
8
  import './BasicCard.css';
8
9
  const b = block('basic-card');
9
10
  const BasicCard = (props) => {
10
- const { url, title, text, border, icon, additionalInfo, links, buttons } = props;
11
+ const { title, text, icon, additionalInfo, links, buttons } = props, cardParams = __rest(props, ["title", "text", "icon", "additionalInfo", "links", "buttons"]);
11
12
  const iconProps = icon && getMediaImage(icon);
12
- return (React.createElement(CardBase, { className: b(), url: url, border: border },
13
+ return (React.createElement(CardBase, Object.assign({ className: b() }, cardParams),
13
14
  React.createElement(CardBase.Content, null,
14
15
  iconProps && React.createElement(Image, Object.assign({}, iconProps, { className: b('icon') })),
15
16
  React.createElement(Content, { title: title, text: text, additionalInfo: additionalInfo, links: links, buttons: buttons, colSizes: { all: 12, md: 12 }, size: "s" }))));
@@ -19,6 +19,10 @@ export declare const BasicCard: {
19
19
  pattern: string;
20
20
  })[];
21
21
  };
22
+ target: {
23
+ type: string;
24
+ enum: string[];
25
+ };
22
26
  title: {
23
27
  oneOf: ({
24
28
  type: string;
@@ -9,6 +9,9 @@ export const BasicCard = {
9
9
  required: ['url'],
10
10
  properties: Object.assign(Object.assign(Object.assign(Object.assign({}, BaseProps), CardBase), BasicCardContentProps), { url: {
11
11
  type: 'string',
12
- }, icon: ImageProps }),
12
+ }, icon: ImageProps, target: {
13
+ type: 'string',
14
+ enum: ['_blank', '_parent', '_top', '_self'],
15
+ } }),
13
16
  },
14
17
  };
@@ -6,6 +6,7 @@ export declare const EXTERNAL_LINK_PROPS: {
6
6
  export declare function getLinkProps(url: string, hostname?: string, target?: string): {
7
7
  target: string | undefined;
8
8
  };
9
+ export declare function isAbsoluteUrl(url: string | URL): boolean;
9
10
  export declare function isLinkExternal(url: string, routerHostname?: string): boolean;
10
11
  export declare function getNonLocaleHostName(hostname: string): string;
11
12
  export declare function setUrlTld(url: string, tld?: string): string;
@@ -1,21 +1,23 @@
1
1
  import { parse, format } from 'url';
2
+ const EXAMPLE_URL = 'https://example.org';
2
3
  export const EXTERNAL_LINK_PROPS = { target: '_blank', rel: 'noopener noreferrer' };
3
4
  export function getLinkProps(url, hostname, target) {
4
5
  let linkProps = { target };
5
- if (target === '_blank' || isLinkExternal(url, hostname)) {
6
+ if (isLinkExternal(url, hostname)) {
6
7
  linkProps = Object.assign(Object.assign({}, linkProps), EXTERNAL_LINK_PROPS);
7
8
  }
8
9
  return linkProps;
9
10
  }
11
+ export function isAbsoluteUrl(url) {
12
+ // Using example URL as base for relative links
13
+ const urlObj = new URL(url, EXAMPLE_URL);
14
+ return (
15
+ // Compare url origin with example and check that original url was not example one
16
+ urlObj.origin !== EXAMPLE_URL || (typeof url === 'string' && url.startsWith(EXAMPLE_URL)));
17
+ }
10
18
  export function isLinkExternal(url, routerHostname) {
11
- if (!routerHostname) {
12
- return true;
13
- }
14
- const { hostname } = parse(url);
15
- if (!hostname) {
16
- return false;
17
- }
18
- return getNonLocaleHostName(hostname) !== getNonLocaleHostName(routerHostname);
19
+ return (isAbsoluteUrl(url) &&
20
+ getNonLocaleHostName(new URL(url).hostname) !== getNonLocaleHostName(routerHostname !== null && routerHostname !== void 0 ? routerHostname : ''));
19
21
  }
20
22
  export function getNonLocaleHostName(hostname) {
21
23
  return hostname.replace(/\.(ru|com)$/, '');
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ import { getLinkProps, isAbsoluteUrl, isLinkExternal } from './url';
2
+ describe('URL utils check', () => {
3
+ test.each([
4
+ ['https://user:pass@sub.example.com:8080/p/a/t/h?query=string&query2=1#hash', true],
5
+ ['http://example.net/path', true],
6
+ ['/p/a/t/h?query=string&query2=1#hash', false],
7
+ ['/path', false],
8
+ ['path', false],
9
+ ])("isAbsoluteUrl('%s') should return '%s'", (url, result) => {
10
+ expect(isAbsoluteUrl(url)).toEqual(result);
11
+ });
12
+ test.each([
13
+ [
14
+ 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string&query2=1#hash',
15
+ 'example.net',
16
+ true,
17
+ ],
18
+ [
19
+ 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string&query2=1#hash',
20
+ 'sub.example.com',
21
+ false,
22
+ ],
23
+ [
24
+ 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string&query2=1#hash',
25
+ undefined,
26
+ true,
27
+ ],
28
+ ['http://example.net/path', 'example.net', false],
29
+ ['http://example.net/path', 'sub.example.com', true],
30
+ ['http://example.net/path', undefined, true],
31
+ ['/p/a/t/h?query=string&query2=1#hash', 'example.net', false],
32
+ ['/p/a/t/h?query=string&query2=1#hash', undefined, false],
33
+ ['/path', 'example.net', false],
34
+ ['/path', undefined, false],
35
+ ['path', 'example.net', false],
36
+ ['path', undefined, false],
37
+ ])("isLinkExternal('%s', '%s') should return '%s'", (url, hostname, result) => {
38
+ expect(isLinkExternal(url, hostname)).toEqual(result);
39
+ });
40
+ test.each([
41
+ ['http://example.net/path', 'example.net', '_blank', { target: '_blank' }],
42
+ ['http://example.net/path', 'example.net', undefined, {}],
43
+ [
44
+ 'http://example.net/path',
45
+ 'example.com',
46
+ '_blank',
47
+ { target: '_blank', rel: 'noopener noreferrer' },
48
+ ],
49
+ [
50
+ 'http://example.net/path',
51
+ 'example.com',
52
+ undefined,
53
+ { target: '_blank', rel: 'noopener noreferrer' },
54
+ ],
55
+ [
56
+ 'http://example.net/path',
57
+ undefined,
58
+ '_blank',
59
+ { target: '_blank', rel: 'noopener noreferrer' },
60
+ ],
61
+ [
62
+ 'http://example.net/path',
63
+ undefined,
64
+ undefined,
65
+ { target: '_blank', rel: 'noopener noreferrer' },
66
+ ],
67
+ ['/path', 'example.net', '_blank', { target: '_blank' }],
68
+ ['/path', 'example.net', undefined, {}],
69
+ ['/path', undefined, '_blank', { target: '_blank' }],
70
+ ['/path', undefined, undefined, {}],
71
+ ])("getLinkProps('%s', '%s', '%s') should return '%s'", (url, hostname, target, result) => {
72
+ expect(getLinkProps(url, hostname, target)).toEqual(result);
73
+ });
74
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/page-constructor",
3
- "version": "1.16.2",
3
+ "version": "1.16.4",
4
4
  "description": "Gravity UI Page Constructor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -106,6 +106,7 @@ export interface BackgroundCardProps extends CardBaseProps, Omit<ContentBlockPro
106
106
  export interface BasicCardProps extends CardBaseProps, Omit<ContentBlockProps, 'colSizes' | 'centered' | 'size' | 'theme'> {
107
107
  url: string;
108
108
  icon?: ImageProps;
109
+ target?: string;
109
110
  }
110
111
  export interface BannerCardProps {
111
112
  title: string;
@@ -6,6 +6,7 @@ export declare const EXTERNAL_LINK_PROPS: {
6
6
  export declare function getLinkProps(url: string, hostname?: string, target?: string): {
7
7
  target: string | undefined;
8
8
  };
9
+ export declare function isAbsoluteUrl(url: string | URL): boolean;
9
10
  export declare function isLinkExternal(url: string, routerHostname?: string): boolean;
10
11
  export declare function getNonLocaleHostName(hostname: string): string;
11
12
  export declare function setUrlTld(url: string, tld?: string): string;
@@ -1,25 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getAbsolutePath = exports.getPageSearchParams = exports.setUrlTld = exports.getNonLocaleHostName = exports.isLinkExternal = exports.getLinkProps = exports.EXTERNAL_LINK_PROPS = void 0;
3
+ exports.getAbsolutePath = exports.getPageSearchParams = exports.setUrlTld = exports.getNonLocaleHostName = exports.isLinkExternal = exports.isAbsoluteUrl = exports.getLinkProps = exports.EXTERNAL_LINK_PROPS = void 0;
4
4
  const url_1 = require("url");
5
+ const EXAMPLE_URL = 'https://example.org';
5
6
  exports.EXTERNAL_LINK_PROPS = { target: '_blank', rel: 'noopener noreferrer' };
6
7
  function getLinkProps(url, hostname, target) {
7
8
  let linkProps = { target };
8
- if (target === '_blank' || isLinkExternal(url, hostname)) {
9
+ if (isLinkExternal(url, hostname)) {
9
10
  linkProps = Object.assign(Object.assign({}, linkProps), exports.EXTERNAL_LINK_PROPS);
10
11
  }
11
12
  return linkProps;
12
13
  }
13
14
  exports.getLinkProps = getLinkProps;
15
+ function isAbsoluteUrl(url) {
16
+ // Using example URL as base for relative links
17
+ const urlObj = new URL(url, EXAMPLE_URL);
18
+ return (
19
+ // Compare url origin with example and check that original url was not example one
20
+ urlObj.origin !== EXAMPLE_URL || (typeof url === 'string' && url.startsWith(EXAMPLE_URL)));
21
+ }
22
+ exports.isAbsoluteUrl = isAbsoluteUrl;
14
23
  function isLinkExternal(url, routerHostname) {
15
- if (!routerHostname) {
16
- return true;
17
- }
18
- const { hostname } = (0, url_1.parse)(url);
19
- if (!hostname) {
20
- return false;
21
- }
22
- return getNonLocaleHostName(hostname) !== getNonLocaleHostName(routerHostname);
24
+ return (isAbsoluteUrl(url) &&
25
+ getNonLocaleHostName(new URL(url).hostname) !== getNonLocaleHostName(routerHostname !== null && routerHostname !== void 0 ? routerHostname : ''));
23
26
  }
24
27
  exports.isLinkExternal = isLinkExternal;
25
28
  function getNonLocaleHostName(hostname) {