feat: 更多的权限点与前端权限显示

pull/49/head
moonrailgun 3 years ago
parent 439749ca4e
commit 76c898ba92

@ -184,6 +184,10 @@ export {
useGroupTextPanelUnread,
} from './redux/hooks/useGroup';
export { useGroupMemberMute } from './redux/hooks/useGroupMemberMute';
export {
useGroupMemberAllPermissions,
useHasGroupPermission,
} from './redux/hooks/useGroupPermission';
export { useUserInfo, useUserId } from './redux/hooks/useUserInfo';
export { useUnread } from './redux/hooks/useUnread';
export {
@ -218,6 +222,13 @@ export {
export { isValidStr } from './utils/string-helper';
export { isValidJson } from './utils/json-helper';
export { MessageHelper } from './utils/message-helper';
export {
PERMISSION,
AllPermission,
permissionList,
getDefaultPermissionList,
applyDefaultFallbackGroupPermission,
} from './utils/role-helper';
export { uploadFile } from './utils/upload-helper';
export type { UploadFileResult } from './utils/upload-helper';
export { parseUrlStr } from './utils/url-helper';

@ -30,6 +30,7 @@
"devDependencies": {
"@types/crc": "^3.4.0",
"@types/lodash": "^4.14.170",
"@types/react": "^17.0.39",
"@types/react-redux": "^7.1.24",
"react": "17.0.2"
},

@ -0,0 +1,72 @@
import { useGroupInfo } from './useGroup';
import { useUserId } from './useUserInfo';
import _uniq from 'lodash/uniq';
import _flatten from 'lodash/flatten';
import { useDebugValue, useMemo } from 'react';
import { permissionList } from '../..';
/**
*
*/
export function useGroupMemberAllPermissions(groupId: string): string[] {
const groupInfo = useGroupInfo(groupId);
const userId = useUserId();
if (!groupInfo || !userId) {
return [];
}
if (groupInfo.owner === userId) {
// 群组管理员拥有一切权限
// 返回所有权限
return permissionList.map((p) => p.key);
}
const members = groupInfo.members;
const groupRoles = groupInfo.roles;
const userRoles = members.find((m) => m.userId === userId)?.roles ?? [];
const userPermissions = _uniq([
..._flatten(
userRoles.map(
(roleId) =>
groupRoles.find((role) => String(role._id) === roleId)?.permissions ??
[]
)
),
...groupInfo.fallbackPermissions,
]);
useDebugValue({
groupRoles,
userRoles,
userPermissions,
fallbackPermissions: groupInfo.fallbackPermissions,
});
return userPermissions;
}
/**
*
*/
export function useHasGroupPermission(
groupId: string,
permissions: string[]
): boolean[] {
const userPermissions = useGroupMemberAllPermissions(groupId);
const result = useMemo(
() => permissions.map((p) => userPermissions.includes(p)),
[userPermissions.join(','), permissions.join(',')]
);
useDebugValue({
groupId,
userPermissions,
checkedPermissions: permissions,
result,
});
return result;
}

@ -0,0 +1,100 @@
import { model, t } from '..';
/**
*
*
*/
export const AllPermission = Symbol('AllPermission');
interface PermissionItem {
key: string;
title: string;
desc: string;
default: boolean;
required?: string[];
}
export const PERMISSION = {
/**
* core
*/
core: {
message: 'core.message',
invite: 'core.invite',
unlimitedInvite: 'core.unlimitedInvite',
groupDetail: 'core.groupDetail',
managePanel: 'core.managePanel',
manageInvite: 'core.manageInvite',
manageRoles: 'core.manageRoles',
},
};
/**
* TODO:
*/
export const permissionList: PermissionItem[] = [
{
key: PERMISSION.core.message,
title: t('发送消息'),
desc: t('允许成员在文字频道发送消息'),
default: true,
},
{
key: PERMISSION.core.invite,
title: t('邀请链接'),
desc: t('允许成员创建邀请链接'),
default: true,
},
{
key: PERMISSION.core.unlimitedInvite,
title: t('不限时邀请链接'),
desc: t('允许成员创建不限时邀请链接'),
default: false,
required: [PERMISSION.core.invite],
},
{
key: PERMISSION.core.groupDetail,
title: t('查看群组详情'),
desc: t('允许成员查看群组详情'),
default: false,
},
{
key: PERMISSION.core.managePanel,
title: t('允许管理频道'),
desc: t('允许成员查看管理频道'),
default: false,
required: [PERMISSION.core.groupDetail],
},
{
key: PERMISSION.core.manageInvite,
title: t('允许管理邀请链接'),
desc: t('允许成员管理邀请链接'),
default: false,
required: [PERMISSION.core.groupDetail],
},
{
key: PERMISSION.core.manageRoles,
title: t('允许管理身份组'),
desc: t('允许成员管理身份组'),
default: false,
required: [PERMISSION.core.groupDetail],
},
];
/**
*
*/
export function getDefaultPermissionList(): string[] {
return permissionList.filter((p) => p.default).map((p) => p.key);
}
/**
*
*/
export async function applyDefaultFallbackGroupPermission(groupId: string) {
await model.group.modifyGroupField(
groupId,
'fallbackPermissions',
getDefaultPermissionList()
);
}

@ -18,6 +18,9 @@ import _uniq from 'lodash/uniq';
interface ChatInputBoxProps {
onSendMsg: (msg: string, meta?: SendMessagePayloadMeta) => void;
}
/**
*
*/
export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
const inputRef = useRef<HTMLInputElement>(null);
const [message, setMessage] = useState('');

@ -9,6 +9,8 @@ import {
t,
humanizeMsDuration,
useInterval,
useHasGroupPermission,
PERMISSION,
} from 'tailchat-shared';
import { GroupPanelWrapper } from './Wrapper';
@ -18,6 +20,9 @@ import { GroupPanelWrapper } from './Wrapper';
function useChatInputInfo(groupId: string) {
const userId = useUserId();
const muteUntil = useGroupMemberMute(groupId, userId ?? '');
const [hasPermission] = useHasGroupPermission(groupId, [
PERMISSION.core.message,
]);
const [placeholder, setPlaceholder] = useState<string | undefined>(undefined);
const updatePlaceholder = useCallback(() => {
@ -44,6 +49,13 @@ function useChatInputInfo(groupId: string) {
updatePlaceholder();
}, [muteUntil]);
if (!hasPermission) {
return {
disabled: true,
placeholder: t('没有发送消息的权限, 请联系群组所有者'),
};
}
return {
disabled: Boolean(muteUntil),
placeholder,

@ -13,7 +13,7 @@ import { Avatar } from '../Avatar';
import { closeModal, ModalWrapper } from '../Modal';
import { Slides, SlidesRef } from '../Slides';
import { useHistory } from 'react-router';
import { applyDefaultFallbackGroupPermission } from '@/utils/role-helper';
import { applyDefaultFallbackGroupPermission } from 'tailchat-shared';
const panelTemplate: {
key: string;

@ -7,6 +7,8 @@ import {
createGroupInviteCode,
t,
GroupInvite,
PERMISSION,
useHasGroupPermission,
} from 'tailchat-shared';
import styles from './CreateInviteCode.module.less';
@ -30,10 +32,16 @@ export const CreateInviteCode: React.FC<CreateInviteCodeProps> = React.memo(
},
[groupId, onInviteCreated]
);
const [hasInvitePermission, hasUnlimitedInvitePermission] =
useHasGroupPermission(groupId, [
PERMISSION.core.invite,
PERMISSION.core.unlimitedInvite,
]);
const menu = (
<Menu>
<Menu.Item
disabled={!hasUnlimitedInvitePermission}
onClick={() => handleCreateInviteLink(InviteCodeType.Permanent)}
>
{t('创建永久邀请码')}
@ -61,6 +69,7 @@ export const CreateInviteCode: React.FC<CreateInviteCodeProps> = React.memo(
className={styles.createInviteBtn}
size="large"
type="primary"
disabled={!hasInvitePermission}
loading={loading}
onClick={() => handleCreateInviteLink(InviteCodeType.Normal)}
overlay={menu}

@ -4,6 +4,7 @@ import React from 'react';
interface PermissionItemProps {
title: string;
desc?: string;
disabled?: boolean;
checked: boolean;
onChange: (checked: boolean) => void;
}
@ -18,7 +19,11 @@ export const PermissionItem: React.FC<PermissionItemProps> = React.memo(
</Col>
<Col>
<Switch checked={props.checked} onChange={props.onChange} />
<Switch
disabled={props.disabled}
checked={props.checked}
onChange={props.onChange}
/>
</Col>
</Row>

@ -1,6 +1,6 @@
import { Loading } from '@/components/Loading';
import { PillTabPane, PillTabs } from '@/components/PillTabs';
import { AllPermission } from '@/utils/role-helper';
import { AllPermission } from 'tailchat-shared';
import React, { useMemo, useState } from 'react';
import { t, useGroupInfo } from 'tailchat-shared';
import { RoleItem } from './RoleItem';

@ -1,4 +1,4 @@
import { AllPermission, permissionList } from '@/utils/role-helper';
import { AllPermission, permissionList } from 'tailchat-shared';
import { Button } from 'antd';
import React, { useCallback, useMemo } from 'react';
import { model, t } from 'tailchat-shared';
@ -54,6 +54,11 @@ export const RolePermission: React.FC<RolePermissionProps> = React.memo(
key={p.key}
title={p.title}
desc={p.desc}
disabled={
p.required
? !p.required.every((r) => editingPermission.includes(r))
: undefined
}
checked={editingPermission.includes(p.key)}
onChange={(checked) => handleSwitchPermission(p.key, checked)}
/>

@ -1,4 +1,4 @@
import { AllPermission, getDefaultPermissionList } from '@/utils/role-helper';
import { AllPermission, getDefaultPermissionList } from 'tailchat-shared';
import { model, t, useAsyncRequest } from 'tailchat-shared';
export function useRoleActions(

@ -7,11 +7,12 @@ import {
import { GroupIdContextProvider } from '@/context/GroupIdContext';
import { pluginCustomPanel } from '@/plugin/common';
import React, { useCallback, useMemo } from 'react';
import { t } from 'tailchat-shared';
import { PERMISSION, t, useHasGroupPermission } from 'tailchat-shared';
import { GroupInvite } from './Invite';
import { GroupPanel } from './Panel';
import { GroupRole } from './Role';
import { GroupSummary } from './Summary';
import _compact from 'lodash/compact';
interface SettingsViewProps {
groupId: string;
@ -27,6 +28,12 @@ export const GroupDetail: React.FC<SettingsViewProps> = React.memo((props) => {
},
[props.onClose]
);
const [allowManagePanel, allowManageInvite, allowManageRoles] =
useHasGroupPermission(groupId, [
PERMISSION.core.managePanel,
PERMISSION.core.manageInvite,
PERMISSION.core.manageRoles,
]);
const menu: SidebarViewMenuType[] = useMemo(() => {
// 内置
@ -34,29 +41,29 @@ export const GroupDetail: React.FC<SettingsViewProps> = React.memo((props) => {
{
type: 'group',
title: t('通用'),
children: [
children: _compact([
{
type: 'item',
title: t('概述'),
content: <GroupSummary groupId={groupId} />,
},
{
allowManagePanel && {
type: 'item',
title: t('面板'),
content: <GroupPanel groupId={groupId} />,
},
{
allowManageInvite && {
type: 'item',
title: t('邀请码'),
content: <GroupInvite groupId={groupId} />,
},
{
allowManageRoles && {
type: 'item',
title: t('身份组'),
isDev: true,
content: <GroupRole groupId={groupId} />,
},
],
]),
},
];

@ -1,7 +1,12 @@
import React from 'react';
import { Menu } from 'antd';
import _isNil from 'lodash/isNil';
import { useGroupInfo, useTranslation } from 'tailchat-shared';
import {
PERMISSION,
useGroupInfo,
useHasGroupPermission,
useTranslation,
} from 'tailchat-shared';
import { SectionHeader } from '@/components/SectionHeader';
import { useGroupHeaderAction } from './useGroupHeaderAction';
@ -12,8 +17,12 @@ export const GroupHeader: React.FC<GroupHeaderProps> = React.memo((props) => {
const { groupId } = props;
const groupInfo = useGroupInfo(groupId);
const { t } = useTranslation();
const [showGroupDetail, showInvite] = useHasGroupPermission(groupId, [
PERMISSION.core.groupDetail,
PERMISSION.core.invite,
]);
const { isOwner, handleShowGroupDetail, handleInviteUser, handleQuitGroup } =
const { handleShowGroupDetail, handleInviteUser, handleQuitGroup } =
useGroupHeaderAction(groupId);
if (_isNil(groupInfo)) {
@ -22,13 +31,13 @@ export const GroupHeader: React.FC<GroupHeaderProps> = React.memo((props) => {
const menu = (
<Menu>
{isOwner && (
{showGroupDetail && (
<Menu.Item key="0" onClick={handleShowGroupDetail}>
{t('查看详情')}
</Menu.Item>
)}
{isOwner && (
{showInvite && (
<Menu.Item key="1" onClick={handleInviteUser}>
{t('邀请用户')}
</Menu.Item>

@ -47,5 +47,5 @@ export function useGroupHeaderAction(groupId: string) {
}
});
return { isOwner, handleShowGroupDetail, handleInviteUser, handleQuitGroup };
return { handleShowGroupDetail, handleInviteUser, handleQuitGroup };
}

@ -1,31 +0,0 @@
import { model, t } from 'tailchat-shared';
/**
*
*
*/
export const AllPermission = Symbol('AllPermission');
export const permissionList = [
{
key: 'core.message',
title: t('发送消息'),
desc: t('允许成员在文字频道发送消息'),
default: true,
},
];
export function getDefaultPermissionList(): string[] {
return permissionList.filter((p) => p.default).map((p) => p.key);
}
/**
*
*/
export async function applyDefaultFallbackGroupPermission(groupId: string) {
await model.group.modifyGroupField(
groupId,
'fallbackPermissions',
getDefaultPermissionList()
);
}

@ -123,6 +123,7 @@ importers:
'@reduxjs/toolkit': ^1.7.1
'@types/crc': ^3.4.0
'@types/lodash': ^4.14.170
'@types/react': ^17.0.39
'@types/react-redux': ^7.1.24
axios: ^0.21.1
crc: ^3.8.0
@ -166,6 +167,7 @@ importers:
devDependencies:
'@types/crc': 3.8.0
'@types/lodash': 4.14.184
'@types/react': 17.0.48
'@types/react-redux': 7.1.24
react: 17.0.2
@ -2983,7 +2985,7 @@ packages:
react-dom: '*'
dependencies:
'@docusaurus/types': 2.0.0-beta.18
'@types/react': 17.0.48
'@types/react': 18.0.17
'@types/react-router-config': 5.0.6
'@types/react-router-dom': 5.3.3
react: 17.0.2
@ -3295,7 +3297,7 @@ packages:
peerDependencies:
react: '*'
dependencies:
'@types/react': 17.0.48
'@types/react': 18.0.17
prop-types: 15.8.1
react: 17.0.2
@ -15191,7 +15193,7 @@ packages:
pretty-format: 27.5.1
slash: 3.0.0
strip-json-comments: 3.1.1
ts-node: 10.9.1_t4lrjbt3sxauai4t5o275zsepa
ts-node: 10.9.1_bqee57coj3oib6dw4m24wknwqe
transitivePeerDependencies:
- bufferutil
- canvas

Loading…
Cancel
Save