@blocklet/ui-react 2.12.60 → 2.12.62

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.
@@ -7,6 +7,7 @@ declare module '@arcblock/ux/lib/ErrorBoundary';
7
7
  declare module '@arcblock/did-connect/*';
8
8
  declare module '@arcblock/did-connect/lib/Session';
9
9
  declare module '@abtnode/constant';
10
+ declare module '@abtnode/util/lib/notification-preview/*';
10
11
 
11
12
  declare module 'is-url';
12
13
 
@@ -0,0 +1,14 @@
1
+ interface NotificationProps {
2
+ keyId: number;
3
+ notification: {
4
+ severity?: string;
5
+ title?: string;
6
+ description?: string;
7
+ activity?: any;
8
+ actorInfo?: any;
9
+ };
10
+ viewAllUrl: string;
11
+ content?: React.ReactNode;
12
+ }
13
+ declare const NotificationComponent: import("react").ForwardRefExoticComponent<NotificationProps & import("react").RefAttributes<HTMLDivElement>>;
14
+ export default NotificationComponent;
@@ -0,0 +1,210 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import styled from "@emotion/styled";
5
+ import { amber, green, common } from "@mui/material/colors";
6
+ import IconButton from "@mui/material/IconButton";
7
+ import Box from "@mui/material/Box";
8
+ import Typography from "@mui/material/Typography";
9
+ import { useCreation } from "ahooks";
10
+ import CloseIcon from "@mui/icons-material/Close";
11
+ import { useNavigate } from "react-router-dom";
12
+ import { Icon } from "@iconify/react";
13
+ import CheckIcon from "@iconify-icons/tabler/circle-check";
14
+ import WarningIcon from "@iconify-icons/tabler/exclamation-circle";
15
+ import InfoIcon from "@iconify-icons/tabler/info-circle";
16
+ import ErrorIcon from "@iconify-icons/tabler/xbox-x";
17
+ import { useSnackbar, SnackbarContent } from "notistack";
18
+ import useWidth from "./hooks/use-width.js";
19
+ import useActivityTitle from "./hooks/use-title.js";
20
+ import { isIncludeActivity, toClickableSpan, sanitize } from "./utils.js";
21
+ const variants = {
22
+ normal: InfoIcon,
23
+ success: CheckIcon,
24
+ info: InfoIcon,
25
+ warning: WarningIcon,
26
+ error: ErrorIcon
27
+ };
28
+ const CloseIconStyled = styled(CloseIcon)`
29
+ font-size: 20px;
30
+ `;
31
+ const MessageDiv = styled.div`
32
+ display: flex;
33
+ align-items: flex-start;
34
+ gap: 8px;
35
+ flex: 1;
36
+ width: 0;
37
+ `;
38
+ const ActionDiv = styled.div`
39
+ display: flex;
40
+ align-items: center;
41
+ margin-left: auto;
42
+ margin-right: -8px;
43
+ width: 44px;
44
+ `;
45
+ const breakpointsMap = {
46
+ xl: "400px",
47
+ lg: "400px",
48
+ md: "400px",
49
+ sm: "300px"
50
+ };
51
+ const StyledSnackbarContent = styled(SnackbarContent)`
52
+ display: flex;
53
+ color: #fff;
54
+ align-items: center;
55
+ padding: 12px 16px;
56
+ border-radius: 4px;
57
+ box-shadow:
58
+ 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
59
+ 0px 6px 10px 0px rgba(0, 0, 0, 0.14),
60
+ 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
61
+
62
+ ${({ severity, breakpoint }) => {
63
+ const width = breakpointsMap[breakpoint] || "400px";
64
+ if (severity === "success") {
65
+ return `
66
+ background-color: ${green[600]} !important;
67
+ width: ${width};
68
+ `;
69
+ }
70
+ if (severity === "error") {
71
+ return `
72
+ background-color: #d32f2f !important;
73
+ width: ${width};
74
+ `;
75
+ }
76
+ if (severity === "info") {
77
+ return `
78
+ background-color: #1976d2 !important;
79
+ width: ${width};
80
+ `;
81
+ }
82
+ if (severity === "warning") {
83
+ return `
84
+ background-color: ${amber[700]} !important;
85
+ width: ${width};
86
+ `;
87
+ }
88
+ return `
89
+ background-color: #333;
90
+ width: ${width};
91
+ `;
92
+ }}
93
+ `;
94
+ const ClickableDiv = styled.div`
95
+ cursor: pointer;
96
+ display: flex;
97
+ flex-direction: column;
98
+ .title {
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ display: -webkit-box;
102
+ -webkit-line-clamp: 1;
103
+ -webkit-box-orient: vertical;
104
+ font-weight: bold;
105
+ }
106
+ .desc {
107
+ overflow: hidden;
108
+ text-overflow: ellipsis;
109
+ display: -webkit-box;
110
+ -webkit-line-clamp: 3;
111
+ -webkit-box-orient: vertical;
112
+ word-break: break-word;
113
+ line-height: 1.2;
114
+ .link,
115
+ .dapp,
116
+ .common {
117
+ color: ${common.white};
118
+ }
119
+ }
120
+ `;
121
+ const NotificationComponent = forwardRef(
122
+ ({ keyId: key, notification = {}, viewAllUrl, content }, ref) => {
123
+ const breakpoint = useWidth();
124
+ const [description, setDescription] = useState(notification.description || "");
125
+ const icon = variants[notification.severity || ""];
126
+ const { closeSnackbar } = useSnackbar();
127
+ const onClickDismiss = () => closeSnackbar(key);
128
+ useEffect(() => {
129
+ toClickableSpan(notification.description || "", "en").then((res) => {
130
+ setDescription(res);
131
+ });
132
+ }, [notification.description]);
133
+ const navigate = useNavigate();
134
+ const onGoNotification = (e) => {
135
+ e.stopPropagation();
136
+ closeSnackbar(key);
137
+ if (!e?.customPreventRedirect) {
138
+ navigate(viewAllUrl);
139
+ }
140
+ };
141
+ const includeActivity = useCreation(() => {
142
+ return isIncludeActivity(notification);
143
+ }, [notification]);
144
+ const activity = useCreation(() => {
145
+ return notification?.activity;
146
+ }, [notification]);
147
+ const activityMeta = useCreation(() => {
148
+ if (!activity || activity.type === "tips") {
149
+ return null;
150
+ }
151
+ return activity?.meta;
152
+ }, [activity]);
153
+ const activityTitle = useActivityTitle({
154
+ activity,
155
+ users: [notification?.actorInfo],
156
+ actors: [notification?.activity?.actor],
157
+ extra: {
158
+ linkColor: common.white
159
+ }
160
+ });
161
+ return /* @__PURE__ */ jsxs(StyledSnackbarContent, { ref, severity: notification.severity, breakpoint, children: [
162
+ /* @__PURE__ */ jsxs(MessageDiv, { children: [
163
+ icon ? /* @__PURE__ */ jsx(Icon, { icon, fontSize: 24 }) : null,
164
+ /* @__PURE__ */ jsx(ClickableDiv, { onClick: onGoNotification, style: { width: "calc(100% - 30px)" }, children: /* @__PURE__ */ jsx(Box, { children: includeActivity ? /* @__PURE__ */ jsxs(Fragment, { children: [
165
+ /* @__PURE__ */ jsx("span", { className: "title", children: activityTitle }),
166
+ activityMeta ? /* @__PURE__ */ jsx(
167
+ Typography,
168
+ {
169
+ variant: "subtitle2",
170
+ fontSize: 16,
171
+ component: "p",
172
+ sx: {
173
+ display: "-webkit-box",
174
+ overflow: "hidden",
175
+ textOverflow: "ellipsis",
176
+ WebkitLineClamp: 3,
177
+ WebkitBoxOrient: "vertical",
178
+ color: common.white,
179
+ lineHeight: 1.2
180
+ },
181
+ children: activityMeta?.content
182
+ }
183
+ ) : null
184
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
185
+ /* @__PURE__ */ jsx("span", { className: "title", children: notification.title }),
186
+ content || /* @__PURE__ */ jsx(
187
+ Typography,
188
+ {
189
+ component: "span",
190
+ className: "desc",
191
+ dangerouslySetInnerHTML: { __html: sanitize(description) }
192
+ }
193
+ )
194
+ ] }) }) })
195
+ ] }),
196
+ /* @__PURE__ */ jsx(ActionDiv, { children: /* @__PURE__ */ jsx(IconButton, { "aria-label": "close", color: "inherit", onClick: onClickDismiss, size: "large", children: /* @__PURE__ */ jsx(CloseIconStyled, {}) }, "close") })
197
+ ] });
198
+ }
199
+ );
200
+ NotificationComponent.displayName = "NotificationComponent";
201
+ NotificationComponent.propTypes = {
202
+ viewAllUrl: PropTypes.string.isRequired,
203
+ keyId: PropTypes.number.isRequired,
204
+ notification: PropTypes.object.isRequired,
205
+ content: PropTypes.node
206
+ };
207
+ NotificationComponent.defaultProps = {
208
+ content: null
209
+ };
210
+ export default NotificationComponent;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Activity types enum for type safety
3
+ * @readonly
4
+ * @enum {string}
5
+ */
6
+ declare const ACTIVITY_TYPES: {
7
+ readonly COMMENT: "comment";
8
+ readonly LIKE: "like";
9
+ readonly FOLLOW: "follow";
10
+ readonly TIPS: "tips";
11
+ readonly MENTION: "mention";
12
+ readonly ASSIGN: "assign";
13
+ };
14
+ type ActivityTypeValues = (typeof ACTIVITY_TYPES)[keyof typeof ACTIVITY_TYPES];
15
+ interface UserData {
16
+ did: string;
17
+ fullName: string;
18
+ }
19
+ interface ActivityTarget {
20
+ type: string;
21
+ name: string;
22
+ }
23
+ interface Activity {
24
+ type: ActivityTypeValues;
25
+ target: ActivityTarget;
26
+ }
27
+ interface ExtraParams {
28
+ linkColor?: string;
29
+ [key: string]: any;
30
+ }
31
+ interface ActivityTitleProps {
32
+ activity: Activity;
33
+ users: UserData[];
34
+ actors: string[];
35
+ extra?: ExtraParams;
36
+ mountPoint?: string;
37
+ }
38
+ /**
39
+ * A hook that returns a formatted activity title with linked usernames
40
+ * @param {Object} params - The parameters object
41
+ * @param {keyof typeof ACTIVITY_TYPES} params.type - The activity type
42
+ * @param {Object} params.target - The target object
43
+ * @param {Array<{did: string, fullName: string}>} params.users - Array of user objects
44
+ * @param {Object} params.extra - Extra parameters
45
+ * @returns {React.ReactNode} Formatted title with linked usernames
46
+ */
47
+ export default function useActivityTitle({ activity, users, actors, extra, mountPoint }: ActivityTitleProps): JSX.Element | null;
48
+ export {};
@@ -0,0 +1,159 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { useCreation, useMemoizedFn } from "ahooks";
4
+ import { temp as colors } from "@arcblock/ux/lib/Colors";
5
+ import Link from "@mui/material/Link";
6
+ import { WELLKNOWN_SERVICE_PATH_PREFIX } from "@abtnode/constant";
7
+ import { joinURL, withQuery } from "ufo";
8
+ import isEmpty from "lodash/isEmpty";
9
+ import { getActivityLink } from "../utils.js";
10
+ const ACTIVITY_TYPES = {
11
+ COMMENT: "comment",
12
+ LIKE: "like",
13
+ FOLLOW: "follow",
14
+ TIPS: "tips",
15
+ MENTION: "mention",
16
+ ASSIGN: "assign"
17
+ };
18
+ const ACTIVITY_DESCRIPTIONS = {
19
+ comment: (targetType, count) => count && count > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
20
+ "left ",
21
+ count,
22
+ " comments on your ",
23
+ targetType
24
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
25
+ "commented on your ",
26
+ targetType
27
+ ] }),
28
+ like: (targetType) => /* @__PURE__ */ jsxs(Fragment, { children: [
29
+ "liked your ",
30
+ targetType
31
+ ] }),
32
+ follow: () => /* @__PURE__ */ jsx(Fragment, { children: "followed you" }),
33
+ tips: (targetType) => /* @__PURE__ */ jsxs(Fragment, { children: [
34
+ "gave tips to your ",
35
+ targetType
36
+ ] }),
37
+ mention: (targetType) => /* @__PURE__ */ jsxs(Fragment, { children: [
38
+ "mentioned you in ",
39
+ targetType
40
+ ] }),
41
+ assign: () => /* @__PURE__ */ jsx(Fragment, { children: "assigned you a task" })
42
+ };
43
+ function UserLink({ user, color = colors.textBase }) {
44
+ const profileLink = withQuery(joinURL(WELLKNOWN_SERVICE_PATH_PREFIX, "user"), { did: user.did });
45
+ return /* @__PURE__ */ jsx(
46
+ Link,
47
+ {
48
+ href: profileLink,
49
+ color,
50
+ fontWeight: 600,
51
+ target: "_blank",
52
+ sx: { textDecoration: "none", "&:hover": { cursor: "pointer" } },
53
+ onClick: (e) => {
54
+ e.customPreventRedirect = true;
55
+ },
56
+ children: user.fullName
57
+ }
58
+ );
59
+ }
60
+ UserLink.displayName = "UserLink";
61
+ export default function useActivityTitle({ activity, users, actors, extra = {}, mountPoint = "" }) {
62
+ const { type, target } = activity || {};
63
+ const { type: targetType } = target || {};
64
+ const { linkColor = colors.textBase } = extra || {};
65
+ const usersMap = useCreation(() => {
66
+ if (!Array.isArray(users))
67
+ return /* @__PURE__ */ new Map();
68
+ const map = /* @__PURE__ */ new Map();
69
+ users.forEach((user) => {
70
+ if (user?.did && !map.has(user.did)) {
71
+ map.set(user.did, user);
72
+ }
73
+ });
74
+ return map;
75
+ }, [users]);
76
+ const uniqueUsers = useCreation(() => {
77
+ if (!Array.isArray(actors))
78
+ return [];
79
+ return actors.map((actorId) => {
80
+ if (!actorId)
81
+ return null;
82
+ return usersMap.get(actorId) || { did: actorId, fullName: actorId?.substring(0, 8) };
83
+ }).filter(Boolean);
84
+ }, [actors, usersMap]);
85
+ const formatLinkedUserNames = useMemoizedFn(() => {
86
+ if (!Array.isArray(uniqueUsers) || uniqueUsers.length === 0) {
87
+ return null;
88
+ }
89
+ if (uniqueUsers.length === 1) {
90
+ return /* @__PURE__ */ jsx(UserLink, { user: uniqueUsers[0], color: linkColor });
91
+ }
92
+ const initialUsers = uniqueUsers.slice(0, -1);
93
+ const lastUser = uniqueUsers[uniqueUsers.length - 1];
94
+ if (uniqueUsers.length === 2) {
95
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
96
+ /* @__PURE__ */ jsx(UserLink, { user: initialUsers[0], color: linkColor }),
97
+ " and ",
98
+ /* @__PURE__ */ jsx(UserLink, { user: lastUser, color: linkColor })
99
+ ] });
100
+ }
101
+ const isMoreThanThree = uniqueUsers.length > 3;
102
+ const displayUsers = isMoreThanThree ? uniqueUsers.slice(0, 2) : initialUsers;
103
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
104
+ displayUsers.map((user, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
105
+ /* @__PURE__ */ jsx(UserLink, { user, color: linkColor }),
106
+ index < displayUsers.length - 1 ? ", " : ""
107
+ ] }, user.did)),
108
+ isMoreThanThree ? `, and ${uniqueUsers.length - 2} others` : /* @__PURE__ */ jsxs(Fragment, { children: [
109
+ ", and ",
110
+ /* @__PURE__ */ jsx(UserLink, { user: lastUser, color: linkColor })
111
+ ] })
112
+ ] });
113
+ });
114
+ const getActivityDescription = useMemoizedFn(() => {
115
+ const descriptionFn = type ? ACTIVITY_DESCRIPTIONS[type] : null;
116
+ return descriptionFn ? descriptionFn(targetType, users.length) : null;
117
+ });
118
+ const title = useCreation(() => {
119
+ const linkedNames = formatLinkedUserNames();
120
+ const description = getActivityDescription();
121
+ if (!linkedNames || !description) {
122
+ return null;
123
+ }
124
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
125
+ linkedNames,
126
+ " ",
127
+ description
128
+ ] });
129
+ }, [type, targetType, uniqueUsers, formatLinkedUserNames, getActivityDescription]);
130
+ const targetLink = useCreation(() => {
131
+ if (!activity)
132
+ return null;
133
+ const link = getActivityLink(activity);
134
+ if (link?.targetLink) {
135
+ return joinURL(mountPoint, link.targetLink);
136
+ }
137
+ return null;
138
+ }, [activity, mountPoint]);
139
+ if (!type || isEmpty(target)) {
140
+ return null;
141
+ }
142
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
143
+ title,
144
+ " ",
145
+ targetLink && /* @__PURE__ */ jsx(
146
+ Link,
147
+ {
148
+ href: targetLink,
149
+ color: linkColor,
150
+ target: "_blank",
151
+ sx: { textDecoration: "none" },
152
+ onClick: (e) => {
153
+ e.customPreventRedirect = true;
154
+ },
155
+ children: target.name
156
+ }
157
+ )
158
+ ] });
159
+ }
@@ -0,0 +1,2 @@
1
+ declare function useWidth(): string;
2
+ export default useWidth;
@@ -0,0 +1,11 @@
1
+ import { useTheme } from "@arcblock/ux/lib/Theme";
2
+ import useMediaQuery from "@mui/material/useMediaQuery";
3
+ function useWidth() {
4
+ const theme = useTheme();
5
+ const keys = [...theme.breakpoints.keys].reverse();
6
+ return keys.reduce((output, key) => {
7
+ const matches = useMediaQuery(theme.breakpoints.up(key));
8
+ return !output && matches ? key : output;
9
+ }, null) || "xs";
10
+ }
11
+ export default useWidth;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 通知对象的活动目标接口
3
+ */
4
+ interface ActivityTarget {
5
+ type?: string;
6
+ id?: string;
7
+ }
8
+ /**
9
+ * 通知对象的活动元数据接口
10
+ */
11
+ interface ActivityMeta {
12
+ id?: string;
13
+ }
14
+ /**
15
+ * 活动对象接口
16
+ */
17
+ interface Activity {
18
+ type?: string;
19
+ actor?: string;
20
+ target?: ActivityTarget;
21
+ meta?: ActivityMeta;
22
+ }
23
+ /**
24
+ * 通知对象接口
25
+ */
26
+ interface Notification {
27
+ activity?: Activity;
28
+ actorInfo?: any;
29
+ severity?: string;
30
+ title?: string;
31
+ description?: string;
32
+ items?: Notification[];
33
+ }
34
+ /**
35
+ * 合并相邻的通知数据
36
+ * 合并条件:
37
+ * 1. 数据必须是相邻的
38
+ * 2. activity.type 必须相同且不为 null 或 undefined
39
+ * 3. 如果存在target对象,activity.target.type和activity.target.id都必须相同
40
+ * 4. 如果相邻数据的 activity.type 相同但没有 activity.target 对象,则需要合并
41
+ *
42
+ * @param {Notification[]} notifications - 需要处理的通知数据
43
+ * @returns {Notification[]} - 合并后的通知数组
44
+ */
45
+ export declare const mergeAdjacentNotifications: (notifications: Notification[]) => Notification[];
46
+ /**
47
+ * 判断通知是否包含activity
48
+ * @param {Notification} notification - 通知对象
49
+ * @returns {boolean} - 是否包含activity
50
+ */
51
+ export declare const isIncludeActivity: (notification: Notification) => boolean;
52
+ /**
53
+ * 是否可以自动已读
54
+ */
55
+ export declare const canAutoRead: (notification: Notification | null | undefined) => boolean;
56
+ /**
57
+ * 获取 activity 的链接
58
+ * 链接的来源有两种
59
+ * 1. activity.meta.id
60
+ * 2. activity.target.id
61
+ * @param {Activity} activity - 活动对象
62
+ * @returns {Object | null} - 活动的链接
63
+ */
64
+ export declare const getActivityLink: (activity: Activity | null | undefined) => {
65
+ metaLink?: string | null;
66
+ targetLink?: string | null;
67
+ } | null;
68
+ export declare const toClickableSpan: (str: string, locale: string, isHighLight?: boolean) => Promise<any>;
69
+ export declare const sanitize: (innerHtml: string) => any;
70
+ export {};
@@ -0,0 +1,130 @@
1
+ import isEmpty from "lodash/isEmpty";
2
+ import DOMPurify from "dompurify";
3
+ import { Link, toTextList, getLink as getLinkUtil } from "@abtnode/util/lib/notification-preview/highlight";
4
+ import { isSameAddr } from "@abtnode/util/lib/notification-preview/func";
5
+ import { client } from "../libs/client.js";
6
+ export const mergeAdjacentNotifications = (notifications) => {
7
+ if (!notifications || !notifications.length) {
8
+ return [];
9
+ }
10
+ const result = [];
11
+ let currentGroup = null;
12
+ let groupItems = [];
13
+ notifications.forEach((notification) => {
14
+ if (!notification.activity || !notification.activity.type || !currentGroup) {
15
+ if (currentGroup) {
16
+ if (groupItems.length === 1) {
17
+ result.push(groupItems[0]);
18
+ } else {
19
+ currentGroup.items = groupItems;
20
+ result.push(currentGroup);
21
+ }
22
+ }
23
+ if (notification.activity && notification.activity.type) {
24
+ currentGroup = notification;
25
+ groupItems = [notification];
26
+ } else {
27
+ result.push(notification);
28
+ currentGroup = null;
29
+ groupItems = [];
30
+ }
31
+ return;
32
+ }
33
+ const currentActivity = groupItems[0].activity;
34
+ const currentType = currentActivity?.type;
35
+ const currentTargetType = currentActivity?.target?.type;
36
+ const currentTargetId = currentActivity?.target?.id;
37
+ const newType = notification.activity.type;
38
+ const newTargetType = notification.activity.target?.type;
39
+ const newTargetId = notification.activity.target?.id;
40
+ const shouldMerge = (
41
+ // activity.type 必须相同且不为null或undefined
42
+ currentType && newType && currentType === newType && // 如果都没有target,可以合并
43
+ (!currentActivity?.target && !notification.activity.target || // 如果都有target,则target.type和target.id都必须相同
44
+ currentActivity?.target && notification.activity.target && currentTargetType === newTargetType && currentTargetId === newTargetId)
45
+ );
46
+ if (shouldMerge) {
47
+ groupItems.push(notification);
48
+ } else {
49
+ if (groupItems.length === 1) {
50
+ result.push(groupItems[0]);
51
+ } else {
52
+ const groupToAdd = Object.assign({}, currentGroup, { items: groupItems });
53
+ result.push(groupToAdd);
54
+ }
55
+ currentGroup = notification;
56
+ groupItems = [notification];
57
+ }
58
+ });
59
+ if (currentGroup) {
60
+ if (groupItems.length === 1) {
61
+ result.push(groupItems[0]);
62
+ } else {
63
+ const groupToAdd = Object.assign({}, currentGroup, { items: groupItems });
64
+ result.push(groupToAdd);
65
+ }
66
+ }
67
+ return result;
68
+ };
69
+ export const isIncludeActivity = (notification) => {
70
+ return !isEmpty(notification.activity) && !!notification.activity?.type && !!notification.activity?.actor;
71
+ };
72
+ export const canAutoRead = (notification) => {
73
+ const { severity } = notification || {};
74
+ return !!severity && ["normal", "success", "info"].includes(severity);
75
+ };
76
+ export const getActivityLink = (activity) => {
77
+ if (!activity) {
78
+ return null;
79
+ }
80
+ const { meta, target } = activity;
81
+ return {
82
+ metaLink: meta?.id,
83
+ targetLink: target?.id
84
+ };
85
+ };
86
+ const getUserProfileUrl = async (did, locale) => {
87
+ try {
88
+ const profileUrl = await client.user.getProfileUrl({ did, locale });
89
+ return profileUrl;
90
+ } catch (error) {
91
+ console.error(error);
92
+ return void 0;
93
+ }
94
+ };
95
+ const getLink = async (item, locale) => {
96
+ const { type, did } = item;
97
+ if (isSameAddr(type, "did")) {
98
+ const profileUrl = await getUserProfileUrl(did, locale);
99
+ return profileUrl || getLinkUtil(item);
100
+ }
101
+ return getLinkUtil(item);
102
+ };
103
+ export const toClickableSpan = async (str, locale, isHighLight = true) => {
104
+ const textList = toTextList(str);
105
+ const result = await Promise.all(
106
+ textList.map(async (item) => {
107
+ if (item instanceof Link) {
108
+ if (isHighLight) {
109
+ const url = await getLink(item, locale);
110
+ const { type, chainId, did } = item;
111
+ if (isSameAddr(type, "dapp")) {
112
+ return `<em class="dapp" data-type="${type}" data-chain-id="${chainId}" data-did="${did}">${item.text}</em>`;
113
+ }
114
+ if (url) {
115
+ return `<a target="_blank" rel="noopener noreferrer" class="link" href="${url}">${item.text}</a>`;
116
+ }
117
+ return `<em class="common" data-type="${type}" data-chain-id="${chainId}" data-did="${did}">${item.text}</em>`;
118
+ }
119
+ return item.text;
120
+ }
121
+ return item;
122
+ })
123
+ ).then((results) => results.join(""));
124
+ return result;
125
+ };
126
+ export const sanitize = (innerHtml) => {
127
+ return DOMPurify.sanitize(innerHtml, {
128
+ ALLOWED_ATTR: ["href", "target", "rel", "class", "id", "style"]
129
+ });
130
+ };
@@ -143,7 +143,7 @@ export default function UserBasicInfo({
143
143
  bottom: 0,
144
144
  left: 0,
145
145
  right: 0,
146
- height: "50%",
146
+ height: "25%",
147
147
  backgroundColor: "rgba(0, 0, 0, 0.3)",
148
148
  display: "flex",
149
149
  justifyContent: "center",