feat: 增加前端ack面板的概念,抽象化已读未读的面板属性而不是单纯的文本面板

pull/70/head
moonrailgun 3 years ago
parent 66a67bf02b
commit 7d1464104a

@ -54,7 +54,6 @@ export { useLanguage } from './i18n/language';
// hooks
export { createUseStorageState } from './hooks/factory/createUseStorageState';
export { useAvailableServices } from './hooks/model/useAvailableServices';
export { useGroupUnreadState } from './hooks/model/useGroupUnreadState';
export { useMessageNotifyEventFilter } from './hooks/model/useMessageNotifyEventFilter';
export { useUserInfoList } from './hooks/model/useUserInfoList';
export { useUsernames } from './hooks/model/useUsernames';
@ -143,6 +142,7 @@ export type {
GroupBasicInfo,
GroupInvite,
GroupMember,
GroupPanelFeature,
} from './model/group';
export type { InboxItem } from './model/inbox';
export {

@ -22,6 +22,13 @@ export interface GroupMember {
muteUntil?: string;
}
/**
*
*/
export type GroupPanelFeature =
| 'subscribe' // 订阅事件变更状态用于加入socket.io群组
| 'ack'; // 是否包含已读未读检查,如果包含的话需要同时开启 subscribe 特性
export interface GroupPanel {
/**
*

@ -63,7 +63,8 @@ function initial(socket: AppSocket, store: AppStore) {
.request<{
dmConverseIds: string[];
groupIds: string[];
panelIds: string[];
textPanelIds: string[];
subscribeFeaturePanelIds: string[];
}>('chat.converse.findAndJoinRoom')
.catch((err) => {
console.error(err);
@ -75,7 +76,7 @@ function initial(socket: AppSocket, store: AppStore) {
});
Promise.all([conversesP, fetchUserAck()]).then(
([{ dmConverseIds, panelIds }, acks]) => {
([{ dmConverseIds, textPanelIds }, acks]) => {
/**
* TODO:
* acklastMessageMap
@ -91,7 +92,7 @@ function initial(socket: AppSocket, store: AppStore) {
);
});
const converseIds = [...dmConverseIds, ...panelIds];
const converseIds = [...dmConverseIds, ...textPanelIds];
fetchConverseLastMessages(converseIds).then((list) => {
store.dispatch(chatActions.setLastMessageMap(list));
});

@ -1,8 +1,6 @@
import { GroupPanelType } from '../../model/group';
import { useGroupInfo } from '../../redux/hooks/useGroup';
import { useUserNotifyMute } from './useUserSettings';
import { isGroupAckPanel } from '@/utils/group-helper';
import _zip from 'lodash/zip';
import { useUnread } from '../../redux/hooks/useUnread';
import { useGroupInfo, useUnread, useUserNotifyMute } from 'tailchat-shared';
/**
*
@ -13,7 +11,7 @@ export function useGroupUnreadState(
): 'none' | 'muted' | 'unread' {
const group = useGroupInfo(groupId);
const groupTextPanelIds = (group?.panels ?? [])
.filter((panel) => panel.type === GroupPanelType.TEXT)
.filter((panel) => isGroupAckPanel(panel))
.map((p) => p.id);
const { mutedList } = useUserNotifyMute();

@ -6,6 +6,7 @@ import {
GroupPanel,
regSocketEventListener,
PermissionItemType,
GroupPanelFeature,
} from 'tailchat-shared';
import type { MetaFormFieldMeta } from 'tailchat-design';
import type { FullModalFactoryConfig } from '@/components/FullModal/Factory';
@ -88,6 +89,11 @@ export interface PluginGroupPanel {
*
*/
menus?: PluginPanelMenu[];
/**
*
*/
feature?: GroupPanelFeature[];
}
export const [pluginGroupPanel, regGroupPanel] =
buildRegList<PluginGroupPanel>();

@ -16,7 +16,7 @@ interface GroupTextPanelItemProps {
/**
*
*/
export const GroupTextPanelItem: React.FC<GroupTextPanelItemProps> = React.memo(
export const GroupAckPanelItem: React.FC<GroupTextPanelItemProps> = React.memo(
(props) => {
const { groupId, panel } = props;
const panelId = panel.id;
@ -40,4 +40,4 @@ export const GroupTextPanelItem: React.FC<GroupTextPanelItemProps> = React.memo(
);
}
);
GroupTextPanelItem.displayName = 'GroupTextPanelItem';
GroupAckPanelItem.displayName = 'GroupAckPanelItem';

@ -12,7 +12,7 @@ import {
useUserNotifyMute,
} from 'tailchat-shared';
import { GroupPanelItem } from '@/components/GroupPanelItem';
import { GroupTextPanelItem } from './TextPanelItem';
import { GroupAckPanelItem } from './AckPanelItem';
import { Dropdown, MenuProps } from 'antd';
import copy from 'copy-to-clipboard';
import { usePanelWindow } from '@/hooks/usePanelWindow';
@ -20,6 +20,7 @@ import { LoadingSpinner } from '@/components/LoadingSpinner';
import _compact from 'lodash/compact';
import { Icon } from 'tailchat-design';
import { useExtraMenuItems, useGroupPanelExtraBadge } from './utils';
import { isGroupAckPanel } from '@/utils/group-helper';
/**
*
@ -112,8 +113,8 @@ export const SidebarItem: React.FC<{
return (
<Dropdown menu={menu} trigger={['contextMenu']}>
<div>
{panel.type === GroupPanelType.TEXT ? (
<GroupTextPanelItem icon={icon} groupId={groupId} panel={panel} />
{isGroupAckPanel(panel) ? (
<GroupAckPanelItem icon={icon} groupId={groupId} panel={panel} />
) : (
<GroupPanelItem
name={panel.name}

@ -8,10 +8,10 @@ import {
t,
useAppSelector,
useGroupAck,
useGroupUnreadState,
} from 'tailchat-shared';
import { NavbarNavItem } from './NavItem';
import { Dropdown } from 'antd';
import { useGroupUnreadState } from '@/hooks/useGroupUnreadState';
/**
*

@ -0,0 +1,18 @@
import { GroupPanel, GroupPanelType } from 'tailchat-shared';
import { findPluginPanelInfoByName } from './plugin-helper';
/**
*
*/
export function isGroupAckPanel(panel: GroupPanel) {
if (panel.type === GroupPanelType.TEXT) {
return true;
}
if (panel.type === GroupPanelType.GROUP) {
const pluginPanelInfo = findPluginPanelInfoByName(panel.name);
return pluginPanelInfo?.feature?.includes('ack') ?? false;
}
return false;
}

@ -20,6 +20,10 @@ import type { ValidationRuleObject } from 'fastest-validator';
import type { BuiltinEventMap } from '../structs/events';
import { CONFIG_GATEWAY_AFTER_HOOK } from '../const';
import _ from 'lodash';
import {
decodeNoConflictServiceNameKey,
encodeNoConflictServiceNameKey,
} from '../utils';
type ServiceActionHandler<T = any> = (
ctx: TcPureContext<any>
@ -314,7 +318,10 @@ export abstract class TcService extends Service {
* @param panelFeature
*/
async setPanelFeature(panelName: string, panelFeatures: PanelFeature[]) {
await this.setGlobalConfig(`panelFeature.${panelName}`, panelFeatures);
await this.setGlobalConfig(
`panelFeature.${encodeNoConflictServiceNameKey(panelName)}`,
panelFeatures
);
}
/**
@ -326,11 +333,15 @@ export abstract class TcService extends Service {
this.getGlobalConfig<Record<string, PanelFeature[]>>('panelFeature') ??
{};
const matched = Object.entries(map).filter(([panelName, panelFeatures]) =>
panelFeatures.includes(panelFeature)
);
const matched = Object.entries(map).filter(([name, features]) => {
if (Array.isArray(features)) {
return features.includes(panelFeature);
}
return false;
});
return matched.map((m) => m[0]);
return matched.map((m) => decodeNoConflictServiceNameKey(m[0]));
}
/**
@ -384,9 +395,8 @@ export abstract class TcService extends Service {
) {
await this.waitForServices(['gateway', 'config']);
await this.broker.call('config.addToSet', {
key: `${CONFIG_GATEWAY_AFTER_HOOK}.${fullActionName}`.replaceAll(
'.',
'-'
key: encodeNoConflictServiceNameKey(
`${CONFIG_GATEWAY_AFTER_HOOK}.${fullActionName}`
),
value: `${this.serviceName}.${callbackAction}`,
});

@ -0,0 +1,16 @@
const noConflictKey = '$';
/**
* `.` , `.` (lodash.set)
*/
export function encodeNoConflictServiceNameKey(key: string): string {
return key.replaceAll('.', noConflictKey);
}
export function decodeNoConflictServiceNameKey(key: string): string {
if (typeof key !== 'string') {
return '';
}
return key.replaceAll(noConflictKey, '.');
}

@ -9,4 +9,5 @@ regGroupPanel({
label: Translate.topicpanel,
provider: PLUGIN_NAME,
render: Loadable(() => import('./group/GroupTopicPanelRender')),
feature: ['subscribe', 'ack'],
});

@ -224,21 +224,25 @@ class ConverseService extends TcService {
);
// 获取群组列表
const { groupIds, panelIds } = await ctx.call<{
groupIds: string[];
panelIds: string[];
}>('group.getJoinedGroupAndPanelIds');
const { groupIds, textPanelIds, subscribeFeaturePanelIds } =
await ctx.call<{
groupIds: string[];
textPanelIds: string[];
subscribeFeaturePanelIds: string[];
}>('group.getJoinedGroupAndPanelIds');
await call(ctx).joinSocketIORoom([
...dmConverseIds,
...groupIds,
...panelIds,
...textPanelIds,
...subscribeFeaturePanelIds,
]);
return {
dmConverseIds,
groupIds,
panelIds,
textPanelIds,
subscribeFeaturePanelIds,
};
}
}

@ -325,6 +325,7 @@ class MessageService extends TcService {
const { converseIds } = ctx.params;
// 这里使用了多个请求但是通过limit=1会将查询范围降低到最低
// 这种方式会比用聚合操作实际上更加节省资源
const list = await Promise.all(
converseIds.map((id) => {
return this.adapter.model

@ -216,14 +216,20 @@ class GroupService extends TcService {
*
* socket
*/
private getSubscribedGroupPanelIds(group: Group): string[] {
private getSubscribedGroupPanelIds(group: Group): {
textPanelIds: string[];
subscribeFeaturePanelIds: string[];
} {
const textPanelIds = this.getGroupTextPanelIds(group);
const subscribeFeaturePanelIds = this.getGroupPanelIdsWithFeature(
group,
'subscribe'
);
return _.uniq([...textPanelIds, ...subscribeFeaturePanelIds]);
return {
textPanelIds,
subscribeFeaturePanelIds,
};
}
/**
@ -274,10 +280,11 @@ class GroupService extends TcService {
owner: userId,
});
const textPanelIds = this.getSubscribedGroupPanelIds(group);
const { textPanelIds, subscribeFeaturePanelIds } =
this.getSubscribedGroupPanelIds(group);
await call(ctx).joinSocketIORoom(
[String(group._id), ...textPanelIds],
[String(group._id), ...textPanelIds, ...subscribeFeaturePanelIds],
userId
);
@ -297,16 +304,23 @@ class GroupService extends TcService {
*/
async getJoinedGroupAndPanelIds(ctx: TcContext): Promise<{
groupIds: string[];
panelIds: string[];
textPanelIds: string[];
subscribeFeaturePanelIds: string[];
}> {
const groups = await this.getUserGroups(ctx); // TODO: 应该使用call而不是直接调用为了获取tracer和caching支持。目前moleculer的文档没有显式的声明类似localCall的行为可以花时间看一下
const panelIds = _.flatten(
groups.map((g) => this.getSubscribedGroupPanelIds(g))
const textPanelIds = _.flatten(
groups.map((g) => this.getSubscribedGroupPanelIds(g).textPanelIds)
);
const subscribeFeaturePanelIds = _.flatten(
groups.map(
(g) => this.getSubscribedGroupPanelIds(g).subscribeFeaturePanelIds
)
);
return {
groupIds: groups.map((g) => String(g._id)),
panelIds,
textPanelIds,
subscribeFeaturePanelIds,
};
}
@ -494,10 +508,11 @@ class GroupService extends TcService {
this.notifyGroupInfoUpdate(ctx, group); // 推送变更
this.unicastNotify(ctx, userId, 'add', group);
const textPanelIds = this.getSubscribedGroupPanelIds(group);
const { textPanelIds, subscribeFeaturePanelIds } =
this.getSubscribedGroupPanelIds(group);
await call(ctx).joinSocketIORoom(
[String(group._id), ...textPanelIds],
[String(group._id), ...textPanelIds, ...subscribeFeaturePanelIds],
userId
);

Loading…
Cancel
Save