@gravity-ui/blog-constructor 3.4.1 → 3.5.0-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
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
  }