@gravity-ui/blog-constructor 3.4.1 → 3.5.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 (31) hide show
  1. package/build/cjs/components/PostInfo/components/Save.js +6 -1
  2. package/build/cjs/components/Prompt/Prompt.css +61 -0
  3. package/build/cjs/components/Prompt/Prompt.d.ts +18 -0
  4. package/build/cjs/components/Prompt/Prompt.js +41 -0
  5. package/build/cjs/components/PromptSignIn/PromptSignIn.d.ts +10 -0
  6. package/build/cjs/components/PromptSignIn/PromptSignIn.js +34 -0
  7. package/build/cjs/components/PromptSignIn/hooks/usePromptSignInProps.d.ts +6 -0
  8. package/build/cjs/components/PromptSignIn/hooks/usePromptSignInProps.js +16 -0
  9. package/build/cjs/containers/BlogPage/BlogPage.d.ts +4 -1
  10. package/build/cjs/containers/BlogPage/BlogPage.js +56 -18
  11. package/build/cjs/contexts/LikesContext.d.ts +2 -0
  12. package/build/cjs/hooks/useOpenCloseTimer.d.ts +9 -0
  13. package/build/cjs/hooks/useOpenCloseTimer.js +30 -0
  14. package/build/cjs/i18n/index.d.ts +3 -1
  15. package/build/cjs/i18n/index.js +6 -0
  16. package/build/esm/components/PostInfo/components/Save.js +6 -1
  17. package/build/esm/components/Prompt/Prompt.css +61 -0
  18. package/build/esm/components/Prompt/Prompt.d.ts +19 -0
  19. package/build/esm/components/Prompt/Prompt.js +35 -0
  20. package/build/esm/components/PromptSignIn/PromptSignIn.d.ts +10 -0
  21. package/build/esm/components/PromptSignIn/PromptSignIn.js +27 -0
  22. package/build/esm/components/PromptSignIn/hooks/usePromptSignInProps.d.ts +6 -0
  23. package/build/esm/components/PromptSignIn/hooks/usePromptSignInProps.js +12 -0
  24. package/build/esm/containers/BlogPage/BlogPage.d.ts +4 -1
  25. package/build/esm/containers/BlogPage/BlogPage.js +33 -18
  26. package/build/esm/contexts/LikesContext.d.ts +2 -0
  27. package/build/esm/hooks/useOpenCloseTimer.d.ts +9 -0
  28. package/build/esm/hooks/useOpenCloseTimer.js +26 -0
  29. package/build/esm/i18n/index.d.ts +3 -1
  30. package/build/esm/i18n/index.js +6 -0
  31. package/package.json +4 -1
@@ -53,7 +53,7 @@ const b = (0, cn_1.block)('post-info');
53
53
  * @returns jsx
54
54
  */
55
55
  const Save = ({ title, postId, hasUserLike, handleUserLike, metrikaGoal, size, theme, dataQa, }) => {
56
- const { toggleLike } = (0, react_1.useContext)(LikesContext_1.LikesContext);
56
+ const { toggleLike, isSignedInUser, requireSignIn } = (0, react_1.useContext)(LikesContext_1.LikesContext);
57
57
  const handleAnalytics = (0, page_constructor_1.useAnalytics)(common_1.DefaultEventNames.SaveButton);
58
58
  const isLikeable = Boolean(toggleLike);
59
59
  return (react_1.default.createElement("div", { className: b('item', { size }), onClick: (event) => {
@@ -64,6 +64,11 @@ const Save = ({ title, postId, hasUserLike, handleUserLike, metrikaGoal, size, t
64
64
  if (!isLikeable) {
65
65
  return;
66
66
  }
67
+ // Open Popup to ask the User to sign in first
68
+ if (!isSignedInUser && requireSignIn) {
69
+ requireSignIn(event);
70
+ return;
71
+ }
67
72
  (0, common_2.postLikeStatus)(postId, Boolean(hasUserLike));
68
73
  handleUserLike();
69
74
  metrika_js_1.default.reachGoal(utils_1.MetrikaCounter.CrossSite, metrikaGoal);
@@ -0,0 +1,61 @@
1
+ .bc-prompt__content {
2
+ box-shadow: 0px 4px 24px var(--pc-color-sfx-shadow), 0px 2px 8px var(--pc-color-sfx-shadow);
3
+ }
4
+
5
+ /* use this for style redefinitions to awoid problems with
6
+ unpredictable css rules order in build */
7
+ @keyframes bc-prompt_open {
8
+ 0% {
9
+ opacity: 0;
10
+ transform: translateY(100%);
11
+ }
12
+ 100% {
13
+ opacity: 1;
14
+ transform: translateY(0%);
15
+ }
16
+ }
17
+ @keyframes bc-prompt_close {
18
+ 0% {
19
+ opacity: 1;
20
+ transform: translateY(0%);
21
+ }
22
+ 100% {
23
+ opacity: 0;
24
+ transform: translateY(100%);
25
+ }
26
+ }
27
+ .bc-prompt {
28
+ display: flex;
29
+ width: 100%;
30
+ justify-content: center;
31
+ overflow: hidden;
32
+ position: fixed;
33
+ bottom: 0;
34
+ }
35
+ .bc-prompt:not(.bc-prompt_mounted) {
36
+ display: none;
37
+ }
38
+ .bc-prompt__content {
39
+ display: flex;
40
+ flex-flow: row wrap;
41
+ gap: 16px;
42
+ align-items: center;
43
+ margin: 24px;
44
+ padding: 16px 20px;
45
+ border-radius: var(--pc-border-radius);
46
+ background-color: var(--yc-color-base-float);
47
+ font-size: 1rem;
48
+ }
49
+ .bc-prompt_close {
50
+ pointer-events: none;
51
+ }
52
+ .bc-prompt_open > .bc-prompt__content {
53
+ opacity: 0;
54
+ transform: translateY(100%);
55
+ animation: bc-prompt_open 600ms forwards;
56
+ }
57
+ .bc-prompt_close > .bc-prompt__content {
58
+ opacity: 1;
59
+ transform: translateY(0%);
60
+ animation: bc-prompt_close 600ms forwards;
61
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { ButtonProps } from '@gravity-ui/uikit';
3
+ export interface PromptProps {
4
+ text: string;
5
+ actions: ButtonProps[];
6
+ openTimestamp?: number;
7
+ openDuration?: number;
8
+ className?: string;
9
+ theme?: 'grey' | 'beige' | 'white';
10
+ }
11
+ /**
12
+ * Popup that appears with text message and button(s) for given `actions`.
13
+ * Features:
14
+ * - Automatically disappears after `openDuration` in milliseconds
15
+ * - `openTimestamp` (`Date.now()`) resets the visible duration
16
+ * @returns {JSX|null}
17
+ */
18
+ export declare const Prompt: React.FC<PromptProps>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Prompt = void 0;
18
+ const react_1 = __importDefault(require("react"));
19
+ const uikit_1 = require("@gravity-ui/uikit");
20
+ const useOpenCloseTimer_1 = require("../../hooks/useOpenCloseTimer");
21
+ const cn_1 = require("../../utils/cn");
22
+ const b = (0, cn_1.block)('prompt');
23
+ /**
24
+ * Popup that appears with text message and button(s) for given `actions`.
25
+ * Features:
26
+ * - Automatically disappears after `openDuration` in milliseconds
27
+ * - `openTimestamp` (`Date.now()`) resets the visible duration
28
+ * @returns {JSX|null}
29
+ */
30
+ const Prompt = ({ text, actions, className, openTimestamp = 0, openDuration, theme, }) => {
31
+ const { open } = (0, useOpenCloseTimer_1.useOpenCloseTimer)(openTimestamp, openDuration);
32
+ const mounted = openTimestamp > 0;
33
+ return (react_1.default.createElement("div", { className: b({ theme, open, close: !open, mounted }, className) },
34
+ react_1.default.createElement("div", { className: b('content') },
35
+ react_1.default.createElement("span", { className: b('text') }, text),
36
+ react_1.default.createElement("div", { className: b('actions') }, actions.map((_a, i) => {
37
+ var { view = 'action', className: btnClass } = _a, btnProps = __rest(_a, ["view", "className"]);
38
+ return (react_1.default.createElement(uikit_1.Button, Object.assign({ key: i, className: b('action', btnClass), view: view }, btnProps)));
39
+ })))));
40
+ };
41
+ exports.Prompt = Prompt;
@@ -0,0 +1,10 @@
1
+ import React, { SyntheticEvent } from 'react';
2
+ import { PromptProps } from '../Prompt/Prompt';
3
+ export interface PromptSignInProps extends Partial<PromptProps> {
4
+ onClickSignIn?: React.EventHandler<SyntheticEvent>;
5
+ }
6
+ /**
7
+ * Authentication Popup that appears when user action requires login
8
+ * @returns {JSX|null}
9
+ */
10
+ export declare const PromptSignIn: React.FC<PromptSignInProps>;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.PromptSignIn = void 0;
18
+ const react_1 = __importDefault(require("react"));
19
+ const i18n_1 = require("../../i18n");
20
+ const Prompt_1 = require("../Prompt/Prompt");
21
+ /**
22
+ * Authentication Popup that appears when user action requires login
23
+ * @returns {JSX|null}
24
+ */
25
+ const PromptSignIn = (_a) => {
26
+ var { text = (0, i18n_1.i18)(i18n_1.Keyset.PromptSignInOnLike), onClickSignIn = () => alert((0, i18n_1.i18)(i18n_1.Keyset.SignIn)), actions = [
27
+ {
28
+ children: (0, i18n_1.i18)(i18n_1.Keyset.SignIn),
29
+ onClick: onClickSignIn,
30
+ },
31
+ ] } = _a, props = __rest(_a, ["text", "onClickSignIn", "actions"]);
32
+ return react_1.default.createElement(Prompt_1.Prompt, Object.assign({}, { text, actions }, props));
33
+ };
34
+ exports.PromptSignIn = PromptSignIn;
@@ -0,0 +1,6 @@
1
+ import React, { SyntheticEvent } from 'react';
2
+ export declare function usePromptSignInProps(onClickSignIn?: React.EventHandler<SyntheticEvent>): {
3
+ onClickSignIn: ((event: React.SyntheticEvent<Element, Event>) => void) | undefined;
4
+ openTimestamp: number;
5
+ requireSignIn: (() => void) | undefined;
6
+ };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePromptSignInProps = void 0;
4
+ const react_1 = require("react");
5
+ function usePromptSignInProps(onClickSignIn) {
6
+ const [openTimestamp, setTime] = (0, react_1.useState)(0);
7
+ const requireSignIn = (0, react_1.useMemo)(() => {
8
+ return onClickSignIn
9
+ ? () => {
10
+ setTime(Date.now());
11
+ }
12
+ : undefined;
13
+ }, [onClickSignIn, setTime]);
14
+ return { onClickSignIn, openTimestamp, requireSignIn };
15
+ }
16
+ exports.usePromptSignInProps = usePromptSignInProps;
@@ -1,3 +1,4 @@
1
+ import React, { SyntheticEvent } from 'react';
1
2
  import { NavigationData, PageConstructorProviderProps, PageContent } from '@gravity-ui/page-constructor';
2
3
  import { GetPostsType, MetaProps, PostsProps, Service, SetQueryType, Tag, ToggleLikeCallbackType } from '../../models/common';
3
4
  export type BlogPageProps = {
@@ -13,5 +14,7 @@ export type BlogPageProps = {
13
14
  setQuery?: SetQueryType;
14
15
  settings?: PageConstructorProviderProps;
15
16
  pageCountForShowSupportButtons?: number;
17
+ isSignedInUser?: boolean;
18
+ onClickSignIn?: React.EventHandler<SyntheticEvent>;
16
19
  };
17
- export declare const BlogPage: ({ content, posts, tags, services, getPosts, metaData, hasLikes, toggleLike, navigation, settings, pageCountForShowSupportButtons, }: BlogPageProps) => JSX.Element;
20
+ export declare const BlogPage: ({ content, posts, tags, services, getPosts, metaData, hasLikes, toggleLike, navigation, settings, pageCountForShowSupportButtons, isSignedInUser, onClickSignIn, }: BlogPageProps) => JSX.Element;
@@ -1,30 +1,68 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __rest = (this && this.__rest) || function (s, e) {
26
+ var t = {};
27
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
28
+ t[p] = s[p];
29
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
30
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
31
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
32
+ t[p[i]] = s[p[i]];
33
+ }
34
+ return t;
35
+ };
2
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
38
  };
5
39
  Object.defineProperty(exports, "__esModule", { value: true });
6
40
  exports.BlogPage = void 0;
7
- const react_1 = __importDefault(require("react"));
41
+ const react_1 = __importStar(require("react"));
8
42
  const page_constructor_1 = require("@gravity-ui/page-constructor");
9
43
  const MetaWrapper_1 = require("../../components/MetaWrapper/MetaWrapper");
44
+ const PromptSignIn_1 = require("../../components/PromptSignIn/PromptSignIn");
45
+ const usePromptSignInProps_1 = require("../../components/PromptSignIn/hooks/usePromptSignInProps");
10
46
  const blocksMap_1 = __importDefault(require("../../constructor/blocksMap"));
11
47
  const FeedContext_1 = require("../../contexts/FeedContext");
12
48
  const LikesContext_1 = require("../../contexts/LikesContext");
13
- const BlogPage = ({ content, posts, tags, services, getPosts, metaData, hasLikes = false, toggleLike, navigation, settings, pageCountForShowSupportButtons, }) => (react_1.default.createElement("main", null,
14
- react_1.default.createElement(LikesContext_1.LikesContext.Provider, { value: {
15
- toggleLike: toggleLike,
16
- hasLikes,
17
- } },
18
- react_1.default.createElement(FeedContext_1.FeedContext.Provider, { value: {
19
- posts: posts.posts,
20
- pinnedPost: posts.pinnedPost,
21
- totalCount: posts.count,
22
- tags,
23
- services: services !== null && services !== void 0 ? services : [],
24
- getPosts,
25
- pageCountForShowSupportButtons,
26
- } },
27
- react_1.default.createElement(page_constructor_1.PageConstructorProvider, Object.assign({}, settings),
28
- metaData ? react_1.default.createElement(MetaWrapper_1.MetaWrapper, Object.assign({}, metaData)) : null,
29
- react_1.default.createElement(page_constructor_1.PageConstructor, { content: content, custom: blocksMap_1.default, navigation: navigation }))))));
49
+ const BlogPage = ({ content, posts, tags, services, getPosts, metaData, hasLikes = false, toggleLike, navigation, settings, pageCountForShowSupportButtons, isSignedInUser = false, onClickSignIn, }) => {
50
+ const _a = (0, usePromptSignInProps_1.usePromptSignInProps)(onClickSignIn), { requireSignIn } = _a, promptSignInProps = __rest(_a, ["requireSignIn"]);
51
+ const likes = (0, react_1.useMemo)(() => ({ toggleLike, hasLikes, isSignedInUser, requireSignIn }), [toggleLike, hasLikes, isSignedInUser, requireSignIn]);
52
+ return (react_1.default.createElement("main", null,
53
+ react_1.default.createElement(LikesContext_1.LikesContext.Provider, { value: likes },
54
+ react_1.default.createElement(FeedContext_1.FeedContext.Provider, { value: {
55
+ posts: posts.posts,
56
+ pinnedPost: posts.pinnedPost,
57
+ totalCount: posts.count,
58
+ tags,
59
+ services: services !== null && services !== void 0 ? services : [],
60
+ getPosts,
61
+ pageCountForShowSupportButtons,
62
+ } },
63
+ react_1.default.createElement(page_constructor_1.PageConstructorProvider, Object.assign({}, settings),
64
+ metaData ? react_1.default.createElement(MetaWrapper_1.MetaWrapper, Object.assign({}, metaData)) : null,
65
+ react_1.default.createElement(page_constructor_1.PageConstructor, { content: content, custom: blocksMap_1.default, navigation: navigation })))),
66
+ react_1.default.createElement(PromptSignIn_1.PromptSignIn, Object.assign({}, promptSignInProps))));
67
+ };
30
68
  exports.BlogPage = BlogPage;
@@ -3,5 +3,7 @@ import { ToggleLikeCallbackType } from '../models/common';
3
3
  export interface LikesContextProps {
4
4
  toggleLike?: ToggleLikeCallbackType;
5
5
  hasLikes?: boolean;
6
+ isSignedInUser?: boolean;
7
+ requireSignIn?: React.MouseEventHandler;
6
8
  }
7
9
  export declare const LikesContext: React.Context<LikesContextProps>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Timer to automatically update `open` state after a given duration
3
+ * @param {number} openTimestamp - UNIX timestamp in milliseconds
4
+ * @param {number} openDuration - in milliseconds
5
+ * @returns {{open: boolean}} {open} - whether the state is open
6
+ */
7
+ export declare function useOpenCloseTimer(openTimestamp?: number, openDuration?: number): {
8
+ open: boolean;
9
+ };
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useOpenCloseTimer = void 0;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Timer to automatically update `open` state after a given duration
7
+ * @param {number} openTimestamp - UNIX timestamp in milliseconds
8
+ * @param {number} openDuration - in milliseconds
9
+ * @returns {{open: boolean}} {open} - whether the state is open
10
+ */
11
+ function useOpenCloseTimer(openTimestamp = Date.now(), openDuration = 4000) {
12
+ const open = Date.now() - openTimestamp < openDuration;
13
+ const [, reset] = (0, react_1.useState)(0); // time to reset `open` state
14
+ (0, react_1.useEffect)(() => {
15
+ const closeTime = openTimestamp + openDuration;
16
+ const delay = closeTime - Date.now();
17
+ if (delay <= 0) {
18
+ return;
19
+ }
20
+ const timer = setTimeout(() => {
21
+ reset(Date.now);
22
+ }, delay);
23
+ // eslint-disable-next-line consistent-return
24
+ return () => {
25
+ clearTimeout(timer);
26
+ };
27
+ }, [openTimestamp, openDuration]);
28
+ return { open };
29
+ }
30
+ exports.useOpenCloseTimer = useOpenCloseTimer;
@@ -17,6 +17,8 @@ export declare enum Keyset {
17
17
  Search = "search_placeholder",
18
18
  AllTags = "label_all_tags",
19
19
  ActionSavedOnly = "action_saved_only",
20
- AllServices = "label_all_services"
20
+ AllServices = "label_all_services",
21
+ PromptSignInOnLike = "prompt_sign_in_on_like",
22
+ SignIn = "Sign In"
21
23
  }
22
24
  export declare const i18: (key: string, params?: import("@gravity-ui/i18n").Params | undefined) => string;
@@ -24,6 +24,8 @@ var Keyset;
24
24
  Keyset["AllTags"] = "label_all_tags";
25
25
  Keyset["ActionSavedOnly"] = "action_saved_only";
26
26
  Keyset["AllServices"] = "label_all_services";
27
+ Keyset["PromptSignInOnLike"] = "prompt_sign_in_on_like";
28
+ Keyset["SignIn"] = "Sign In";
27
29
  })(Keyset = exports.Keyset || (exports.Keyset = {}));
28
30
  exports.i18n.registerKeyset(locale_1.Lang.En, KEYSET_NAME, {
29
31
  [Keyset.Title]: 'Blog',
@@ -42,11 +44,13 @@ exports.i18n.registerKeyset(locale_1.Lang.En, KEYSET_NAME, {
42
44
  [Keyset.AllTags]: 'All topics',
43
45
  [Keyset.ActionSavedOnly]: 'Saved',
44
46
  [Keyset.AllServices]: 'All Services',
47
+ [Keyset.PromptSignInOnLike]: 'Please sign in to save your bookmarks',
45
48
  [Keyset.ContextReadingTime]: [
46
49
  '{{count}} min to read',
47
50
  '{{count}} mins to read',
48
51
  '{{count}} mins to read',
49
52
  ],
53
+ [Keyset.SignIn]: 'Sign In',
50
54
  });
51
55
  exports.i18n.registerKeyset(locale_1.Lang.Ru, KEYSET_NAME, {
52
56
  [Keyset.Title]: 'Блог',
@@ -65,10 +69,12 @@ exports.i18n.registerKeyset(locale_1.Lang.Ru, KEYSET_NAME, {
65
69
  [Keyset.AllTags]: 'Все темы',
66
70
  [Keyset.ActionSavedOnly]: 'Сохранённые',
67
71
  [Keyset.AllServices]: 'Все сервисы',
72
+ [Keyset.PromptSignInOnLike]: 'Войдите, чтобы добавить доклад в своё расписание',
68
73
  [Keyset.ContextReadingTime]: [
69
74
  '{{count}} минута чтения',
70
75
  '{{count}} минуты чтения',
71
76
  '{{count}} минут чтения',
72
77
  ],
78
+ [Keyset.SignIn]: 'Войти',
73
79
  });
74
80
  exports.i18 = exports.i18n.keyset(KEYSET_NAME);
@@ -25,7 +25,7 @@ const b = block('post-info');
25
25
  * @returns jsx
26
26
  */
27
27
  export const Save = ({ title, postId, hasUserLike, handleUserLike, metrikaGoal, size, theme, dataQa, }) => {
28
- const { toggleLike } = useContext(LikesContext);
28
+ const { toggleLike, isSignedInUser, requireSignIn } = useContext(LikesContext);
29
29
  const handleAnalytics = useAnalytics(DefaultEventNames.SaveButton);
30
30
  const isLikeable = Boolean(toggleLike);
31
31
  return (React.createElement("div", { className: b('item', { size }), onClick: (event) => {
@@ -36,6 +36,11 @@ export const Save = ({ title, postId, hasUserLike, handleUserLike, metrikaGoal,
36
36
  if (!isLikeable) {
37
37
  return;
38
38
  }
39
+ // Open Popup to ask the User to sign in first
40
+ if (!isSignedInUser && requireSignIn) {
41
+ requireSignIn(event);
42
+ return;
43
+ }
39
44
  postLikeStatus(postId, Boolean(hasUserLike));
40
45
  handleUserLike();
41
46
  metrika.reachGoal(MetrikaCounter.CrossSite, metrikaGoal);
@@ -0,0 +1,61 @@
1
+ .bc-prompt__content {
2
+ box-shadow: 0px 4px 24px var(--pc-color-sfx-shadow), 0px 2px 8px var(--pc-color-sfx-shadow);
3
+ }
4
+
5
+ /* use this for style redefinitions to awoid problems with
6
+ unpredictable css rules order in build */
7
+ @keyframes bc-prompt_open {
8
+ 0% {
9
+ opacity: 0;
10
+ transform: translateY(100%);
11
+ }
12
+ 100% {
13
+ opacity: 1;
14
+ transform: translateY(0%);
15
+ }
16
+ }
17
+ @keyframes bc-prompt_close {
18
+ 0% {
19
+ opacity: 1;
20
+ transform: translateY(0%);
21
+ }
22
+ 100% {
23
+ opacity: 0;
24
+ transform: translateY(100%);
25
+ }
26
+ }
27
+ .bc-prompt {
28
+ display: flex;
29
+ width: 100%;
30
+ justify-content: center;
31
+ overflow: hidden;
32
+ position: fixed;
33
+ bottom: 0;
34
+ }
35
+ .bc-prompt:not(.bc-prompt_mounted) {
36
+ display: none;
37
+ }
38
+ .bc-prompt__content {
39
+ display: flex;
40
+ flex-flow: row wrap;
41
+ gap: 16px;
42
+ align-items: center;
43
+ margin: 24px;
44
+ padding: 16px 20px;
45
+ border-radius: var(--pc-border-radius);
46
+ background-color: var(--yc-color-base-float);
47
+ font-size: 1rem;
48
+ }
49
+ .bc-prompt_close {
50
+ pointer-events: none;
51
+ }
52
+ .bc-prompt_open > .bc-prompt__content {
53
+ opacity: 0;
54
+ transform: translateY(100%);
55
+ animation: bc-prompt_open 600ms forwards;
56
+ }
57
+ .bc-prompt_close > .bc-prompt__content {
58
+ opacity: 1;
59
+ transform: translateY(0%);
60
+ animation: bc-prompt_close 600ms forwards;
61
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { ButtonProps } from '@gravity-ui/uikit';
3
+ import './Prompt.css';
4
+ export interface PromptProps {
5
+ text: string;
6
+ actions: ButtonProps[];
7
+ openTimestamp?: number;
8
+ openDuration?: number;
9
+ className?: string;
10
+ theme?: 'grey' | 'beige' | 'white';
11
+ }
12
+ /**
13
+ * Popup that appears with text message and button(s) for given `actions`.
14
+ * Features:
15
+ * - Automatically disappears after `openDuration` in milliseconds
16
+ * - `openTimestamp` (`Date.now()`) resets the visible duration
17
+ * @returns {JSX|null}
18
+ */
19
+ export declare const Prompt: React.FC<PromptProps>;
@@ -0,0 +1,35 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import React from 'react';
13
+ import { Button } from '@gravity-ui/uikit';
14
+ import { useOpenCloseTimer } from '../../hooks/useOpenCloseTimer';
15
+ import { block } from '../../utils/cn';
16
+ import './Prompt.css';
17
+ const b = block('prompt');
18
+ /**
19
+ * Popup that appears with text message and button(s) for given `actions`.
20
+ * Features:
21
+ * - Automatically disappears after `openDuration` in milliseconds
22
+ * - `openTimestamp` (`Date.now()`) resets the visible duration
23
+ * @returns {JSX|null}
24
+ */
25
+ export const Prompt = ({ text, actions, className, openTimestamp = 0, openDuration, theme, }) => {
26
+ const { open } = useOpenCloseTimer(openTimestamp, openDuration);
27
+ const mounted = openTimestamp > 0;
28
+ return (React.createElement("div", { className: b({ theme, open, close: !open, mounted }, className) },
29
+ React.createElement("div", { className: b('content') },
30
+ React.createElement("span", { className: b('text') }, text),
31
+ React.createElement("div", { className: b('actions') }, actions.map((_a, i) => {
32
+ var { view = 'action', className: btnClass } = _a, btnProps = __rest(_a, ["view", "className"]);
33
+ return (React.createElement(Button, Object.assign({ key: i, className: b('action', btnClass), view: view }, btnProps)));
34
+ })))));
35
+ };
@@ -0,0 +1,10 @@
1
+ import React, { SyntheticEvent } from 'react';
2
+ import { PromptProps } from '../Prompt/Prompt';
3
+ export interface PromptSignInProps extends Partial<PromptProps> {
4
+ onClickSignIn?: React.EventHandler<SyntheticEvent>;
5
+ }
6
+ /**
7
+ * Authentication Popup that appears when user action requires login
8
+ * @returns {JSX|null}
9
+ */
10
+ export declare const PromptSignIn: React.FC<PromptSignInProps>;
@@ -0,0 +1,27 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import React from 'react';
13
+ import { Keyset, i18 } from '../../i18n';
14
+ import { Prompt } from '../Prompt/Prompt';
15
+ /**
16
+ * Authentication Popup that appears when user action requires login
17
+ * @returns {JSX|null}
18
+ */
19
+ export const PromptSignIn = (_a) => {
20
+ var { text = i18(Keyset.PromptSignInOnLike), onClickSignIn = () => alert(i18(Keyset.SignIn)), actions = [
21
+ {
22
+ children: i18(Keyset.SignIn),
23
+ onClick: onClickSignIn,
24
+ },
25
+ ] } = _a, props = __rest(_a, ["text", "onClickSignIn", "actions"]);
26
+ return React.createElement(Prompt, Object.assign({}, { text, actions }, props));
27
+ };
@@ -0,0 +1,6 @@
1
+ import React, { SyntheticEvent } from 'react';
2
+ export declare function usePromptSignInProps(onClickSignIn?: React.EventHandler<SyntheticEvent>): {
3
+ onClickSignIn: ((event: React.SyntheticEvent<Element, Event>) => void) | undefined;
4
+ openTimestamp: number;
5
+ requireSignIn: (() => void) | undefined;
6
+ };
@@ -0,0 +1,12 @@
1
+ import { useMemo, useState } from 'react';
2
+ export function usePromptSignInProps(onClickSignIn) {
3
+ const [openTimestamp, setTime] = useState(0);
4
+ const requireSignIn = useMemo(() => {
5
+ return onClickSignIn
6
+ ? () => {
7
+ setTime(Date.now());
8
+ }
9
+ : undefined;
10
+ }, [onClickSignIn, setTime]);
11
+ return { onClickSignIn, openTimestamp, requireSignIn };
12
+ }
@@ -1,3 +1,4 @@
1
+ import React, { SyntheticEvent } from 'react';
1
2
  import { NavigationData, PageConstructorProviderProps, PageContent } from '@gravity-ui/page-constructor';
2
3
  import { GetPostsType, MetaProps, PostsProps, Service, SetQueryType, Tag, ToggleLikeCallbackType } from '../../models/common';
3
4
  import './BlogPage.css';
@@ -14,5 +15,7 @@ export type BlogPageProps = {
14
15
  setQuery?: SetQueryType;
15
16
  settings?: PageConstructorProviderProps;
16
17
  pageCountForShowSupportButtons?: number;
18
+ isSignedInUser?: boolean;
19
+ onClickSignIn?: React.EventHandler<SyntheticEvent>;
17
20
  };
18
- export declare const BlogPage: ({ content, posts, tags, services, getPosts, metaData, hasLikes, toggleLike, navigation, settings, pageCountForShowSupportButtons, }: BlogPageProps) => JSX.Element;
21
+ export declare const BlogPage: ({ content, posts, tags, services, getPosts, metaData, hasLikes, toggleLike, navigation, settings, pageCountForShowSupportButtons, isSignedInUser, onClickSignIn, }: BlogPageProps) => JSX.Element;
@@ -1,24 +1,39 @@
1
- import React from 'react';
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import React, { useMemo } from 'react';
2
13
  import { PageConstructor, PageConstructorProvider, } from '@gravity-ui/page-constructor';
3
14
  import { MetaWrapper } from '../../components/MetaWrapper/MetaWrapper';
15
+ import { PromptSignIn } from '../../components/PromptSignIn/PromptSignIn';
16
+ import { usePromptSignInProps } from '../../components/PromptSignIn/hooks/usePromptSignInProps';
4
17
  import componentMap from '../../constructor/blocksMap';
5
18
  import { FeedContext } from '../../contexts/FeedContext';
6
19
  import { LikesContext } from '../../contexts/LikesContext';
7
20
  import './BlogPage.css';
8
- export const BlogPage = ({ content, posts, tags, services, getPosts, metaData, hasLikes = false, toggleLike, navigation, settings, pageCountForShowSupportButtons, }) => (React.createElement("main", null,
9
- React.createElement(LikesContext.Provider, { value: {
10
- toggleLike: toggleLike,
11
- hasLikes,
12
- } },
13
- React.createElement(FeedContext.Provider, { value: {
14
- posts: posts.posts,
15
- pinnedPost: posts.pinnedPost,
16
- totalCount: posts.count,
17
- tags,
18
- services: services !== null && services !== void 0 ? services : [],
19
- getPosts,
20
- pageCountForShowSupportButtons,
21
- } },
22
- React.createElement(PageConstructorProvider, Object.assign({}, settings),
23
- metaData ? React.createElement(MetaWrapper, Object.assign({}, metaData)) : null,
24
- React.createElement(PageConstructor, { content: content, custom: componentMap, navigation: navigation }))))));
21
+ export const BlogPage = ({ content, posts, tags, services, getPosts, metaData, hasLikes = false, toggleLike, navigation, settings, pageCountForShowSupportButtons, isSignedInUser = false, onClickSignIn, }) => {
22
+ const _a = usePromptSignInProps(onClickSignIn), { requireSignIn } = _a, promptSignInProps = __rest(_a, ["requireSignIn"]);
23
+ const likes = useMemo(() => ({ toggleLike, hasLikes, isSignedInUser, requireSignIn }), [toggleLike, hasLikes, isSignedInUser, requireSignIn]);
24
+ return (React.createElement("main", null,
25
+ React.createElement(LikesContext.Provider, { value: likes },
26
+ React.createElement(FeedContext.Provider, { value: {
27
+ posts: posts.posts,
28
+ pinnedPost: posts.pinnedPost,
29
+ totalCount: posts.count,
30
+ tags,
31
+ services: services !== null && services !== void 0 ? services : [],
32
+ getPosts,
33
+ pageCountForShowSupportButtons,
34
+ } },
35
+ React.createElement(PageConstructorProvider, Object.assign({}, settings),
36
+ metaData ? React.createElement(MetaWrapper, Object.assign({}, metaData)) : null,
37
+ React.createElement(PageConstructor, { content: content, custom: componentMap, navigation: navigation })))),
38
+ React.createElement(PromptSignIn, Object.assign({}, promptSignInProps))));
39
+ };
@@ -3,5 +3,7 @@ import { ToggleLikeCallbackType } from '../models/common';
3
3
  export interface LikesContextProps {
4
4
  toggleLike?: ToggleLikeCallbackType;
5
5
  hasLikes?: boolean;
6
+ isSignedInUser?: boolean;
7
+ requireSignIn?: React.MouseEventHandler;
6
8
  }
7
9
  export declare const LikesContext: React.Context<LikesContextProps>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Timer to automatically update `open` state after a given duration
3
+ * @param {number} openTimestamp - UNIX timestamp in milliseconds
4
+ * @param {number} openDuration - in milliseconds
5
+ * @returns {{open: boolean}} {open} - whether the state is open
6
+ */
7
+ export declare function useOpenCloseTimer(openTimestamp?: number, openDuration?: number): {
8
+ open: boolean;
9
+ };
@@ -0,0 +1,26 @@
1
+ import { useEffect, useState } from 'react';
2
+ /**
3
+ * Timer to automatically update `open` state after a given duration
4
+ * @param {number} openTimestamp - UNIX timestamp in milliseconds
5
+ * @param {number} openDuration - in milliseconds
6
+ * @returns {{open: boolean}} {open} - whether the state is open
7
+ */
8
+ export function useOpenCloseTimer(openTimestamp = Date.now(), openDuration = 4000) {
9
+ const open = Date.now() - openTimestamp < openDuration;
10
+ const [, reset] = useState(0); // time to reset `open` state
11
+ useEffect(() => {
12
+ const closeTime = openTimestamp + openDuration;
13
+ const delay = closeTime - Date.now();
14
+ if (delay <= 0) {
15
+ return;
16
+ }
17
+ const timer = setTimeout(() => {
18
+ reset(Date.now);
19
+ }, delay);
20
+ // eslint-disable-next-line consistent-return
21
+ return () => {
22
+ clearTimeout(timer);
23
+ };
24
+ }, [openTimestamp, openDuration]);
25
+ return { open };
26
+ }
@@ -17,6 +17,8 @@ export declare enum Keyset {
17
17
  Search = "search_placeholder",
18
18
  AllTags = "label_all_tags",
19
19
  ActionSavedOnly = "action_saved_only",
20
- AllServices = "label_all_services"
20
+ AllServices = "label_all_services",
21
+ PromptSignInOnLike = "prompt_sign_in_on_like",
22
+ SignIn = "Sign In"
21
23
  }
22
24
  export declare const i18: (key: string, params?: import("@gravity-ui/i18n").Params | undefined) => string;
@@ -21,6 +21,8 @@ export var Keyset;
21
21
  Keyset["AllTags"] = "label_all_tags";
22
22
  Keyset["ActionSavedOnly"] = "action_saved_only";
23
23
  Keyset["AllServices"] = "label_all_services";
24
+ Keyset["PromptSignInOnLike"] = "prompt_sign_in_on_like";
25
+ Keyset["SignIn"] = "Sign In";
24
26
  })(Keyset || (Keyset = {}));
25
27
  i18n.registerKeyset(Lang.En, KEYSET_NAME, {
26
28
  [Keyset.Title]: 'Blog',
@@ -39,11 +41,13 @@ i18n.registerKeyset(Lang.En, KEYSET_NAME, {
39
41
  [Keyset.AllTags]: 'All topics',
40
42
  [Keyset.ActionSavedOnly]: 'Saved',
41
43
  [Keyset.AllServices]: 'All Services',
44
+ [Keyset.PromptSignInOnLike]: 'Please sign in to save your bookmarks',
42
45
  [Keyset.ContextReadingTime]: [
43
46
  '{{count}} min to read',
44
47
  '{{count}} mins to read',
45
48
  '{{count}} mins to read',
46
49
  ],
50
+ [Keyset.SignIn]: 'Sign In',
47
51
  });
48
52
  i18n.registerKeyset(Lang.Ru, KEYSET_NAME, {
49
53
  [Keyset.Title]: 'Блог',
@@ -62,10 +66,12 @@ i18n.registerKeyset(Lang.Ru, KEYSET_NAME, {
62
66
  [Keyset.AllTags]: 'Все темы',
63
67
  [Keyset.ActionSavedOnly]: 'Сохранённые',
64
68
  [Keyset.AllServices]: 'Все сервисы',
69
+ [Keyset.PromptSignInOnLike]: 'Войдите, чтобы добавить доклад в своё расписание',
65
70
  [Keyset.ContextReadingTime]: [
66
71
  '{{count}} минута чтения',
67
72
  '{{count}} минуты чтения',
68
73
  '{{count}} минут чтения',
69
74
  ],
75
+ [Keyset.SignIn]: 'Войти',
70
76
  });
71
77
  export const i18 = i18n.keyset(KEYSET_NAME);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/blog-constructor",
3
- "version": "3.4.1",
3
+ "version": "3.5.0-alpha.0",
4
4
  "description": "Gravity UI Blog Constructor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -141,5 +141,8 @@
141
141
  "*.{json,yaml,yml,md}": [
142
142
  "prettier --write"
143
143
  ]
144
+ },
145
+ "publishConfig": {
146
+ "tag": "alpha"
144
147
  }
145
148
  }