@arcblock/ux 3.1.28 → 3.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/UserCard/Cards/avatar-only.js +11 -22
- package/lib/UserCard/Cards/index.js +4 -12
- package/lib/UserCard/Cards/social-actions.d.ts +7 -0
- package/lib/UserCard/Cards/social-actions.js +112 -0
- package/lib/UserCard/Content/minimal.js +62 -47
- package/lib/UserCard/index.d.ts +2 -0
- package/lib/UserCard/index.js +31 -28
- package/lib/UserCard/types.d.ts +8 -1
- package/lib/UserCard/types.js +4 -4
- package/lib/UserCard/use-follow.d.ts +16 -0
- package/lib/UserCard/use-follow.js +67 -0
- package/lib/Util/index.d.ts +15 -0
- package/lib/Util/index.js +191 -171
- package/lib/package.json.js +9 -0
- package/package.json +10 -8
- package/src/UserCard/Cards/avatar-only.tsx +1 -15
- package/src/UserCard/Cards/index.tsx +2 -11
- package/src/UserCard/Cards/social-actions.tsx +196 -0
- package/src/UserCard/Content/minimal.tsx +43 -31
- package/src/UserCard/UserCard.stories.jsx +1 -0
- package/src/UserCard/index.tsx +6 -0
- package/src/UserCard/types.ts +10 -1
- package/src/UserCard/use-follow.tsx +119 -0
- package/src/Util/index.ts +67 -0
- package/lib/UserCard/Cards/name-only.d.ts +0 -5
- package/lib/UserCard/Cards/name-only.js +0 -13
- package/src/UserCard/Cards/name-only.tsx +0 -17
@@ -0,0 +1,196 @@
|
|
1
|
+
import { useMemo } from 'react';
|
2
|
+
import { Box, Button, styled } from '@mui/material';
|
3
|
+
import { useMemoizedFn } from 'ahooks';
|
4
|
+
import { joinURL } from 'ufo';
|
5
|
+
import noop from 'lodash/noop';
|
6
|
+
import { Icon } from '@iconify/react';
|
7
|
+
import NotificationsIcon from '@mui/icons-material/Notifications';
|
8
|
+
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
|
9
|
+
|
10
|
+
import useFollow from '../use-follow';
|
11
|
+
import { User, UserCardProps } from '../types';
|
12
|
+
import { useLocaleContext } from '../../Locale/context';
|
13
|
+
import { translate } from '../../Locale/util';
|
14
|
+
import { isSupportFollow } from '../../Util';
|
15
|
+
|
16
|
+
type SocialActionsProps = Pick<UserCardProps, 'showSocialActions' | 'session'> & {
|
17
|
+
user: User; // 当前卡片显示的用户,要与 session.user 区分开
|
18
|
+
};
|
19
|
+
|
20
|
+
const translations = {
|
21
|
+
zh: {
|
22
|
+
chat: '聊天',
|
23
|
+
follow: '关注',
|
24
|
+
unfollow: '取消关注',
|
25
|
+
follow_success: '关注成功',
|
26
|
+
unfollow_success: '取消关注成功',
|
27
|
+
},
|
28
|
+
en: {
|
29
|
+
chat: 'Chat',
|
30
|
+
follow: 'Follow',
|
31
|
+
unfollow: 'Unfollow',
|
32
|
+
follow_success: 'Follow success',
|
33
|
+
unfollow_success: 'Unfollow success',
|
34
|
+
},
|
35
|
+
};
|
36
|
+
|
37
|
+
const getDiscussKitMountPoint = () => {
|
38
|
+
const { componentMountPoints = [] } = window.blocklet || {};
|
39
|
+
const component = componentMountPoints.find((c: any) => c.name === 'did-comments');
|
40
|
+
return component?.mountPoint;
|
41
|
+
};
|
42
|
+
|
43
|
+
// chat: 1. showSocialActions.chat 为 true,2. 必须安装了 discuss kit
|
44
|
+
function Chat({
|
45
|
+
user,
|
46
|
+
showSocialActions,
|
47
|
+
session,
|
48
|
+
}: {
|
49
|
+
user: SocialActionsProps['user'];
|
50
|
+
showSocialActions: SocialActionsProps['showSocialActions'];
|
51
|
+
session: SocialActionsProps['session'];
|
52
|
+
}) {
|
53
|
+
const { locale } = useLocaleContext() || { locale: 'en' };
|
54
|
+
const t = useMemoizedFn((key, data = {}) => {
|
55
|
+
return translate(translations, key, locale, 'en', data);
|
56
|
+
});
|
57
|
+
|
58
|
+
const isLogin = useMemo(() => {
|
59
|
+
return !!session?.user?.did;
|
60
|
+
}, [session?.user?.did]);
|
61
|
+
|
62
|
+
// 获取 discuss kit 的挂载点
|
63
|
+
const discussKitMountPoint = useMemo(() => getDiscussKitMountPoint(), []);
|
64
|
+
|
65
|
+
const onNavigateToChat = useMemoizedFn(() => {
|
66
|
+
if (!isLogin) {
|
67
|
+
session?.login(noop, {
|
68
|
+
openMode: 'redirect',
|
69
|
+
redirect: window.location.href,
|
70
|
+
}); // TODO: 需要确认使用是否正确
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
window.open(joinURL(discussKitMountPoint, `/chat/dm/${user?.did}`), '_blank');
|
74
|
+
});
|
75
|
+
|
76
|
+
const showChat = useMemo(() => {
|
77
|
+
// boolean:直接使用原值;对象:取对应属性值
|
78
|
+
const chatEnabled = typeof showSocialActions === 'boolean' ? !!showSocialActions : showSocialActions?.chat;
|
79
|
+
|
80
|
+
return chatEnabled && !!discussKitMountPoint;
|
81
|
+
}, [showSocialActions, discussKitMountPoint]);
|
82
|
+
|
83
|
+
if (!showChat) {
|
84
|
+
return null;
|
85
|
+
}
|
86
|
+
return (
|
87
|
+
<ButtonWrapper
|
88
|
+
className="user-card__social-actions-chat"
|
89
|
+
variant="outlined"
|
90
|
+
color="inherit"
|
91
|
+
onClick={onNavigateToChat}>
|
92
|
+
<Icon icon="tabler:message-dots" style={{ marginRight: 4 }} />
|
93
|
+
{t('chat')}
|
94
|
+
</ButtonWrapper>
|
95
|
+
);
|
96
|
+
}
|
97
|
+
|
98
|
+
// follow: 1. showSocialActions.follow 为 true
|
99
|
+
function Follow({
|
100
|
+
user,
|
101
|
+
showSocialActions,
|
102
|
+
session,
|
103
|
+
}: {
|
104
|
+
user: SocialActionsProps['user'];
|
105
|
+
showSocialActions: SocialActionsProps['showSocialActions'];
|
106
|
+
session: SocialActionsProps['session'];
|
107
|
+
}) {
|
108
|
+
const { locale } = useLocaleContext() || { locale: 'en' };
|
109
|
+
const t = useMemoizedFn((key, data = {}) => {
|
110
|
+
return translate(translations, key, locale, 'en', data);
|
111
|
+
});
|
112
|
+
|
113
|
+
const isLogin = useMemo(() => {
|
114
|
+
return !!session?.user?.did;
|
115
|
+
}, [session?.user?.did]);
|
116
|
+
|
117
|
+
const showFollow = useMemo(() => {
|
118
|
+
// boolean:直接使用原值;对象:取对应属性值
|
119
|
+
return typeof showSocialActions === 'boolean' ? !!showSocialActions : showSocialActions?.follow;
|
120
|
+
}, [showSocialActions]);
|
121
|
+
|
122
|
+
const { followed, followUser, unfollowUser } = useFollow({
|
123
|
+
user,
|
124
|
+
t,
|
125
|
+
isMySelf: false,
|
126
|
+
visible: !!showFollow,
|
127
|
+
});
|
128
|
+
const handleFollowAction = useMemoizedFn(() => {
|
129
|
+
if (!isLogin) {
|
130
|
+
session?.login(noop, {
|
131
|
+
openMode: 'redirect',
|
132
|
+
redirect: window.location.href,
|
133
|
+
});
|
134
|
+
return;
|
135
|
+
}
|
136
|
+
if (followed) {
|
137
|
+
unfollowUser(user.did);
|
138
|
+
} else {
|
139
|
+
followUser(user.did);
|
140
|
+
}
|
141
|
+
});
|
142
|
+
|
143
|
+
if (!showFollow) {
|
144
|
+
return null;
|
145
|
+
}
|
146
|
+
|
147
|
+
return (
|
148
|
+
<ButtonWrapper
|
149
|
+
className="user-card__social-actions-follow"
|
150
|
+
variant="outlined"
|
151
|
+
color="inherit"
|
152
|
+
onClick={handleFollowAction}>
|
153
|
+
{followed ? (
|
154
|
+
<NotificationsOffIcon sx={{ fontSize: 14, mr: 0.5 }} />
|
155
|
+
) : (
|
156
|
+
<NotificationsIcon sx={{ fontSize: 14, mr: 0.5 }} />
|
157
|
+
)}
|
158
|
+
{followed ? t('unfollow') : t('follow')}
|
159
|
+
</ButtonWrapper>
|
160
|
+
);
|
161
|
+
}
|
162
|
+
|
163
|
+
// 社交操作组件
|
164
|
+
// 按钮的显示逻辑
|
165
|
+
// 通用逻辑: 1. user 和 session.user 不能是同一个用户, 2. 相关依赖支持,3. showSocialActions 为 true 或 为 object
|
166
|
+
// 其他逻辑: 1. 如果 session 中没有用户信息,点击后需要重定向到登录页面
|
167
|
+
function SocialActions({ showSocialActions, session, user }: SocialActionsProps) {
|
168
|
+
const isSameUser = useMemo(() => {
|
169
|
+
return session?.user?.did === user?.did;
|
170
|
+
}, [session?.user?.did, user?.did]);
|
171
|
+
|
172
|
+
const isSupportSocialActions = useMemo(() => {
|
173
|
+
return isSupportFollow();
|
174
|
+
}, []);
|
175
|
+
|
176
|
+
// 如果 session 中没有用户信息,或者 user 和 session.user 是同一个用户,则不显示社交操作
|
177
|
+
if (isSameUser || !isSupportSocialActions || !showSocialActions) {
|
178
|
+
return null;
|
179
|
+
}
|
180
|
+
|
181
|
+
return (
|
182
|
+
<Box className="user-card__social-actions" sx={{ display: 'flex', gap: 1 }}>
|
183
|
+
<Chat session={session} user={user} showSocialActions={showSocialActions} />
|
184
|
+
<Follow session={session} user={user} showSocialActions={showSocialActions} />
|
185
|
+
</Box>
|
186
|
+
);
|
187
|
+
}
|
188
|
+
|
189
|
+
export default SocialActions;
|
190
|
+
|
191
|
+
export const ButtonWrapper = styled(Button)`
|
192
|
+
color: ${({ theme }) => theme.palette.text.primary};
|
193
|
+
font-weight: 500;
|
194
|
+
border-color: ${({ theme }) => theme.palette.grey[300]};
|
195
|
+
line-height: 1.5;
|
196
|
+
`;
|
@@ -5,6 +5,7 @@ import { User, UserCardProps } from '../types';
|
|
5
5
|
import TooltipAvatar from './tooltip-avatar';
|
6
6
|
import { renderTopRight } from '../components';
|
7
7
|
import ShortenLabel from './shorten-label';
|
8
|
+
import SocialActions from '../Cards/social-actions';
|
8
9
|
|
9
10
|
interface MinimalContentProps extends UserCardProps {
|
10
11
|
user: User;
|
@@ -42,44 +43,55 @@ function MinimalContent({ ...props }: MinimalContentProps) {
|
|
42
43
|
<Box
|
43
44
|
sx={{
|
44
45
|
display: 'flex',
|
45
|
-
justifyContent: '
|
46
|
+
justifyContent: 'space-between',
|
46
47
|
alignItems: 'center',
|
47
48
|
gap: 1,
|
48
49
|
flex: 1,
|
49
50
|
minWidth: 0,
|
50
51
|
}}>
|
51
|
-
<
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
<
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
renderName(
|
72
|
-
|
73
|
-
|
74
|
-
{
|
75
|
-
|
76
|
-
|
77
|
-
|
52
|
+
<Box sx={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center', gap: 1, flex: 1 }}>
|
53
|
+
<TooltipAvatar
|
54
|
+
user={user}
|
55
|
+
avatarSize={avatarSize}
|
56
|
+
shouldShowHoverCard={shouldShowHoverCard}
|
57
|
+
renderCardContent={renderCardContent}
|
58
|
+
avatarProps={avatarProps}
|
59
|
+
{...rest}
|
60
|
+
/>
|
61
|
+
<Box>
|
62
|
+
<Typography
|
63
|
+
variant="subtitle1"
|
64
|
+
className="user-card__full-name-label"
|
65
|
+
noWrap
|
66
|
+
sx={{
|
67
|
+
fontWeight: 500,
|
68
|
+
color: 'text.primary',
|
69
|
+
fontSize: 18,
|
70
|
+
lineHeight: 1.1,
|
71
|
+
}}>
|
72
|
+
{renderName ? (
|
73
|
+
renderName(user)
|
74
|
+
) : (
|
75
|
+
<ShortenLabel sx={{ fontWeight: 500 }} {...shortenLabelProps}>
|
76
|
+
{user.fullName || user.email || user.did}
|
77
|
+
</ShortenLabel>
|
78
|
+
)}
|
79
|
+
</Typography>
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
-
|
81
|
+
{showDid && user.did ? (
|
82
|
+
<DID
|
83
|
+
did={user.did}
|
84
|
+
size={14}
|
85
|
+
copyable
|
86
|
+
compact
|
87
|
+
locale="en"
|
88
|
+
sx={{ lineHeight: 1.5 }}
|
89
|
+
{...(didProps ?? {})}
|
90
|
+
/>
|
91
|
+
) : null}
|
92
|
+
</Box>
|
82
93
|
</Box>
|
94
|
+
<SocialActions showSocialActions={rest.showSocialActions} user={user!} session={rest.session} />
|
83
95
|
</Box>
|
84
96
|
{renderTopRight(renderTopRightContent, topRightMaxWidth)}
|
85
97
|
</Box>
|
@@ -5,6 +5,7 @@ export { default as DidControl } from './demo/didControl';
|
|
5
5
|
export { default as TopRightContent } from './demo/topRightContent';
|
6
6
|
export { default as CustomFooter } from './demo/customFooter';
|
7
7
|
export { default as DidOnlyAvatar } from './demo/did-only-avatar';
|
8
|
+
export { default as SocialActions } from './demo/socialActions';
|
8
9
|
|
9
10
|
export default {
|
10
11
|
title: 'Data Display/UserCard',
|
package/src/UserCard/index.tsx
CHANGED
@@ -6,6 +6,7 @@ import DetailedCard from './Cards';
|
|
6
6
|
import DialogContainer from './Container/dialog';
|
7
7
|
import CardContainer from './Container/card';
|
8
8
|
import Avatar from '../Avatar';
|
9
|
+
import SocialActions from './Cards/social-actions';
|
9
10
|
import { getUserByDid, isUserDid } from './utils';
|
10
11
|
|
11
12
|
// 创建仅显示名称首字母的头像
|
@@ -93,6 +94,9 @@ function UserCard(props: UserCardProps) {
|
|
93
94
|
infoType={props.popupInfoType || props.infoType}
|
94
95
|
didProps={props.popupDidProps || props.didProps}
|
95
96
|
showDid={props.popupShowDid || props.showDid}
|
97
|
+
showSocialActions={
|
98
|
+
props.popupShowSocialActions === undefined ? props.showSocialActions : props.popupShowSocialActions
|
99
|
+
}
|
96
100
|
/>
|
97
101
|
</DialogContainer>
|
98
102
|
);
|
@@ -124,3 +128,5 @@ function UserCard(props: UserCardProps) {
|
|
124
128
|
}
|
125
129
|
|
126
130
|
export default UserCard;
|
131
|
+
|
132
|
+
export { SocialActions };
|
package/src/UserCard/types.ts
CHANGED
@@ -3,6 +3,7 @@ import { SxProps, Theme, TooltipProps } from '@mui/material';
|
|
3
3
|
import { DIDProps } from '../DID';
|
4
4
|
import { AvatarProps } from '../Avatar';
|
5
5
|
import { ShortenLabelProps } from './Content/shorten-label';
|
6
|
+
import type { Session } from '../type';
|
6
7
|
|
7
8
|
type UserPublicInfo = {
|
8
9
|
avatar: string;
|
@@ -100,13 +101,17 @@ export enum CardType {
|
|
100
101
|
|
101
102
|
// 信息类型
|
102
103
|
export enum InfoType {
|
103
|
-
NameOnly = 'NameOnly', // 仅显示名称
|
104
104
|
Minimal = 'Minimal', // 极简模式,显示头像、名称和DID
|
105
105
|
Basic = 'Basic', // 基本信息模式
|
106
106
|
}
|
107
107
|
|
108
|
+
export type SocialActionProps = {
|
109
|
+
chat: boolean;
|
110
|
+
follow: boolean;
|
111
|
+
};
|
108
112
|
// 定义UserCard属性接口
|
109
113
|
export interface UserCardProps {
|
114
|
+
session?: Session;
|
110
115
|
user?: User;
|
111
116
|
did?: string;
|
112
117
|
cardType?: CardType;
|
@@ -146,6 +151,10 @@ export interface UserCardProps {
|
|
146
151
|
renderTopRightContent?: () => React.ReactNode;
|
147
152
|
topRightMaxWidth?: number;
|
148
153
|
|
154
|
+
// 社交按钮相关属性
|
155
|
+
showSocialActions?: SocialActionProps | boolean;
|
156
|
+
popupShowSocialActions?: SocialActionProps | boolean;
|
157
|
+
|
149
158
|
// 自定义内容渲染函数
|
150
159
|
renderCustomContent?: () => React.ReactNode;
|
151
160
|
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
2
|
+
import { useMemoizedFn } from 'ahooks';
|
3
|
+
import isNil from 'lodash/isNil';
|
4
|
+
|
5
|
+
import type { AxiosError } from 'axios';
|
6
|
+
import { BlockletSDK } from '@blocklet/js-sdk';
|
7
|
+
import Toast from '../Toast';
|
8
|
+
import type { User } from './types';
|
9
|
+
|
10
|
+
export const formatAxiosError = (err: AxiosError) => {
|
11
|
+
const { response } = err;
|
12
|
+
|
13
|
+
if (response) {
|
14
|
+
return `Request failed: ${response.status} ${response.statusText}: ${JSON.stringify(response.data)}`;
|
15
|
+
}
|
16
|
+
|
17
|
+
return err.message;
|
18
|
+
};
|
19
|
+
|
20
|
+
/**
|
21
|
+
* 登录用户与当前用户(userDid)的关注关系
|
22
|
+
*/
|
23
|
+
export default function useFollow({
|
24
|
+
user,
|
25
|
+
t,
|
26
|
+
isMySelf,
|
27
|
+
visible,
|
28
|
+
}: {
|
29
|
+
user: User;
|
30
|
+
t: (k: string) => string;
|
31
|
+
isMySelf: boolean;
|
32
|
+
visible: boolean;
|
33
|
+
}) {
|
34
|
+
const [followed, setFollowed] = useState(false);
|
35
|
+
|
36
|
+
const userDid = useMemo(() => user?.did, [user]);
|
37
|
+
|
38
|
+
// 判断 user 对象中 isFollowing 值是否有效,有isFollowed字段并且值不为 undefined 或 null
|
39
|
+
// 如果为 true 则不需要通过 api 查询,直接使用 user.isFollowing 值
|
40
|
+
// 如果为 false 则需要通过 api 查询,并设置 user.isFollowing 值
|
41
|
+
const hasFollowedField = useMemo(() => {
|
42
|
+
return Object.prototype.hasOwnProperty.call(user, 'isFollowing') && !isNil((user as any).isFollowing);
|
43
|
+
}, [user]);
|
44
|
+
|
45
|
+
const client = useMemo(() => {
|
46
|
+
let _client: BlockletSDK | null = null;
|
47
|
+
try {
|
48
|
+
_client = new BlockletSDK();
|
49
|
+
} catch (error) {
|
50
|
+
console.error('Failed to initialize BlockletSDK:', error);
|
51
|
+
_client = null;
|
52
|
+
}
|
53
|
+
return _client;
|
54
|
+
}, []);
|
55
|
+
|
56
|
+
const isFollowingUser = useMemoizedFn(async () => {
|
57
|
+
if (!client) {
|
58
|
+
setFollowed(false);
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
try {
|
62
|
+
if (isMySelf) {
|
63
|
+
setFollowed(true);
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
const res = await client.user.isFollowingUser({ userDid });
|
67
|
+
setFollowed(res);
|
68
|
+
} catch (error) {
|
69
|
+
console.error(error);
|
70
|
+
}
|
71
|
+
});
|
72
|
+
|
73
|
+
const followUser = useMemoizedFn(async (followUserDid: string = userDid) => {
|
74
|
+
if (!client || (isMySelf && followUserDid === userDid)) {
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
try {
|
78
|
+
await client.user.followUser({ userDid: followUserDid });
|
79
|
+
Toast.success(t('follow_success'));
|
80
|
+
isFollowingUser();
|
81
|
+
} catch (error) {
|
82
|
+
console.error(error);
|
83
|
+
Toast.error(formatAxiosError(error as AxiosError));
|
84
|
+
}
|
85
|
+
});
|
86
|
+
const unfollowUser = useMemoizedFn(async (unfollowUserDid: string = userDid) => {
|
87
|
+
if (!client || (isMySelf && unfollowUserDid === userDid)) {
|
88
|
+
return;
|
89
|
+
}
|
90
|
+
try {
|
91
|
+
await client.user.unfollowUser({ userDid: unfollowUserDid });
|
92
|
+
Toast.success(t('unfollow_success'));
|
93
|
+
isFollowingUser();
|
94
|
+
} catch (error) {
|
95
|
+
console.error(error);
|
96
|
+
Toast.error(formatAxiosError(error as AxiosError));
|
97
|
+
}
|
98
|
+
});
|
99
|
+
|
100
|
+
useEffect(() => {
|
101
|
+
// 如果 user 对象中已经存在了 isFollowing 字段,则不进行查询
|
102
|
+
if (visible && userDid && !isMySelf && !!client && !hasFollowedField) {
|
103
|
+
isFollowingUser();
|
104
|
+
}
|
105
|
+
}, [isFollowingUser, userDid, isMySelf, client, hasFollowedField, visible]);
|
106
|
+
|
107
|
+
// 如果 user 对象中已经存在了 isFollowing 字段,则直接设置 followed 状态
|
108
|
+
useEffect(() => {
|
109
|
+
if (hasFollowedField) {
|
110
|
+
setFollowed((user as any)?.isFollowing || false);
|
111
|
+
}
|
112
|
+
}, [hasFollowedField, user]);
|
113
|
+
|
114
|
+
return {
|
115
|
+
followed,
|
116
|
+
followUser,
|
117
|
+
unfollowUser,
|
118
|
+
};
|
119
|
+
}
|
package/src/Util/index.ts
CHANGED
@@ -15,8 +15,10 @@ import timezone from 'dayjs/plugin/timezone';
|
|
15
15
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
16
16
|
import updateLocale from 'dayjs/plugin/updateLocale';
|
17
17
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
18
|
+
import semver from 'semver';
|
18
19
|
|
19
20
|
import { DID_PREFIX, BLOCKLET_SERVICE_PATH_PREFIX } from './constant';
|
21
|
+
import pkg from '../../package.json';
|
20
22
|
import type { $TSFixMe, Locale } from '../type';
|
21
23
|
import { getFederatedEnabled } from './federated';
|
22
24
|
|
@@ -658,3 +660,68 @@ export function hexToRgba(hex: string, alpha = 1) {
|
|
658
660
|
// 返回 RGBA 格式
|
659
661
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
660
662
|
}
|
663
|
+
|
664
|
+
// 获取 server 的版本号
|
665
|
+
export const getServerVersion = () => {
|
666
|
+
return window.blocklet?.serverVersion || '';
|
667
|
+
};
|
668
|
+
|
669
|
+
// 获取 UX 包版本号
|
670
|
+
export const getUxPackageVersion = () => {
|
671
|
+
return pkg.version || '';
|
672
|
+
};
|
673
|
+
|
674
|
+
export const getJsSdkVersion = () => {
|
675
|
+
// 从 package.json 的 dependencies 中获取 @blocklet/js-sdk 的版本
|
676
|
+
const jsSdkVersion = pkg.dependencies?.['@blocklet/js-sdk'];
|
677
|
+
if (!jsSdkVersion) {
|
678
|
+
return '';
|
679
|
+
}
|
680
|
+
// 移除版本前缀符号 (^, ~, >=, 等)
|
681
|
+
return jsSdkVersion.replace(/^[\^~>=<]+/, '');
|
682
|
+
};
|
683
|
+
|
684
|
+
/**
|
685
|
+
* 比较两个版本号,version1 是否大于等于 version2
|
686
|
+
* @param {*} version1
|
687
|
+
* @param {*} version2
|
688
|
+
* @returns boolean
|
689
|
+
*/
|
690
|
+
export const compareVersions = (version1: string, version2: string) => {
|
691
|
+
const getDateVersion = (version: string) => {
|
692
|
+
const match = version.match(/^(\d+\.\d+\.\d+(?:-[^-]+?-\d{8}))/);
|
693
|
+
return match ? match[1] : version;
|
694
|
+
};
|
695
|
+
|
696
|
+
const dateVersion1 = getDateVersion(version1);
|
697
|
+
const dateVersion2 = getDateVersion(version2);
|
698
|
+
|
699
|
+
// 如果基础版本相同,但完整版本不同(意味着有额外部分),返回false
|
700
|
+
if (dateVersion1 === dateVersion2 && version1 !== version2) {
|
701
|
+
return false;
|
702
|
+
}
|
703
|
+
|
704
|
+
// 其他情况正常比较
|
705
|
+
return semver.gte(dateVersion1, dateVersion2);
|
706
|
+
};
|
707
|
+
|
708
|
+
/**
|
709
|
+
* 是否支持用户的 follow 关系
|
710
|
+
* 通过 server 的版本和 ux 的版本共同决定
|
711
|
+
*/
|
712
|
+
export const isSupportFollow = () => {
|
713
|
+
const serverVersion = getServerVersion();
|
714
|
+
const uxVersion = getUxPackageVersion();
|
715
|
+
const jsSdkVersion = getJsSdkVersion();
|
716
|
+
|
717
|
+
if (!serverVersion || !uxVersion || !jsSdkVersion) {
|
718
|
+
return false;
|
719
|
+
}
|
720
|
+
// UX 包支持
|
721
|
+
const uxVersionSupport = compareVersions(uxVersion, '3.1.29');
|
722
|
+
// 服务端接口实现支持
|
723
|
+
const serverVersionSupport = compareVersions(serverVersion, '1.16.49-beta-20250822-070545-6d3344cc');
|
724
|
+
// SDK 接口实现支持
|
725
|
+
const jsSdkVersionSupport = compareVersions(jsSdkVersion, '1.16.49-beta-20250822-070545-6d3344cc');
|
726
|
+
return uxVersionSupport && serverVersionSupport && jsSdkVersionSupport;
|
727
|
+
};
|
@@ -1,13 +0,0 @@
|
|
1
|
-
import { jsxs as o, Fragment as i, jsx as n } from "react/jsx-runtime";
|
2
|
-
import { Typography as m } from "@mui/material";
|
3
|
-
import { renderAvatar as d } from "../components.js";
|
4
|
-
function c(a) {
|
5
|
-
const { user: r, avatarSize: t = 48, onAvatarClick: e } = a;
|
6
|
-
return /* @__PURE__ */ o(i, { children: [
|
7
|
-
d(r, t, a.avatarProps, e),
|
8
|
-
/* @__PURE__ */ n(m, { variant: "body1", children: r.fullName || r.email || r.did })
|
9
|
-
] });
|
10
|
-
}
|
11
|
-
export {
|
12
|
-
c as default
|
13
|
-
};
|
@@ -1,17 +0,0 @@
|
|
1
|
-
import { Typography } from '@mui/material';
|
2
|
-
import { UserCardProps, User } from '../types';
|
3
|
-
import { renderAvatar } from '../components';
|
4
|
-
|
5
|
-
// 详细卡片模式下的NameOnly渲染组件
|
6
|
-
function NameOnlyCard(props: Omit<UserCardProps, 'user'> & { user: User }) {
|
7
|
-
const { user, avatarSize = 48, onAvatarClick } = props;
|
8
|
-
|
9
|
-
return (
|
10
|
-
<>
|
11
|
-
{renderAvatar(user, avatarSize, props.avatarProps, onAvatarClick)}
|
12
|
-
<Typography variant="body1">{user.fullName || user.email || user.did}</Typography>
|
13
|
-
</>
|
14
|
-
);
|
15
|
-
}
|
16
|
-
|
17
|
-
export default NameOnlyCard;
|