@cntrl-site/sdk-nextjs 1.10.8 → 1.11.0-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.
- package/cntrl-site-sdk-nextjs-1.9.125.tgz +0 -0
- package/lib/components/StructuredBlockItem/BlockCaption/BlockCaption.js +35 -0
- package/lib/components/StructuredBlockItem/StructuredBlockItem.js +17 -42
- package/lib/components/StructuredBlockItem/StructuredCode/StructuredCode.js +75 -0
- package/lib/components/StructuredBlockItem/StructuredDivider/StructuredDivider.js +29 -0
- package/lib/components/StructuredBlockItem/StructuredEmbed/StructuredVimeoEmbed.js +89 -0
- package/lib/components/StructuredBlockItem/StructuredEmbed/StructuredYoutubeEmbed.js +97 -0
- package/lib/components/StructuredBlockItem/StructuredEmbed/embedFrameStyles.js +36 -0
- package/lib/components/StructuredBlockItem/StructuredHeaderImage/StructuredHeaderImage.js +43 -0
- package/lib/components/StructuredBlockItem/StructuredImage/StructuredImage.js +65 -0
- package/lib/components/StructuredBlockItem/StructuredRichText/StructuredRichText.js +46 -0
- package/lib/components/StructuredBlockItem/StructuredVideo/StructuredVideo.js +74 -0
- package/lib/components/StructuredBlockItem/blockPlacement.js +41 -0
- package/lib/components/StructuredBlockItem/blocksMap.js +29 -0
- package/lib/components/StructuredBlockItem/textStylesRule.js +46 -0
- package/lib/components/StructuredContent/StructuredContent.js +24 -17
- package/lib/components/StructuredContent/StructuredHeader.js +95 -0
- package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +2 -15
- package/lib/utils/RichTextConverter/RichTextConverter.js +52 -14
- package/lib/utils/getVimeoEmbedUrl.js +18 -0
- package/package.json +3 -3
- package/src/components/StructuredBlockItem/BlockCaption/BlockCaption.tsx +42 -0
- package/src/components/StructuredBlockItem/StructuredBlockItem.tsx +23 -50
- package/src/components/StructuredBlockItem/StructuredCode/StructuredCode.tsx +86 -0
- package/src/components/StructuredBlockItem/StructuredDivider/StructuredDivider.tsx +31 -0
- package/src/components/StructuredBlockItem/StructuredEmbed/StructuredVimeoEmbed.tsx +101 -0
- package/src/components/StructuredBlockItem/StructuredEmbed/StructuredYoutubeEmbed.tsx +106 -0
- package/src/components/StructuredBlockItem/StructuredEmbed/embedFrameStyles.ts +32 -0
- package/src/components/StructuredBlockItem/StructuredHeaderImage/StructuredHeaderImage.tsx +43 -0
- package/src/components/StructuredBlockItem/StructuredImage/StructuredImage.tsx +75 -0
- package/src/components/StructuredBlockItem/StructuredRichText/StructuredRichText.tsx +53 -0
- package/src/components/StructuredBlockItem/StructuredVideo/StructuredVideo.tsx +106 -0
- package/src/components/StructuredBlockItem/blockPlacement.ts +39 -0
- package/src/components/StructuredBlockItem/blocksMap.ts +30 -0
- package/src/components/StructuredBlockItem/textStylesRule.ts +44 -0
- package/src/components/StructuredContent/StructuredContent.test.tsx +217 -0
- package/src/components/StructuredContent/StructuredContent.tsx +25 -19
- package/src/components/StructuredContent/StructuredHeader.tsx +119 -0
- package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +2 -14
- package/src/utils/RichTextConverter/RichTextConverter.test.tsx +67 -0
- package/src/utils/RichTextConverter/RichTextConverter.tsx +77 -14
- package/src/utils/getVimeoEmbedUrl.ts +16 -0
|
Binary file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BlockCaption = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
11
|
+
const useCntrlContext_1 = require("../../../provider/useCntrlContext");
|
|
12
|
+
const textStylesRule_1 = require("../textStylesRule");
|
|
13
|
+
const CAPTION_GAP_PX = 5;
|
|
14
|
+
/** The caption under an image: as wide as the reading column, or as the image where it is narrower, and centered under it. */
|
|
15
|
+
const BlockCaption = ({ blockId, caption, layoutParams, readingWidth }) => {
|
|
16
|
+
const reactId = (0, react_1.useId)();
|
|
17
|
+
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
18
|
+
const layoutValues = [layoutParams, readingWidth];
|
|
19
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `structured-caption-${blockId}`, children: caption }), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-caption-${blockId}`, children: `
|
|
20
|
+
.structured-caption-${blockId} {
|
|
21
|
+
margin-left: auto;
|
|
22
|
+
margin-right: auto;
|
|
23
|
+
white-space: pre-wrap;
|
|
24
|
+
overflow-wrap: break-word;
|
|
25
|
+
}
|
|
26
|
+
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([params, width], exemplary) => (`
|
|
27
|
+
.structured-caption-${blockId} {
|
|
28
|
+
width: ${width !== undefined ? `min(${width * 100}vw, 100%)` : '100%'};
|
|
29
|
+
margin-top: ${CAPTION_GAP_PX / exemplary * 100}vw;
|
|
30
|
+
}
|
|
31
|
+
${(params === null || params === void 0 ? void 0 : params.captionStyles) ? (0, textStylesRule_1.textStylesRule)(`.structured-caption-${blockId}`, params.captionStyles) : ''}
|
|
32
|
+
`))}
|
|
33
|
+
` })] }));
|
|
34
|
+
};
|
|
35
|
+
exports.BlockCaption = BlockCaption;
|
|
@@ -9,51 +9,26 @@ const sdk_1 = require("@cntrl-site/sdk");
|
|
|
9
9
|
const react_1 = require("react");
|
|
10
10
|
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
11
11
|
const useCntrlContext_1 = require("../../provider/useCntrlContext");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const itemsMap = {
|
|
16
|
-
[sdk_1.StructuredBlockType.Component]: StructuredComponent_1.StructuredComponent,
|
|
17
|
-
[sdk_1.StructuredBlockType.RichText]: noop,
|
|
18
|
-
[sdk_1.StructuredBlockType.Image]: noop,
|
|
19
|
-
[sdk_1.StructuredBlockType.VimeoEmbed]: noop,
|
|
20
|
-
[sdk_1.StructuredBlockType.YoutubeEmbed]: noop,
|
|
21
|
-
};
|
|
22
|
-
const StructuredBlockItem = ({ block, maxWidthMap }) => {
|
|
12
|
+
const blockPlacement_1 = require("./blockPlacement");
|
|
13
|
+
const blocksMap_1 = require("./blocksMap");
|
|
14
|
+
const StructuredBlockItem = ({ block, readingWidth }) => {
|
|
23
15
|
var _a;
|
|
24
16
|
const reactId = (0, react_1.useId)();
|
|
25
17
|
const id = `${reactId}-item-${block.id}`;
|
|
26
18
|
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
27
|
-
const layoutValues = [block.area, block.layoutParams, (_a = block.hidden) !== null && _a !== void 0 ? _a : {}
|
|
28
|
-
const
|
|
29
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: `structured-block-item-${block.id}`, children: [(0, jsx_runtime_1.jsx)(
|
|
30
|
-
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams, hidden
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
left: ${((_b = area.horizontalOffset) !== null && _b !== void 0 ? _b : 0) * 100}vw;
|
|
42
|
-
position: relative;
|
|
43
|
-
z-index: ${area.zIndex};
|
|
44
|
-
}
|
|
45
|
-
`);
|
|
46
|
-
})}
|
|
47
|
-
` })] }));
|
|
19
|
+
const layoutValues = [block.area, block.layoutParams, (_a = block.hidden) !== null && _a !== void 0 ? _a : {}];
|
|
20
|
+
const BlockComponent = blocksMap_1.blocksMap[block.type];
|
|
21
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `structured-block-item-${block.id}`, children: [(0, jsx_runtime_1.jsx)(BlockComponent, { block: block, readingWidth: readingWidth }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
|
|
22
|
+
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams, hidden]) => (`
|
|
23
|
+
.structured-block-item-${block.id} {
|
|
24
|
+
display: ${hidden ? 'none' : 'block'};
|
|
25
|
+
position: relative;
|
|
26
|
+
z-index: ${area.zIndex};
|
|
27
|
+
margin-top: ${area.paddingTop ? `${area.paddingTop * 100}vw` : 0};
|
|
28
|
+
outline: none;
|
|
29
|
+
${(0, blockPlacement_1.blockPlacement)(area, layoutParams)}
|
|
30
|
+
}
|
|
31
|
+
`))}
|
|
32
|
+
` })] }));
|
|
48
33
|
};
|
|
49
34
|
exports.StructuredBlockItem = StructuredBlockItem;
|
|
50
|
-
function getAlignSelf(alignment) {
|
|
51
|
-
switch (alignment) {
|
|
52
|
-
case 'left':
|
|
53
|
-
return 'start';
|
|
54
|
-
case 'right':
|
|
55
|
-
return 'end';
|
|
56
|
-
default:
|
|
57
|
-
return 'center';
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StructuredCode = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
11
|
+
const useCntrlContext_1 = require("../../../provider/useCntrlContext");
|
|
12
|
+
const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
|
|
13
|
+
// the box and its type are fixed, in pixels of the layout, scaled with it like everything drawn in it
|
|
14
|
+
const BOX = { paddingTop: 14, paddingRight: 18, paddingBottom: 16, paddingLeft: 18, radius: 6, border: 1 };
|
|
15
|
+
const CODE_FONT_PX = 14;
|
|
16
|
+
const CODE_FONT = 'ui-monospace, \'SF Mono\', Menlo, Consolas, \'Liberation Mono\', monospace';
|
|
17
|
+
// the default theme's own, for code whose theme could not be loaded
|
|
18
|
+
const DEFAULT_COLORS = { background: '#FFFFFF', foreground: '#000000' };
|
|
19
|
+
const FONT_STYLE_ITALIC = 1;
|
|
20
|
+
const FONT_STYLE_BOLD = 2;
|
|
21
|
+
const FONT_STYLE_UNDERLINE = 4;
|
|
22
|
+
function tokenStyle({ color, fontStyle = 0 }) {
|
|
23
|
+
return {
|
|
24
|
+
color,
|
|
25
|
+
fontStyle: fontStyle & FONT_STYLE_ITALIC ? 'italic' : undefined,
|
|
26
|
+
fontWeight: fontStyle & FONT_STYLE_BOLD ? 'bold' : undefined,
|
|
27
|
+
textDecoration: fontStyle & FONT_STYLE_UNDERLINE ? 'underline' : undefined
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Code in a box coloured by its theme, as it comes from the API: in lines of coloured tokens. */
|
|
31
|
+
const StructuredCode = ({ block }) => {
|
|
32
|
+
const reactId = (0, react_1.useId)();
|
|
33
|
+
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
34
|
+
const [ref, setRef] = (0, react_1.useState)(null);
|
|
35
|
+
(0, useItemGeometry_1.useItemGeometry)(block.id, ref);
|
|
36
|
+
const { lines, colors = DEFAULT_COLORS } = block.commonParams;
|
|
37
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: setRef, className: `structured-code-${block.id}`, children: [(0, jsx_runtime_1.jsx)("pre", { className: `structured-code-text-${block.id}`, children: (0, jsx_runtime_1.jsx)("code", { children: lines.map((line, index) => ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [index > 0 && '\n', line.map((token, tokenIndex) => (0, jsx_runtime_1.jsx)("span", { style: tokenStyle(token), children: token.content }, tokenIndex))] }, index))) }) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-code-${block.id}`, children: `
|
|
38
|
+
.structured-code-${block.id} {
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
width: 100%;
|
|
41
|
+
border-style: solid;
|
|
42
|
+
background: ${colors.background};
|
|
43
|
+
border-color: color-mix(in srgb, ${colors.foreground} 15%, ${colors.background});
|
|
44
|
+
}
|
|
45
|
+
.structured-code-text-${block.id} {
|
|
46
|
+
margin: 0;
|
|
47
|
+
color: ${colors.foreground};
|
|
48
|
+
font-family: ${CODE_FONT};
|
|
49
|
+
line-height: 1.5;
|
|
50
|
+
tab-size: 2;
|
|
51
|
+
white-space: pre-wrap;
|
|
52
|
+
overflow-wrap: anywhere;
|
|
53
|
+
word-break: normal;
|
|
54
|
+
font-variant-ligatures: none;
|
|
55
|
+
letter-spacing: 0;
|
|
56
|
+
}
|
|
57
|
+
.structured-code-text-${block.id} code {
|
|
58
|
+
font: inherit;
|
|
59
|
+
}
|
|
60
|
+
${(0, sdk_1.getLayoutStyles)(layouts, [], (_, exemplary) => {
|
|
61
|
+
const px = (value) => `${value / exemplary * 100}vw`;
|
|
62
|
+
return (`
|
|
63
|
+
.structured-code-${block.id} {
|
|
64
|
+
padding: ${px(BOX.paddingTop)} ${px(BOX.paddingRight)} ${px(BOX.paddingBottom)} ${px(BOX.paddingLeft)};
|
|
65
|
+
border-width: ${px(BOX.border)};
|
|
66
|
+
border-radius: ${px(BOX.radius)};
|
|
67
|
+
}
|
|
68
|
+
.structured-code-text-${block.id} {
|
|
69
|
+
font-size: ${px(CODE_FONT_PX)};
|
|
70
|
+
}
|
|
71
|
+
`);
|
|
72
|
+
})}
|
|
73
|
+
` })] }));
|
|
74
|
+
};
|
|
75
|
+
exports.StructuredCode = StructuredCode;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StructuredDivider = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
11
|
+
const useCntrlContext_1 = require("../../../provider/useCntrlContext");
|
|
12
|
+
const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
|
|
13
|
+
const textStylesRule_1 = require("../textStylesRule");
|
|
14
|
+
/** A line filling its block, which is as tall as the line. */
|
|
15
|
+
const StructuredDivider = ({ block }) => {
|
|
16
|
+
const reactId = (0, react_1.useId)();
|
|
17
|
+
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
18
|
+
const [ref, setRef] = (0, react_1.useState)(null);
|
|
19
|
+
(0, useItemGeometry_1.useItemGeometry)(block.id, ref);
|
|
20
|
+
const layoutValues = [block.layoutParams];
|
|
21
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { ref: setRef, className: `structured-divider-${block.id}` }), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-divider-${block.id}`, children: `
|
|
22
|
+
.structured-divider-${block.id} {
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
}
|
|
26
|
+
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([layoutParams]) => ((0, textStylesRule_1.colorRule)(`.structured-divider-${block.id}`, 'background-color', layoutParams.color)))}
|
|
27
|
+
` })] }));
|
|
28
|
+
};
|
|
29
|
+
exports.StructuredDivider = StructuredDivider;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.StructuredVimeoEmbed = void 0;
|
|
16
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
17
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
18
|
+
const player_1 = __importDefault(require("@vimeo/player"));
|
|
19
|
+
const react_1 = require("react");
|
|
20
|
+
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
21
|
+
const useCntrlContext_1 = require("../../../provider/useCntrlContext");
|
|
22
|
+
const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
|
|
23
|
+
const getVimeoEmbedUrl_1 = require("../../../utils/getVimeoEmbedUrl");
|
|
24
|
+
const useLayoutContext_1 = require("../../useLayoutContext");
|
|
25
|
+
const embedFrameStyles_1 = require("./embedFrameStyles");
|
|
26
|
+
/** A Vimeo video playing as the Vimeo item does, its cover over it until it plays. */
|
|
27
|
+
const StructuredVimeoEmbed = ({ block }) => {
|
|
28
|
+
const reactId = (0, react_1.useId)();
|
|
29
|
+
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
30
|
+
const layoutId = (0, useLayoutContext_1.useLayoutContext)();
|
|
31
|
+
const layoutParams = layoutId ? block.layoutParams[layoutId] : null;
|
|
32
|
+
const { url, coverUrl } = block.commonParams;
|
|
33
|
+
const [ref, setRef] = (0, react_1.useState)(null);
|
|
34
|
+
const [iframe, setIframe] = (0, react_1.useState)(null);
|
|
35
|
+
const player = (0, react_1.useMemo)(() => (iframe ? new player_1.default(iframe) : undefined), [iframe]);
|
|
36
|
+
const [isCoverVisible, setIsCoverVisible] = (0, react_1.useState)(coverUrl !== null);
|
|
37
|
+
(0, useItemGeometry_1.useItemGeometry)(block.id, ref);
|
|
38
|
+
const src = (0, react_1.useMemo)(() => (layoutParams ? (0, getVimeoEmbedUrl_1.getVimeoEmbedUrl)(url, layoutParams) : url), [url, layoutParams]);
|
|
39
|
+
(0, react_1.useEffect)(() => {
|
|
40
|
+
if (!player || !layoutParams)
|
|
41
|
+
return;
|
|
42
|
+
setIsCoverVisible(layoutParams.play !== 'auto' && coverUrl !== null);
|
|
43
|
+
const showCover = () => setIsCoverVisible(coverUrl !== null);
|
|
44
|
+
const handlePause = ({ seconds }) => {
|
|
45
|
+
if (seconds === 0)
|
|
46
|
+
showCover();
|
|
47
|
+
};
|
|
48
|
+
player.on('pause', handlePause);
|
|
49
|
+
player.on('ended', showCover);
|
|
50
|
+
return () => {
|
|
51
|
+
player.off('pause', handlePause);
|
|
52
|
+
player.off('ended', showCover);
|
|
53
|
+
};
|
|
54
|
+
}, [player, layoutParams, coverUrl]);
|
|
55
|
+
const play = () => {
|
|
56
|
+
setIsCoverVisible(false);
|
|
57
|
+
player === null || player === void 0 ? void 0 : player.play();
|
|
58
|
+
};
|
|
59
|
+
const togglePlay = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
if (!player)
|
|
61
|
+
return;
|
|
62
|
+
if (yield player.getPaused()) {
|
|
63
|
+
play();
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
player.pause();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const layoutValues = [block.area, block.layoutParams];
|
|
70
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: setRef, className: `structured-embed-${block.id}`, onMouseEnter: () => {
|
|
71
|
+
if ((layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play) === 'on-hover' && !isCoverVisible)
|
|
72
|
+
player === null || player === void 0 ? void 0 : player.play();
|
|
73
|
+
}, onMouseLeave: () => {
|
|
74
|
+
if ((layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play) === 'on-hover')
|
|
75
|
+
player === null || player === void 0 ? void 0 : player.pause();
|
|
76
|
+
}, children: [coverUrl && isCoverVisible && ((0, jsx_runtime_1.jsx)("img", { className: `structured-embed-cover-${block.id}`, src: coverUrl, alt: "", onClick: (layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play) === 'on-click' ? play : undefined, onMouseEnter: (layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play) === 'on-hover' ? play : undefined })), layoutParams && !layoutParams.controls && (layoutParams.play === 'on-click' || layoutParams.play === 'auto') && ((0, jsx_runtime_1.jsx)("div", { className: `structured-embed-overlay-${block.id}`, onClick: togglePlay })), (0, jsx_runtime_1.jsx)("iframe", { ref: setIframe, className: `structured-embed-frame-${block.id}`, src: src, allow: "autoplay; fullscreen; picture-in-picture;", allowFullScreen: true }), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-vimeo-${block.id}`, children: `
|
|
77
|
+
${(0, embedFrameStyles_1.embedFrameStyles)(block.id)}
|
|
78
|
+
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams]) => (`
|
|
79
|
+
.structured-embed-${block.id} {
|
|
80
|
+
${area.height === undefined ? 'aspect-ratio: 16 / 9;' : 'height: 100%;'}
|
|
81
|
+
opacity: ${layoutParams.opacity};
|
|
82
|
+
}
|
|
83
|
+
.structured-embed-frame-${block.id} {
|
|
84
|
+
border-radius: ${layoutParams.radius * 100}vw;
|
|
85
|
+
}
|
|
86
|
+
`))}
|
|
87
|
+
` })] }));
|
|
88
|
+
};
|
|
89
|
+
exports.StructuredVimeoEmbed = StructuredVimeoEmbed;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StructuredYoutubeEmbed = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
11
|
+
const useCntrlContext_1 = require("../../../provider/useCntrlContext");
|
|
12
|
+
const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
|
|
13
|
+
const getValidYoutubeUrl_1 = require("../../../utils/getValidYoutubeUrl");
|
|
14
|
+
const useYouTubeIframeApi_1 = require("../../../utils/Youtube/useYouTubeIframeApi");
|
|
15
|
+
const YoutubeIframeApi_1 = require("../../../utils/Youtube/YoutubeIframeApi");
|
|
16
|
+
const useLayoutContext_1 = require("../../useLayoutContext");
|
|
17
|
+
const embedFrameStyles_1 = require("./embedFrameStyles");
|
|
18
|
+
/** A YouTube video playing as the YouTube item does, its cover over it until it plays. */
|
|
19
|
+
const StructuredYoutubeEmbed = ({ block }) => {
|
|
20
|
+
const reactId = (0, react_1.useId)();
|
|
21
|
+
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
22
|
+
const layoutId = (0, useLayoutContext_1.useLayoutContext)();
|
|
23
|
+
const layoutParams = layoutId ? block.layoutParams[layoutId] : null;
|
|
24
|
+
const { url, coverUrl } = block.commonParams;
|
|
25
|
+
const YT = (0, useYouTubeIframeApi_1.useYouTubeIframeApi)();
|
|
26
|
+
const [ref, setRef] = (0, react_1.useState)(null);
|
|
27
|
+
const [frame, setFrame] = (0, react_1.useState)(null);
|
|
28
|
+
const [player, setPlayer] = (0, react_1.useState)(undefined);
|
|
29
|
+
const [isCoverVisible, setIsCoverVisible] = (0, react_1.useState)(coverUrl !== null);
|
|
30
|
+
(0, useItemGeometry_1.useItemGeometry)(block.id, ref);
|
|
31
|
+
(0, react_1.useEffect)(() => {
|
|
32
|
+
if (!YT || !frame || !layoutParams)
|
|
33
|
+
return;
|
|
34
|
+
const { play, controls } = layoutParams;
|
|
35
|
+
setIsCoverVisible(play !== 'auto' && coverUrl !== null);
|
|
36
|
+
const placeholder = document.createElement('div');
|
|
37
|
+
frame.appendChild(placeholder);
|
|
38
|
+
const created = new YT.Player(placeholder, {
|
|
39
|
+
videoId: (0, getValidYoutubeUrl_1.getYoutubeId)(new URL(url)),
|
|
40
|
+
playerVars: {
|
|
41
|
+
autoplay: play === 'auto' ? '1' : '0',
|
|
42
|
+
controls: controls ? '1' : '0'
|
|
43
|
+
},
|
|
44
|
+
events: {
|
|
45
|
+
onStateChange: (event) => {
|
|
46
|
+
if (play !== 'auto')
|
|
47
|
+
return;
|
|
48
|
+
if (event.data === YoutubeIframeApi_1.YTPlayerState.Playing)
|
|
49
|
+
setIsCoverVisible(false);
|
|
50
|
+
if (event.data === YoutubeIframeApi_1.YTPlayerState.Paused || event.data === YoutubeIframeApi_1.YTPlayerState.Unstarted)
|
|
51
|
+
setIsCoverVisible(coverUrl !== null);
|
|
52
|
+
},
|
|
53
|
+
onReady: (event) => {
|
|
54
|
+
setPlayer(event.target);
|
|
55
|
+
// only a video started by a click may play with sound
|
|
56
|
+
if (play !== 'on-click')
|
|
57
|
+
created.mute();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return () => {
|
|
62
|
+
var _a;
|
|
63
|
+
setPlayer(undefined);
|
|
64
|
+
created.destroy();
|
|
65
|
+
(_a = placeholder.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(placeholder);
|
|
66
|
+
};
|
|
67
|
+
}, [YT, frame, layoutParams, url, coverUrl]);
|
|
68
|
+
const play = () => {
|
|
69
|
+
setIsCoverVisible(false);
|
|
70
|
+
player === null || player === void 0 ? void 0 : player.playVideo();
|
|
71
|
+
};
|
|
72
|
+
const layoutValues = [block.area, block.layoutParams];
|
|
73
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: setRef, className: `structured-embed-${block.id}`, onMouseEnter: () => {
|
|
74
|
+
if ((layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play) === 'on-hover' && !isCoverVisible)
|
|
75
|
+
player === null || player === void 0 ? void 0 : player.playVideo();
|
|
76
|
+
}, onMouseLeave: () => {
|
|
77
|
+
if ((layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play) === 'on-hover')
|
|
78
|
+
player === null || player === void 0 ? void 0 : player.pauseVideo();
|
|
79
|
+
}, children: [coverUrl && isCoverVisible && ((0, jsx_runtime_1.jsx)("img", { className: `structured-embed-cover-${block.id}`, src: coverUrl, alt: "", onClick: (layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play) === 'on-click' ? play : undefined, onMouseEnter: (layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play) === 'on-hover' ? play : undefined })), (0, jsx_runtime_1.jsx)("div", { ref: setFrame, className: `structured-embed-frame-${block.id}` }), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-youtube-${block.id}`, children: `
|
|
80
|
+
${(0, embedFrameStyles_1.embedFrameStyles)(block.id)}
|
|
81
|
+
.structured-embed-frame-${block.id} iframe {
|
|
82
|
+
width: 100%;
|
|
83
|
+
height: 100%;
|
|
84
|
+
border: none;
|
|
85
|
+
}
|
|
86
|
+
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams]) => (`
|
|
87
|
+
.structured-embed-${block.id} {
|
|
88
|
+
${area.height === undefined ? 'aspect-ratio: 16 / 9;' : 'height: 100%;'}
|
|
89
|
+
opacity: ${layoutParams.opacity};
|
|
90
|
+
}
|
|
91
|
+
.structured-embed-frame-${block.id} {
|
|
92
|
+
border-radius: ${layoutParams.radius * 100}vw;
|
|
93
|
+
}
|
|
94
|
+
`))}
|
|
95
|
+
` })] }));
|
|
96
|
+
};
|
|
97
|
+
exports.StructuredYoutubeEmbed = StructuredYoutubeEmbed;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.embedFrameStyles = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The box an embedded player fills: as tall as its block where the block has a height of its own,
|
|
6
|
+
* else at a video's 16:9, with the cover and the click catcher laid over the player.
|
|
7
|
+
*/
|
|
8
|
+
function embedFrameStyles(blockId) {
|
|
9
|
+
return `
|
|
10
|
+
.structured-embed-${blockId} {
|
|
11
|
+
position: relative;
|
|
12
|
+
width: 100%;
|
|
13
|
+
}
|
|
14
|
+
.structured-embed-frame-${blockId} {
|
|
15
|
+
display: block;
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 100%;
|
|
18
|
+
border: none;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
}
|
|
21
|
+
.structured-embed-cover-${blockId},
|
|
22
|
+
.structured-embed-overlay-${blockId} {
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 0;
|
|
25
|
+
left: 0;
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
z-index: 1;
|
|
30
|
+
}
|
|
31
|
+
.structured-embed-cover-${blockId} {
|
|
32
|
+
object-fit: cover;
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
exports.embedFrameStyles = embedFrameStyles;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StructuredHeaderImage = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
11
|
+
const useCntrlContext_1 = require("../../../provider/useCntrlContext");
|
|
12
|
+
const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
|
|
13
|
+
const BlockCaption_1 = require("../BlockCaption/BlockCaption");
|
|
14
|
+
/**
|
|
15
|
+
* A section header's key image with its caption; nothing until an image is uploaded. Where the
|
|
16
|
+
* header draws it as its backdrop, the header styles it: no caption, and covering the header if set to.
|
|
17
|
+
*/
|
|
18
|
+
const StructuredHeaderImage = ({ block, readingWidth }) => {
|
|
19
|
+
const reactId = (0, react_1.useId)();
|
|
20
|
+
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
21
|
+
const [ref, setRef] = (0, react_1.useState)(null);
|
|
22
|
+
(0, useItemGeometry_1.useItemGeometry)(block.id, ref);
|
|
23
|
+
const { url, altText, caption } = block.commonParams;
|
|
24
|
+
if (!url)
|
|
25
|
+
return null;
|
|
26
|
+
const layoutValues = [block.layoutParams];
|
|
27
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: setRef, className: `structured-header-image-${block.id}`, children: [(0, jsx_runtime_1.jsx)("img", { className: `structured-header-picture-${block.id}`, src: url, alt: altText }), caption && (0, jsx_runtime_1.jsx)(BlockCaption_1.BlockCaption, { blockId: block.id, caption: caption, layoutParams: block.layoutParams, readingWidth: readingWidth }), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-header-image-${block.id}`, children: `
|
|
28
|
+
.structured-header-image-${block.id} {
|
|
29
|
+
width: 100%;
|
|
30
|
+
}
|
|
31
|
+
.structured-header-picture-${block.id} {
|
|
32
|
+
display: block;
|
|
33
|
+
width: 100%;
|
|
34
|
+
height: auto;
|
|
35
|
+
}
|
|
36
|
+
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([layoutParams]) => (`
|
|
37
|
+
.structured-header-picture-${block.id} {
|
|
38
|
+
opacity: ${layoutParams.opacity};
|
|
39
|
+
}
|
|
40
|
+
`))}
|
|
41
|
+
` })] }));
|
|
42
|
+
};
|
|
43
|
+
exports.StructuredHeaderImage = StructuredHeaderImage;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StructuredImage = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
11
|
+
const useCntrlContext_1 = require("../../../provider/useCntrlContext");
|
|
12
|
+
const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
|
|
13
|
+
const BlockCaption_1 = require("../BlockCaption/BlockCaption");
|
|
14
|
+
const GALLERY_GAP_PX = 8;
|
|
15
|
+
/**
|
|
16
|
+
* A gallery: its images side by side in one row, each growing from nothing by its share of the row's
|
|
17
|
+
* aspect ratios, so that they fill the row as their ratios do and come out equally tall, with the
|
|
18
|
+
* caption under them. Until an image has loaded its ratio is taken to be square.
|
|
19
|
+
*/
|
|
20
|
+
const StructuredImage = ({ block, readingWidth }) => {
|
|
21
|
+
const reactId = (0, react_1.useId)();
|
|
22
|
+
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
23
|
+
const [ref, setRef] = (0, react_1.useState)(null);
|
|
24
|
+
(0, useItemGeometry_1.useItemGeometry)(block.id, ref);
|
|
25
|
+
const { urls, altText, caption } = block.commonParams;
|
|
26
|
+
const [ratios, setRatios] = (0, react_1.useState)({});
|
|
27
|
+
// an image already loaded when the page hydrates fires no load event of its own
|
|
28
|
+
const measure = (0, react_1.useCallback)((url, image) => {
|
|
29
|
+
if (!image || !image.complete || !image.naturalHeight)
|
|
30
|
+
return;
|
|
31
|
+
const ratio = image.naturalWidth / image.naturalHeight;
|
|
32
|
+
setRatios(prev => (prev[url] === ratio ? prev : Object.assign(Object.assign({}, prev), { [url]: ratio })));
|
|
33
|
+
}, []);
|
|
34
|
+
// growth factors summing to less than one leave part of the row empty, so a lone portrait image
|
|
35
|
+
// would fill only its ratio of the block: each image grows by its share of the total instead
|
|
36
|
+
const totalRatio = urls.reduce((sum, url) => { var _a; return sum + ((_a = ratios[url]) !== null && _a !== void 0 ? _a : 1); }, 0);
|
|
37
|
+
const layoutValues = [block.layoutParams];
|
|
38
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: setRef, className: `structured-gallery-${block.id}`, children: [(0, jsx_runtime_1.jsx)("div", { className: `structured-gallery-row-${block.id}`, children: urls.map((url, index) => {
|
|
39
|
+
var _a;
|
|
40
|
+
return ((0, jsx_runtime_1.jsx)("img", { ref: image => measure(url, image), className: `structured-gallery-image-${block.id}`, src: url, alt: altText, onLoad: event => measure(url, event.currentTarget), style: { flexGrow: ((_a = ratios[url]) !== null && _a !== void 0 ? _a : 1) / totalRatio } }, `${url}-${index}`));
|
|
41
|
+
}) }), caption && (0, jsx_runtime_1.jsx)(BlockCaption_1.BlockCaption, { blockId: block.id, caption: caption, layoutParams: block.layoutParams, readingWidth: readingWidth }), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-gallery-${block.id}`, children: `
|
|
42
|
+
.structured-gallery-row-${block.id} {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: flex-start;
|
|
45
|
+
width: 100%;
|
|
46
|
+
gap: var(--gallery-gap);
|
|
47
|
+
}
|
|
48
|
+
.structured-gallery-image-${block.id} {
|
|
49
|
+
display: block;
|
|
50
|
+
flex-shrink: 1;
|
|
51
|
+
flex-basis: 0;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
height: auto;
|
|
54
|
+
}
|
|
55
|
+
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([layoutParams], exemplary) => (`
|
|
56
|
+
.structured-gallery-${block.id} {
|
|
57
|
+
--gallery-gap: ${GALLERY_GAP_PX / exemplary * 100}vw;
|
|
58
|
+
}
|
|
59
|
+
.structured-gallery-row-${block.id} {
|
|
60
|
+
opacity: ${layoutParams.opacity};
|
|
61
|
+
}
|
|
62
|
+
`))}
|
|
63
|
+
` })] }));
|
|
64
|
+
};
|
|
65
|
+
exports.StructuredImage = StructuredImage;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StructuredRichText = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
11
|
+
const useCntrlContext_1 = require("../../../provider/useCntrlContext");
|
|
12
|
+
const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
|
|
13
|
+
const RichTextConverter_1 = require("../../../utils/RichTextConverter/RichTextConverter");
|
|
14
|
+
const detectLang_1 = require("../../../utils/detectLang");
|
|
15
|
+
const textStylesRule_1 = require("../textStylesRule");
|
|
16
|
+
const richTextConverter = new RichTextConverter_1.RichTextConverter();
|
|
17
|
+
/**
|
|
18
|
+
* A block of text: rich text, a quote, which is drawn in a box of its own, or a date, which comes as
|
|
19
|
+
* the text it reads as. Its text is set in the styles it was drawn with, its ranges styled over them.
|
|
20
|
+
*/
|
|
21
|
+
const StructuredRichText = ({ block }) => {
|
|
22
|
+
const reactId = (0, react_1.useId)();
|
|
23
|
+
const id = `${reactId}-structured-text-${block.id}`;
|
|
24
|
+
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
25
|
+
const [ref, setRef] = (0, react_1.useState)(null);
|
|
26
|
+
(0, useItemGeometry_1.useItemGeometry)(block.id, ref);
|
|
27
|
+
const [content, rangeStyles] = (0, react_1.useMemo)(() => richTextConverter.toHtml(block, layouts, { isScaledToViewport: false }), [block, layouts]);
|
|
28
|
+
const lang = (0, react_1.useMemo)(() => (0, detectLang_1.detectLang)(block.commonParams.text), [block.commonParams.text]);
|
|
29
|
+
const isQuote = block.type === sdk_1.StructuredBlockType.Quote;
|
|
30
|
+
const layoutValues = [block.layoutParams];
|
|
31
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { ref: setRef, lang: lang, className: `structured-text-${block.id}`, children: isQuote ? (0, jsx_runtime_1.jsx)("div", { className: `structured-quote-box-${block.id}`, children: content }) : content }), (0, jsx_runtime_1.jsxs)(style_1.default, { id: id, children: [rangeStyles, `${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([layoutParams]) => {
|
|
32
|
+
var _a, _b;
|
|
33
|
+
return (`
|
|
34
|
+
${(0, textStylesRule_1.textStylesRule)(`.structured-text-${block.id}`, layoutParams)}
|
|
35
|
+
${isQuote ? `
|
|
36
|
+
.structured-quote-box-${block.id} {
|
|
37
|
+
padding: ${((_a = layoutParams.boxPadding) !== null && _a !== void 0 ? _a : 0) * 100}vw;
|
|
38
|
+
border-radius: ${((_b = layoutParams.boxRadius) !== null && _b !== void 0 ? _b : 0) * 100}vw;
|
|
39
|
+
background-color: transparent;
|
|
40
|
+
}
|
|
41
|
+
${layoutParams.background ? (0, textStylesRule_1.colorRule)(`.structured-quote-box-${block.id}`, 'background-color', layoutParams.background) : ''}
|
|
42
|
+
` : ''}
|
|
43
|
+
`);
|
|
44
|
+
})}`] })] }));
|
|
45
|
+
};
|
|
46
|
+
exports.StructuredRichText = StructuredRichText;
|