Merge branch 'main' into next

This commit is contained in:
nocobase[bot]
2025-05-15 05:49:17 +00:00
11 changed files with 92 additions and 42 deletions
@@ -7,33 +7,32 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React from 'react';
import { observer } from '@formily/reactive-react';
import { Schema } from '@formily/react';
import { Layout, List, Badge, Button, Flex, Tabs, ConfigProvider, theme } from 'antd';
import { css } from '@emotion/css';
import { Schema } from '@formily/react';
import { observer } from '@formily/reactive-react';
import { useApp } from '@nocobase/client';
import { dayjs } from '@nocobase/utils/client';
import { Badge, Button, ConfigProvider, Flex, Layout, List, Tabs, theme } from 'antd';
import React from 'react';
import { useLocalTranslation } from '../../locale';
import {
fetchChannels,
selectedChannelNameObs,
channelListObs,
isFetchingChannelsObs,
showChannelLoadingMoreObs,
selectedMessageListObs,
channelStatusFilterObs,
ChannelStatus,
channelStatusFilterObs,
fetchChannels,
isFetchingChannelsObs,
selectedChannelNameObs,
selectedMessageListObs,
showChannelLoadingMoreObs,
} from '../observables';
import MessageList from './MessageList';
import FilterTab from './FilterTab';
import { useApp } from '@nocobase/client';
import MessageList from './MessageList';
const InnerInboxContent = () => {
const app = useApp();
const { token } = theme.useToken();
const { t } = useLocalTranslation();
const channels = channelListObs.value;
const messages = selectedMessageListObs.value;
const selectedChannelName = selectedChannelNameObs.value;
const onLoadChannelsMore = () => {
@@ -18,9 +18,11 @@ import { useLocalTranslation } from '../../locale';
import { useApp } from '@nocobase/client';
import {
channelMapObs,
channelStatusFilterObs,
fetchMessages,
inboxVisible,
isFecthingMessageObs,
markAllMessagesAsRead,
selectedChannelNameObs,
selectedMessageListObs,
showMsgLoadingMoreObs,
@@ -42,13 +44,22 @@ const MessageList = observer(() => {
const { token } = theme.useToken();
const [hoveredMessageId, setHoveredMessageId] = useState<string | null>(null);
const selectedChannelName = selectedChannelNameObs.value;
const selectedChannel = selectedChannelName ? channelMapObs.value[selectedChannelName] : null;
const isFetchingMessages = isFecthingMessageObs.value;
const messages = selectedMessageListObs.value;
const msgStatusDict = {
read: t('Read'),
unread: t('Unread'),
};
const onMarkAllReadClick = useCallback(() => {
if (selectedChannelName) {
markAllMessagesAsRead({ channelName: selectedChannelName });
}
}, [selectedChannelName]);
if (!selectedChannelName) return null;
const onItemClicked = (message) => {
updateMessage({
filterByTk: message.id,
@@ -88,9 +99,19 @@ const MessageList = observer(() => {
components: { Badge: { dotSize: 8 } },
}}
>
<Typography.Title level={4} style={{ marginBottom: token.marginLG }}>
{title}
</Typography.Title>
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: token.marginLG }}
>
<Typography.Title level={4} style={{ margin: 0 }}>
{title}
</Typography.Title>
<Button
disabled={selectedChannel?.unreadMsgCnt === 0 || channelStatusFilterObs.value === 'read'}
onClick={onMarkAllReadClick}
>
{t('Mark all as read')}
</Button>
</div>
{messages.length === 0 && isFecthingMessageObs.value ? (
<Spin style={{ width: '100%', marginTop: token.marginXXL }} />
@@ -150,7 +171,7 @@ const MessageList = observer(() => {
<Descriptions.Item label={t('Datetime')}>{dayjs(message.receiveTimestamp).fromNow()}</Descriptions.Item>
<Descriptions.Item label={t('Status')}>
<div style={{ height: token.controlHeight }}>
{hoveredMessageId === message.id && message.status === 'unread' ? (
{hoveredMessageId === message.id ? (
<Button
type="link"
size="small"
@@ -159,12 +180,12 @@ const MessageList = observer(() => {
updateMessage({
filterByTk: message.id,
values: {
status: 'read',
status: message.status === 'read' ? 'unread' : 'read',
},
});
}}
>
{t('Mark as read')}
{t(message.status === 'unread' ? 'Mark as read' : 'Mark as unread')}
</Button>
) : (
<Tag color={message.status === 'unread' ? 'red' : 'green'}>{msgStatusDict[message.status]}</Tag>
@@ -7,10 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observable, autorun, reaction } from '@formily/reactive';
import { autorun, observable, reaction } from '@formily/reactive';
import { merge } from '@nocobase/utils/client';
import { Channel } from '../../types';
import { getAPIClient } from '../utils';
import { merge } from '@nocobase/utils/client';
import { userIdObs } from './user';
export type ChannelStatus = 'all' | 'read' | 'unread';
@@ -60,9 +60,19 @@ export const fetchChannels = async (params: any) => {
channelMapObs.value[channel.name] = channel;
});
}
await countChannels();
isFetchingChannelsObs.value = false;
};
export const countChannels = async () => {
const apiClient = getAPIClient();
const res = await apiClient.request({
url: 'myInAppChannels:list',
method: 'get',
params: { filter: { status: channelStatusFilterObs.value } },
});
const count = res.data?.meta?.count;
if (count >= 0) channelCountObs.value = count;
isFetchingChannelsObs.value = false;
};
autorun(() => {
@@ -7,19 +7,12 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observable, autorun } from '@formily/reactive';
import { Message } from '../../types';
import { getAPIClient } from '../utils';
import {
channelMapObs,
selectedChannelNameObs,
fetchChannels,
InappChannelStatusEnum,
channelStatusFilterObs,
} from './channel';
import { userIdObs } from './user';
import { InAppMessagesDefinition } from '../../types';
import { autorun, observable } from '@formily/reactive';
import { merge } from '@nocobase/utils/client';
import { InAppMessagesDefinition, Message } from '../../types';
import { getAPIClient } from '../utils';
import { channelMapObs, channelStatusFilterObs, fetchChannels, selectedChannelNameObs } from './channel';
import { userIdObs } from './user';
export const messageMapObs = observable<{ value: Record<string, Message> }>({ value: {} });
export const isFecthingMessageObs = observable<{ value: boolean }>({ value: false });
@@ -76,7 +69,24 @@ export const updateMessage = async (params: { filterByTk: any; values: Record<an
});
const unupdatedMessage = messageMapObs.value[params.filterByTk];
messageMapObs.value[params.filterByTk] = { ...unupdatedMessage, ...params.values };
// fetchChannels({ filter: { name: unupdatedMessage.channelName, status: InappChannelStatusEnum.all } });
fetchChannels({ filter: { name: unupdatedMessage.channelName, status: 'all' } });
updateUnreadMsgsCount();
};
export const markAllMessagesAsRead = async ({ channelName }: { channelName: string }) => {
const apiClient = getAPIClient();
await apiClient.request({
resource: InAppMessagesDefinition.name,
action: 'update',
method: 'post',
params: { filter: { status: 'unread', channelName: channelName }, values: { status: 'read' } },
});
Object.values(messageMapObs.value).forEach((message) => {
if (message.channelName === channelName && message.status === 'unread') {
message.status = 'read';
}
});
fetchChannels({ filter: { name: channelName, status: 'all' } });
updateUnreadMsgsCount();
};
@@ -8,11 +8,11 @@
*/
import { observable, reaction } from '@formily/reactive';
import { SSEData } from '../../types';
import { messageMapObs, updateUnreadMsgsCount } from './message';
import { fetchChannels } from './channel';
import { getAPIClient } from '../utils';
import { uid } from '@nocobase/utils/client';
import { SSEData } from '../../types';
import { getAPIClient } from '../utils';
import { fetchChannels } from './channel';
import { messageMapObs, updateUnreadMsgsCount } from './message';
export const liveSSEObs = observable<{ value: SSEData | null }>({ value: null });
reaction(
@@ -18,10 +18,14 @@
"Channel name": "Channel name",
"Message group name": "Message group name",
"Message title": "Message title",
"Content type": "Content type",
"Plain text": "Plain text",
"Message content": "Message content",
"Details page for desktop": "Details page for desktop",
"Support two types of links: internal links and external links. If using an internal link, the link starts with\"/\", for example, \"/admin\". If using an external link, the link starts with \"http\", for example, \"https://example.com\".": "Support two types of links: internal links and external links. If using an internal link, the link starts with \"/\", for example, \"/admin\". If using an external link, the link starts with \"http\", for example, \"https://example.com\".",
"Mark as read": "Mark as read",
"Mark as unread": "Mark as unread",
"Mark all as read": "Mark all as read",
"Support two types of links: internal links and external links. If using an internal link, the link starts with\"/\", for example, \"/m\". If using an external link, the link starts with \"http\", for example, \"https://example.com\".": "Support two types of links: internal links and external links. If using an internal link, the link starts with \"/\", for example, \"/m\". If using an external link, the link starts with \"http\", for example, \"https://example.com\".",
"Details page for mobile": "Details page for mobile",
"The message page has already been created.": "The message page has already been created.",
@@ -18,10 +18,14 @@
"Message group name": "消息分组名称",
"Message title": "消息标题",
"Message content": "消息内容",
"Content type": "内容格式",
"Plain text": "纯文本",
"detail URL": "详情链接",
"Details page for desktop": "桌面端详情页",
"Support two types of links: internal links and external links. If using an internal link, the link starts with\"/\", for example, \"/admin\". If using an external link, the link starts with \"http\", for example, \"https://example.com\".": "支持两种链接类型:内部链接和外部链接。如果使用内部链接,链接以“/”开头,例如“/admin”。如果使用外部链接,链接以“http”开头,例如“https://example.com”。",
"Mark as read": "标为已读",
"Mark as read": "标为已读",
"Mark as unread": "标为未读",
"Mark all as read": "全部标为已读",
"Support two types of links: internal links and external links. If using an internal link, the link starts with\"/\", for example, \"/m\". If using an external link, the link starts with \"http\", for example, \"https://example.com\".": "支持两种链接类型:内部链接和外部链接。如果使用内部链接,链接以“/”开头,例如“/m”。如果使用外部链接,链接以“http”开头,例如“https://example.com”。",
"Details page for mobile": "移动端详情页",
"The message page has already been created.": "站内信页面已创建。",
@@ -10,7 +10,7 @@
import { i18n } from '@nocobase/client';
import { useTranslation } from 'react-i18next';
export const NAMESPACE = '@nocobase/plugin-notification-manager';
export const NAMESPACE = 'notification-manager';
export function lang(key: string) {
return i18n.t(key, { ns: NAMESPACE });
@@ -181,7 +181,7 @@ export const channelsSchema: ISchema = {
properties: {
edit: {
type: 'void',
title: 'Edit',
title: '{{t("Edit")}}',
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
@@ -20,6 +20,7 @@
"Add new": "Add new",
"Trigger From": "Trigger From",
"Status": "Status",
"Edit": "Edit",
"Created At": "Created At",
"Delete record": "Delete record",
"Are you sure you want to delete it?": "Are you sure you want to delete it?",
@@ -25,6 +25,7 @@
"Are you sure you want to delete it?": "确定要删除吗?",
"Deleted successfully!": "删除成功!",
"Add user": "添加用户",
"Edit": "编辑",
"Select users": "选择用户",
"Query users": "查询用户",
"Receiver type": "接收人类型",