diff --git a/packages/tsdaodaobase/src/Components/Conversation/index.tsx b/packages/tsdaodaobase/src/Components/Conversation/index.tsx index d422fbd..99391e4 100644 --- a/packages/tsdaodaobase/src/Components/Conversation/index.tsx +++ b/packages/tsdaodaobase/src/Components/Conversation/index.tsx @@ -480,12 +480,12 @@ export class Conversation extends Component implements Conver } render() { - const { chatBg, channel } = this.props + const { chatBg, channel,initLocateMessageSeq } = this.props const channelInfo = WKSDK.shared().channelManager.getChannelInfo(channel) return { - this.vm = new ConversationVM(channel) + this.vm = new ConversationVM(channel,initLocateMessageSeq) return this.vm }} render={(vm: ConversationVM) => { return <> diff --git a/packages/tsdaodaobase/src/Components/Conversation/vm.ts b/packages/tsdaodaobase/src/Components/Conversation/vm.ts index 0c5f95b..8e17841 100644 --- a/packages/tsdaodaobase/src/Components/Conversation/vm.ts +++ b/packages/tsdaodaobase/src/Components/Conversation/vm.ts @@ -24,7 +24,7 @@ export default class ConversationVM extends ProviderListener { currentConversation?: Conversation // 当前最近会话 messagesOfOrigin: MessageWrap[] = [] // 原始消息集合(不包含时间消息等本地消息) browseToMessageSeq: number = 0 // 已经预览到的最新的messageSeq - initLocateMessageSeq: number = 0 // 初始定位的消息messageSeq 0为不定位 + initLocateMessageSeq?: number = 0 // 初始定位的消息messageSeq 0为不定位 shouldShowHistorySplit: boolean = false // 是否应该显示历史消息分割线 private _editOn: boolean = false // 是否开启编辑模式 orgUnreadCount: number = 0 // 原未读数量 @@ -57,10 +57,14 @@ export default class ConversationVM extends ProviderListener { onFirstMessagesLoaded?: Function // 第一屏消息已加载完成 - constructor(channel: Channel) { + constructor(channel: Channel, initLocateMessageSeq?: number) { super() this.channel = channel - // this.initLocateMessageSeq = initLocateMessageSeq + if(initLocateMessageSeq==0) { + this.initLocateMessageSeq = undefined + }else { + this.initLocateMessageSeq = initLocateMessageSeq + } } get currentReplyMessage() { @@ -131,7 +135,7 @@ export default class ConversationVM extends ProviderListener { } } - + // 选中消息 checkedMessage(message: Message, checked: boolean): void { let messageWrap = this.findMessageWithClientMsgNo(message.clientMsgNo) @@ -255,11 +259,11 @@ export default class ConversationVM extends ProviderListener { didMount(): void { this.conversationListener = (conversation: Conversation, action: ConversationAction) => { - if(!conversation.channel.isEqual(this.channel)) { + if (!conversation.channel.isEqual(this.channel)) { return } - if(action == ConversationAction.update) { - console.log("update-2--->",conversation.unread) + if (action == ConversationAction.update) { + console.log("update-2--->", conversation.unread) this.unreadCount = conversation.unread } } @@ -315,8 +319,8 @@ export default class ConversationVM extends ProviderListener { WKApp.endpointManager.setMethod(EndpointID.clearChannelMessages, (channel: Channel) => { if (channel.isEqual(this.channel)) { - if(this.messagesOfOrigin.length > 0) { - this.browseToMessageSeq = this.messagesOfOrigin[this.messagesOfOrigin.length-1].messageSeq + if (this.messagesOfOrigin.length > 0) { + this.browseToMessageSeq = this.messagesOfOrigin[this.messagesOfOrigin.length - 1].messageSeq } this.messagesOfOrigin = [] this.messages = [] @@ -364,7 +368,7 @@ export default class ConversationVM extends ProviderListener { this.orgUnreadCount = unread this.unreadCount = unread this.currentConversation = conversation - + this.shouldShowHistorySplit = unread > 0 if (unread > 0) { @@ -383,7 +387,7 @@ export default class ConversationVM extends ProviderListener { WKSDK.shared().conversationManager.openConversation = conversation } - this.requestMessagesOfFirstPage(undefined, () => { + this.requestMessagesOfFirstPage(this.initLocateMessageSeq, () => { if (this.onFirstMessagesLoaded) { this.onFirstMessagesLoaded() } @@ -404,8 +408,8 @@ export default class ConversationVM extends ProviderListener { } // 加载频道信息完成 - async loadChannelInfoFinished() { - if(this.channel.channelType !== ChannelTypeGroup) { + async loadChannelInfoFinished() { + if (this.channel.channelType !== ChannelTypeGroup) { return } this.reloadSubscribers() @@ -416,25 +420,25 @@ export default class ConversationVM extends ProviderListener { this.reloadSubscribers() }) - if(this.channelInfo?.orgData?.group_type == SuperGroup) { + if (this.channelInfo?.orgData?.group_type == SuperGroup) { // 如果是超级群则只获取第一页成员 - this.subscribers = await this.getFirstPageMembers() - WKSDK.shared().channelManager.subscribeCacheMap.set(this.channel.getChannelKey(), this.subscribers) - WKSDK.shared().channelManager.notifySubscribeChangeListeners(this.channel) - this.notifyListener() - }else { + this.subscribers = await this.getFirstPageMembers() + WKSDK.shared().channelManager.subscribeCacheMap.set(this.channel.getChannelKey(), this.subscribers) + WKSDK.shared().channelManager.notifySubscribeChangeListeners(this.channel) + this.notifyListener() + } else { WKSDK.shared().channelManager.syncSubscribes(this.channel) } - + } // 获取第一页成员列表(超大群) getFirstPageMembers() { - return WKApp.dataSource.channelDataSource.subscribers(this.channel,{ - limit: 100, - page: 1 - }) + return WKApp.dataSource.channelDataSource.subscribers(this.channel, { + limit: 100, + page: 1 + }) } // 标记提醒已完成 @@ -690,7 +694,7 @@ export default class ConversationVM extends ProviderListener { // 刷新新消息数量 refreshNewMsgCount() { - + const oldUnreadCount = this.unreadCount if (this.browseToMessageSeq == 0) { this.unreadCount = 0 @@ -1023,7 +1027,7 @@ export default class ConversationVM extends ProviderListener { } } newMessages.push(message) - if (shouldShowHistorySplit && this.initLocateMessageSeq > 0 && message.messageSeq === this.initLocateMessageSeq) { + if (shouldShowHistorySplit && this.initLocateMessageSeq && this.initLocateMessageSeq > 0 && message.messageSeq === this.initLocateMessageSeq) { newMessages.push(new MessageWrap(this.getHistorySplit())) } } diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/index.css b/packages/tsdaodaobase/src/Components/GlobalSearch/index.css new file mode 100644 index 0000000..e69de29 diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/index.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/index.tsx new file mode 100644 index 0000000..d725049 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/index.tsx @@ -0,0 +1,126 @@ +import { Component, ReactNode } from "react"; +import React from "react"; +import { Input } from '@douyinfe/semi-ui'; +import { IconSearch } from '@douyinfe/semi-icons'; +import { Tabs } from '@douyinfe/semi-ui'; +import Provider from "../../Service/Provider"; +import GlobalSearchVM from "./vm"; +import TabAll from "./tab-all"; +import TabContacts from "./tab-contacts"; +import TabGroup from "./tab-group"; +import TabFile from "./tab-file"; +import { Channel } from "wukongimjssdk"; + +interface GlobalSearchProps { + channel?: Channel; // 查询指定频道的聊天记录 + // item点击事件,传递item和type,type为contacts、group、message,file + onClick?: (item: any, type: string) => void; +} + +export default class GlobalSearch extends Component { + vm!: GlobalSearchVM + + + tabPanel(key: string) { + + // message + if (key === 'all') { + return { + this.vm.loadMore() + }} + onClick={(item, type) => { + if (this.props.onClick) { + this.props.onClick(item, type) + } + }} + /> + } + + // contacts + if (key === 'contacts') { + return { + if (this.props.onClick) { + this.props.onClick(item, "contacts") + } + }} + > + } + + // groups + if (key === 'groups') { + return { + if (this.props.onClick) { + this.props.onClick(item, "group") + } + }} + > + } + + // files + if (key === 'files') { + return { + this.vm.loadMore() + }} + onClick={(item) => { + if (this.props.onClick) { + this.props.onClick(item, "file") + } + }} + /> + } + } + + render(): ReactNode { + const { channel } = this.props; + return { + this.vm = new GlobalSearchVM() + this.vm.channel = channel + return this.vm + }} + render={(vm: GlobalSearchVM) => { + + return
+ { + vm.searchInChannel ?
{vm.searchTitle}
: undefined + } + } + showClear + style={{ height: "40px" }} + onCompositionStart={() => { vm.isComposing = true; }} + onCompositionEnd={(e: any) => { + vm.isComposing = false; + vm.handleInputChange(e.target.value); + }} + onChange={(value) => { + vm.handleInputChange(value); + }}> +
+ { + vm.onTabClick(key); + }} + > + {this.tabPanel(vm.selectedTabKey)} + +
+
+ }}> + +
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/item-contacts.css b/packages/tsdaodaobase/src/Components/GlobalSearch/item-contacts.css new file mode 100644 index 0000000..68be279 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/item-contacts.css @@ -0,0 +1,34 @@ + +.wk-item-contacts { + display: flex; + align-items: center; + padding: 10px; + border-radius: 4px; + cursor: pointer; + +} + +.wk-item-contacts:hover { + background-color: #f0f0f0; /* Change this color to your desired hover color */ +} + +body[theme-mode=dark] .wk-item-contacts:hover { + background-color: #333333; /* Change this color to your desired hover color */ +} + +.wk-item-contacts-name { + margin-left: 15px; + color: var(--wk-text-item); + font-size: 14px; + font-weight: 500; +} + +body[theme-mode=dark] .wk-item-contacts-name { + color: white; +} + + +mark { + background-color: transparent; /* 移除默认背景色 */ + color: var(--wk-color-theme); /* 设置文本颜色为红色,可以根据需要更改 */ +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/item-contacts.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/item-contacts.tsx new file mode 100644 index 0000000..1088529 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/item-contacts.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Component, ReactNode } from "react"; +import WKAvatar from "../WKAvatar"; +import "./item-contacts.css" +interface ItemContactsProps { + avatar: string; + name: string; + onClick?: () => void; +} + +export default class ItemContacts extends Component { + + render(): ReactNode { + return
{ + if(this.props.onClick){ + this.props.onClick() + } + }}> + +
+
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/item-file.css b/packages/tsdaodaobase/src/Components/GlobalSearch/item-file.css new file mode 100644 index 0000000..0ece122 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/item-file.css @@ -0,0 +1,60 @@ + + +.wk-item-file-icon { + width: 42px; + height: 42px; + display: flex; + justify-content: center; + align-items: center; +} + +.wk-item-file { + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; /* 两行 */ + gap: 0px; + padding: 10px; + border-radius: 4px; +} + +.wk-item-file:hover { + background-color: #f0f0f0; /* Change this color to your desired hover color */ +} + +body[theme-mode=dark] .wk-item-file:hover { + background-color: #333333; /* Change this color to your desired hover color */ +} + + +.wk-item-file-icon { + grid-column: 1 / 2; /* 第一列 */ + grid-row: 1 / 3; /* 第一行 */ +} + +.wk-item-file-name { + grid-column: 2 / 3; /* 第二列 */ + grid-row: 1 / 2; /* 第一行 */ + margin-left: 15px; + color: var(--wk-text-item); + font-size: 14px; + font-weight: 500; +} + +.wk-item-file-desc { + grid-row: 2 / 3; /* 第二行 */ + grid-column: 2 / 3; /* 第二列 */ + display: flex; + margin-left: 15px; + color: #666; + font-size: 12px; + align-items: center; +} + +.wk-item-file-line { + width: 1px; + height: 8px; + background-color: #666; + margin-top: 10px; + margin-bottom: 10px; + margin: 4px; +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/item-file.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/item-file.tsx new file mode 100644 index 0000000..455d1ed --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/item-file.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { Component, ReactNode } from "react"; +import "./item-file.css" +import FileHelper from "../../Utils/filehelper"; +import { getTimeStringAutoShort2 } from "../../Utils/time"; +interface ItemFileProps { + message: any; + sender?: string; + onClick?: () => void; +} + +export default class ItemFile extends Component { + + render(): ReactNode { + const file = this.props.message.payload; + const channel = this.props.message.channel; + const realName = file.name?.replaceAll("", "").replaceAll("", ""); + const fileIconInfo = FileHelper.getFileIconInfo(realName); + return
{ + if (this.props.onClick) { + this.props.onClick() + } + }}> +
+ +
+
+
+
{this.props.sender}
+
{channel.channel_name}
+
{FileHelper.getFileSizeFormat(file.size || 0)}
+
{getTimeStringAutoShort2(this.props.message.timestamp * 1000, true)}
+
+
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/item-group.css b/packages/tsdaodaobase/src/Components/GlobalSearch/item-group.css new file mode 100644 index 0000000..e44332c --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/item-group.css @@ -0,0 +1,32 @@ + +.wk-item-group { + display: flex; + align-items: center; + padding: 10px; + border-radius: 4px; + cursor: pointer; +} + + +body[theme-mode=dark] .wk-item-group:hover { + background-color: #333333; /* Change this color to your desired hover color */ +} + +.wk-item-group:hover { + background-color: #f0f0f0; /* Change this color to your desired hover color */ +} + +.wk-item-group-name { + margin-left: 15px; + color: #333; + font-size: 14px; + font-weight: 500; +} + +body[theme-mode=dark] .wk-item-group-name { + color: white; +} + + + + diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/item-group.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/item-group.tsx new file mode 100644 index 0000000..dfa6df6 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/item-group.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Component, ReactNode } from "react"; +import WKAvatar from "../WKAvatar"; +import "./item-group.css" +interface ItemGroupProps { + avatar: string; + name: string; + onClick?: () => void; +} + +export default class ItemGroup extends Component { + + render(): ReactNode { + return
{ + if(this.props.onClick){ + this.props.onClick() + } + }}> + +
+
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/item-message.css b/packages/tsdaodaobase/src/Components/GlobalSearch/item-message.css new file mode 100644 index 0000000..7c30b9a --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/item-message.css @@ -0,0 +1,53 @@ + +.wk-item-message { + display: flex; + align-items: center; + padding: 10px; + border-radius: 4px; + cursor: pointer; + +} + +.wk-item-message:hover { + background-color: #f0f0f0; /* Change this color to your desired hover color */ +} +body[theme-mode=dark] .wk-item-message:hover { + background-color: #333333; /* Change this color to your desired hover color */ +} + +.wk-item-message-content { + display: grid; + grid-template-columns: 1fr; /* 两列,左边自适应,右边自动宽度 */ + grid-template-rows: auto auto; /* 两行 */ + gap: 0px; + min-width: 200px; + max-width: 420px; + +} + +.wk-item-message-name { + margin-left: 15px; + color: #333; + font-size: 14px; + font-weight: 500; + grid-column: 1 / 2; /* 第一列 */ + grid-row: 1 / 2; /* 第一行 */ +} + +body[theme-mode=dark] .wk-item-message-name { + color: white; +} + + +.wk-item-message-digest { + margin-left: 15px; + color: #999; + white-space: nowrap; /* 不换行 */ + overflow: hidden; /* 超出部分隐藏 */ + text-overflow: ellipsis; /* 超出部分以省略号显示 */ +} + +mark { + background-color: transparent; /* 移除默认背景色 */ + color: var(--wk-color-theme); /* 设置文本颜色为红色,可以根据需要更改 */ +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/item-message.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/item-message.tsx new file mode 100644 index 0000000..655973a --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/item-message.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Component, ReactNode } from "react"; +import WKAvatar from "../WKAvatar"; +import "./item-message.css" +interface ItemMessageProps { + avatar: string; // 会话头像 + name: string; // 会话名字 + digest: string; // 消息摘要 + sender?: string; // 发送者 + onClick?: () => void; +} + +export default class ItemMessage extends Component { + + render(): ReactNode { + + let digest = this.props?.digest + if(this.props.sender && this.props.sender !== ""){ + digest = this.props.sender + ": " + digest + } + + return
{ + if (this.props.onClick) { + this.props.onClick() + } + } }> + +
+
{this.props.name}
+ {/*
{this.props.time}
*/} +
+
+
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/section.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/section.tsx new file mode 100644 index 0000000..d243700 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/section.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { Component, ReactNode } from "react"; + + +interface SectionProps { + title: string; + children?: ReactNode; +} + +export default class Section extends Component { + render(): ReactNode { + return
+
{this.props.title}
+ {this.props.children} +
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/tab-all.css b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-all.css new file mode 100644 index 0000000..b1e2145 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-all.css @@ -0,0 +1,9 @@ + + +.wk-tab-all { + width: 100%; + height: 50vh; + display: flex; + overflow: auto; + flex-direction: column; +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/tab-all.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-all.tsx new file mode 100644 index 0000000..e02be95 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-all.tsx @@ -0,0 +1,131 @@ +import React, { Component } from "react"; +import { ReactNode } from "react"; +import Section from "./section"; +import ItemContacts from "./item-contacts"; +import ItemGroup from "./item-group"; +import ItemMessage from "./item-message"; +import WKApp from "../../App"; +import "./tab-all.css" +import WKSDK, { Channel, ChannelTypePerson, MessageContentType } from "wukongimjssdk"; +import { MessageContentTypeConst } from "../../Service/Const"; + + +interface TabAllProps { + keyword?: string; + searchResult?: any; + loadMore?: () => void; // 添加加载更多的回调函数 + // item点击事件,传递item和type,type为contacts、group、message + onClick?: (item: any, type: string) => void; +} + +export default class TabAll extends Component { + + handleScroll = (event: React.UIEvent) => { + const { scrollTop, scrollHeight, clientHeight } = event.currentTarget; + if (scrollTop + clientHeight >= scrollHeight) { + if (this.props.loadMore) { + this.props.loadMore(); + } + } + }; + + render(): ReactNode { + + let existFriends = this.props.searchResult?.friends.length > 0 + let existGroups = this.props.searchResult?.groups.length > 0 + let existMessages = this.props.searchResult?.messages.length > 0 + + return
+ + { + existFriends ? (
+ { + this.props.searchResult.friends.map((item: any) => { + return { + if (this.props.onClick) { + this.props.onClick(item, "contacts") + } + }} + /> + }) + } +
) : null + } + { + existGroups ? ( +
+ { + this.props.searchResult?.groups.map((item: any) => { + if (this.props.keyword && item.channel_name.indexOf(this.props.keyword) !== -1) { + item.channel_name = item.channel_name.replace(this.props.keyword, `${this.props.keyword}`) + } + return { + if (this.props.onClick) { + this.props.onClick(item, "group") + } + }} + /> + }) + } +
+ ) : null + } + + { + existMessages ? ( +
+ { + this.props.searchResult?.messages.map((item: any) => { + let digest = "[未知消息]" + if(item.content) { + digest = item.content.conversationDigest + }else { + if (item.payload.type === MessageContentType.text) { + digest = item.payload.content + } else if (item.payload.type === MessageContentTypeConst.file) { + digest = `[${item.payload.name}]` + } + } + + + let sender; + if (item.channel.channel_type !== ChannelTypePerson && item.from_uid && item.from_uid !== "") { + const senderChannel = new Channel(item.from_uid, ChannelTypePerson) + const channelInfo = WKSDK.shared().channelManager.getChannelInfo(senderChannel) + if (channelInfo) { + sender = channelInfo.title + } else { + WKSDK.shared().channelManager.fetchChannelInfo(senderChannel) + } + } + + return { + if (this.props.onClick) { + this.props.onClick(item, "message") + } + }} + /> + }) + } +
+ ) : null + } + + +
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/tab-contacts.css b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-contacts.css new file mode 100644 index 0000000..0d0ce4c --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-contacts.css @@ -0,0 +1,8 @@ + +.wk-tab-contacts { + width: 100%; + height: 50vh; + display: flex; + overflow: auto; + flex-direction: column; +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/tab-contacts.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-contacts.tsx new file mode 100644 index 0000000..aa9642c --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-contacts.tsx @@ -0,0 +1,37 @@ +import React, { Component } from "react"; +import { ReactNode } from "react"; +import ItemContacts from "./item-contacts"; +import WKApp from "../../App"; +import "./tab-contacts.css" + +interface TabContactsProps { + keyword?: string; + friends?: any[]; + onClick?: (item: any) => void; +} + +export default class TabContacts extends Component { + + + render(): ReactNode { + return
+ { + this.props.friends?.map((item: any) => { + if (this.props.keyword && item.channel_name.indexOf(this.props.keyword) !== -1) { + item.channel_name = item.channel_name.replace(this.props.keyword, `${this.props.keyword}`) + } + return { + if(this.props.onClick) { + this.props.onClick(item) + } + }} + /> + }) + } +
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/tab-file.css b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-file.css new file mode 100644 index 0000000..80a2bf9 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-file.css @@ -0,0 +1,8 @@ +.wk-tab-file { + width: 100%; + height: 50vh; + display: flex; + overflow: auto; + flex-direction: column; + cursor: pointer; +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/tab-file.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-file.tsx new file mode 100644 index 0000000..8764245 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-file.tsx @@ -0,0 +1,52 @@ +import React, { Component } from "react"; +import { ReactNode } from "react"; +import ItemFile from "./item-file"; +import WKApp from "../../App"; +import "./tab-file.css" +import WKSDK, { Channel, ChannelTypePerson } from "wukongimjssdk"; + +interface TabFileProps { + keyword?: string; + files?: any[]; + loadMore?: () => void; // 添加加载更多的回调函数 + onClick?: (item: any) => void; +} + +export default class TabFile extends Component { + + handleScroll = (event: React.UIEvent) => { + const { scrollTop, scrollHeight, clientHeight } = event.currentTarget; + if (scrollTop + clientHeight >= scrollHeight) { + if (this.props.loadMore) { + this.props.loadMore(); + } + } + }; + render(): ReactNode { + return
+ { + this.props.files?.map((item: any) => { + let sender; + const senderChannel = new Channel(item.from_uid, ChannelTypePerson) + const channelInfo = WKSDK.shared().channelManager.getChannelInfo(senderChannel) + if (channelInfo) { + sender = channelInfo.title + } else { + WKSDK.shared().channelManager.fetchChannelInfo(senderChannel) + } + + return { + if(this.props.onClick) { + this.props.onClick(item) + } + }} + /> + }) + } +
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/tab-group.css b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-group.css new file mode 100644 index 0000000..4188d63 --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-group.css @@ -0,0 +1,9 @@ + + +.wk-tab-group { + width: 100%; + height: 50vh; + display: flex; + overflow: auto; + flex-direction: column; +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/tab-group.tsx b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-group.tsx new file mode 100644 index 0000000..ce8571b --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/tab-group.tsx @@ -0,0 +1,37 @@ +import React, { Component } from "react"; +import { ReactNode } from "react"; +import ItemGroup from "./item-group"; +import WKApp from "../../App"; +import "./tab-group.css" + +interface TabGroupProps { + keyword?: string; + groups?: any[]; + onClick?: (item: any) => void; +} + +export default class TabGroup extends Component { + + + render(): ReactNode { + return
+ { + this.props.groups?.map((item: any) => { + if (this.props.keyword && item.channel_name.indexOf(this.props.keyword) !== -1) { + item.channel_name = item.channel_name.replace(this.props.keyword, `${this.props.keyword}`) + } + return { + if(this.props.onClick) { + this.props.onClick(item) + } + }} + /> + }) + } +
+ } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/GlobalSearch/vm.ts b/packages/tsdaodaobase/src/Components/GlobalSearch/vm.ts new file mode 100644 index 0000000..ec4e6ed --- /dev/null +++ b/packages/tsdaodaobase/src/Components/GlobalSearch/vm.ts @@ -0,0 +1,219 @@ +import WKSDK, { Channel, ChannelInfo, ChannelInfoListener, ChannelTypePerson, MessageContentManager, SystemContent } from "wukongimjssdk"; +import APIClient from "../../Service/APIClient"; +import { MessageContentTypeConst } from "../../Service/Const"; +import { ProviderListener } from "../../Service/Provider"; + +export default class GlobalSearchVM extends ProviderListener { + // 选中的tab组件 + private _selectedTabKey = "all"; + + public page = 1 // 当前页码 + public limit = 20 // 每页条数 + public keyword = "" // 搜索关键字 + public searchResult: any + public isComposing: boolean = false; // 是否正在输入(防止中文输入法干扰) + public loadMoreing = false; // 是否正在加载更多中 + public loadFinish = false; // 是否加载完成 + public contentTypes = new Array() // 内容类型 + private channelInfoListener!: ChannelInfoListener; + public channel?: Channel // 查询指定频道的消息 + // tab数据列表 + public get tabList() { + if (this.searchInChannel) { + return [ + { tab: '聊天', itemKey: 'all' }, + { tab: '文件', itemKey: 'files' }, + ]; + } + return [ + { tab: '聊天', itemKey: 'all' }, + { tab: '联系人', itemKey: 'contacts' }, + { tab: '群组', itemKey: 'groups' }, + { tab: '文件', itemKey: 'files' }, + ]; + } + + public get selectedTabKey() { + return this._selectedTabKey; + } + + public set selectedTabKey(value: string) { + this._selectedTabKey = value; + this.notifyListener() + } + + // 是否在频道内搜索 + public get searchInChannel(): boolean { + return this.channel !== undefined + } + // 搜索标题 + public get searchTitle() { + if (this.searchInChannel) { + const channelInfo = WKSDK.shared().channelManager.getChannelInfo(this.channel!) + if(channelInfo) { + return `与“${channelInfo.title}”的聊天记录` + } + return "" + } + return undefined + } + + // tab选中事件 + public onTabClick(key: string) { + if (key === "files") { + this.contentTypes = [MessageContentTypeConst.file] + this.initLoad() + this.requestSearch() + } else { + this.contentTypes = [] + this.initLoad() + this.requestSearch() + } + this.selectedTabKey = key; + } + + didMount(): void { + this.requestSearch() + + this.channelInfoListener = (channelInfo: ChannelInfo) => { + if (channelInfo.channel.channelType !== ChannelTypePerson) { + return + } + if (this.searchResult?.messages && this.searchResult.messages.length > 0) { + this.searchResult.messages.forEach((item: any) => { + if (item.from_uid === channelInfo.channel.channelID) { + this.notifyListener() + return + } + }) + } + } + + WKSDK.shared().channelManager.addListener(this.channelInfoListener) + } + + didUnMount(): void { + WKSDK.shared().channelManager.removeListener(this.channelInfoListener) + } + + // 输入框输入事件 + public handleInputChange = (value: string) => { + if (!this.isComposing) { + this.keyword = value; + console.log(this.keyword); + this.initLoad() + this.requestSearch(); + } + }; + + public initLoad() { + this.page = 1 + this.loadFinish = false + this.loadMoreing = false + this.searchResult = null + this.notifyListener() + } + + // 请求搜索 + public requestSearch() { + + const param: any = { + keyword: this.keyword || "", + page: this.page, + limit: this.limit, + content_type: this.contentTypes + } + + + if (this.channel) { + param.channel_id = this.channel.channelID + param.channel_type = this.channel.channelType + param.only_message = 1 + } + + console.log("requestSearch", param); + + + APIClient.shared.post("/search/global", param).then(res => { + + if (res.messages.length < this.limit) { + this.loadFinish = true + } + if (this.loadMoreing) { + if (this.searchResult) { + this.searchResult.messages = this.searchResult.messages?.concat(res.messages) + } else { + this.searchResult = res + } + + } else { + this.searchResult = res + } + + // 替换备注如果有备注的话 + this.searchResult.friends?.forEach((v: any) => { + if (v.channel_remark && v.channel_remark !== "") { + v.channel_name = v.channel_remark + } + }) + this.searchResult.groups?.forEach((v: any) => { + if (v.channel_remark && v.channel_remark !== "") { + v.channel_name = v.channel_remark + } + }) + this.searchResult.messages?.forEach((v: any) => { + if (v.channel.channel_remark && v.channel.channel_remark !== "") { + v.channel.channel_name = v.channel.channel_remark + } + + // 解析消息内容 + if(v.payload) { + const contentType = v.payload.type + + const messageContent = MessageContentManager.shared().getMessageContent(contentType) + if (messageContent) { + messageContent.decode(this.jsonToUint8Array(v.payload)) + } + + if(messageContent instanceof SystemContent) { + messageContent.content["content"] = "[系统消息]" + } + + v.content = messageContent + } + + }) + }).finally(() => { + this.loadMoreing = false + this.notifyListener() + }) + } + + jsonToUint8Array(json: any): Uint8Array { + // 将 JSON 对象转换为字符串 + const jsonString = JSON.stringify(json); + + return this.stringToUint8Array(jsonString) + } + + stringToUint8Array(str: string): Uint8Array { + const newStr = unescape(encodeURIComponent(str)) + const arr = new Array(); + for (let i = 0, j = newStr.length; i < j; ++i) { + arr.push(newStr.charCodeAt(i)); + } + const tmpUint8Array = new Uint8Array(arr); + return tmpUint8Array + } + + // 加载更多消息 + loadMore() { + if (this.loadMoreing) { + return + } + this.loadMoreing = true + this.page++ + this.requestSearch() + console.log("加载更多"); + } +} \ No newline at end of file diff --git a/packages/tsdaodaobase/src/Components/WKBase/index.tsx b/packages/tsdaodaobase/src/Components/WKBase/index.tsx index 40cfb2b..dba9576 100644 --- a/packages/tsdaodaobase/src/Components/WKBase/index.tsx +++ b/packages/tsdaodaobase/src/Components/WKBase/index.tsx @@ -35,6 +35,7 @@ export class GlobalModalOptions { footer?: ReactNode; className?: string; closable?: boolean; + onCancel?: () => void; } export interface WKBaseProps { @@ -248,6 +249,7 @@ export default class WKBase visible={this.state.showGlobalModal} width={this.state.globalModalOptions?.width} footer={this.state.globalModalOptions?.footer} + onCancel={this.state.globalModalOptions?.onCancel} > {this.state.globalModalOptions?.body} diff --git a/packages/tsdaodaobase/src/EndpointCommon.tsx b/packages/tsdaodaobase/src/EndpointCommon.tsx index 82c1134..77bcab5 100644 --- a/packages/tsdaodaobase/src/EndpointCommon.tsx +++ b/packages/tsdaodaobase/src/EndpointCommon.tsx @@ -11,6 +11,11 @@ export class MessageContextMenus { onClick?: () => void; } +export class ShowConversationOptions { + // 聊天消息定位的messageSeq + initLocateMessageSeq?: number; +} + export class EndpointCommon { private _onLogins: VoidFunction[] = []; // 登录成功 @@ -32,10 +37,11 @@ export class EndpointCommon { } } - showConversation(channel: Channel) { + showConversation(channel: Channel, opts?: ShowConversationOptions) { WKApp.shared.openChannel = channel; EndpointManager.shared.invoke(EndpointID.showConversation, { channel: channel, + opts: opts, }); WKApp.shared.notifyListener(); } @@ -65,21 +71,38 @@ export class EndpointCommon { EndpointID.showConversation, (param: any) => { const channel = param.channel as Channel; - const conversation = - WKSDK.shared().conversationManager.findConversation(channel); - let initLocateMessageSeq = 0; - if ( - conversation && - conversation.lastMessage && - conversation.unread > 0 && - conversation.lastMessage.messageSeq > conversation.unread - ) { - initLocateMessageSeq = - conversation.lastMessage.messageSeq - conversation.unread; + let opts: ShowConversationOptions = {} + if (param.opts) { + opts = param.opts } + + let initLocateMessageSeq = 0; + if (opts && opts.initLocateMessageSeq && opts.initLocateMessageSeq > 0) { + initLocateMessageSeq = opts.initLocateMessageSeq; + } + + if (initLocateMessageSeq <= 0) { + const conversation = + WKSDK.shared().conversationManager.findConversation(channel); + if ( + conversation && + conversation.lastMessage && + conversation.unread > 0 && + conversation.lastMessage.messageSeq > conversation.unread + ) { + initLocateMessageSeq = + conversation.lastMessage.messageSeq - conversation.unread; + } + } + + let key = channel.getChannelKey() + if (initLocateMessageSeq > 0) { + key = `${key}-${initLocateMessageSeq}` + } + WKApp.routeRight.replaceToRoot( diff --git a/packages/tsdaodaobase/src/Pages/Chat/index.tsx b/packages/tsdaodaobase/src/Pages/Chat/index.tsx index 33d35c9..8892b73 100644 --- a/packages/tsdaodaobase/src/Pages/Chat/index.tsx +++ b/packages/tsdaodaobase/src/Pages/Chat/index.tsx @@ -3,9 +3,9 @@ import { Conversation } from "../../Components/Conversation"; import ConversationList from "../../Components/ConversationList"; import Provider from "../../Service/Provider"; -import { Spin, Button, Popover } from "@douyinfe/semi-ui"; -import { IconPlus } from "@douyinfe/semi-icons"; -import { ChatVM } from "./vm"; +import { Spin, Modal, Popover } from "@douyinfe/semi-ui"; +import { IconPlus, IconSearch } from "@douyinfe/semi-icons"; +import { ChatVM, handleGlobalSearchClick } from "./vm"; import "./index.css"; import { ConversationWrap } from "../../Service/Model"; import WKApp, { ThemeMode } from "../../App"; @@ -15,11 +15,12 @@ import { Channel, ChannelInfo, WKSDK } from "wukongimjssdk"; import { ChannelInfoListener } from "wukongimjssdk"; import { ChatMenus } from "../../App"; import ConversationContext from "../../Components/Conversation/context"; -import { EndpointID } from "../../Service/Const"; +import GlobalSearch from "../../Components/GlobalSearch"; +import { ShowConversationOptions } from "../../EndpointCommon"; export interface ChatContentPageProps { channel: Channel; - initLocateMessageSeq?: number; + initLocateMessageSeq?: number; // 打开时定位到某条消息 } export interface ChatContentPageState { @@ -180,7 +181,7 @@ export default class ChatPage extends Component { componentWillUnmount() { } - + render(): ReactNode { return ( @@ -201,6 +202,14 @@ export default class ChatPage extends Component {
{vm.connectTitle}
+
{ + vm.showGlobalSearch = true; + }} + > + +
{ vm.showAddPopover = false; @@ -220,6 +229,7 @@ export default class ChatPage extends Component { >
{ vm.showAddPopover = !vm.showAddPopover; }} @@ -253,6 +263,23 @@ export default class ChatPage extends Component {
+ { + vm.showGlobalSearch = false + }} + footer={null} + width="80%" + > +
+ { + handleGlobalSearchClick(item,type,()=>{ + vm.showGlobalSearch = false + }) + }}/> +
+
); }} diff --git a/packages/tsdaodaobase/src/Pages/Chat/vm.ts b/packages/tsdaodaobase/src/Pages/Chat/vm.ts index 5ac2109..af62560 100644 --- a/packages/tsdaodaobase/src/Pages/Chat/vm.ts +++ b/packages/tsdaodaobase/src/Pages/Chat/vm.ts @@ -9,6 +9,7 @@ import { ProviderListener } from "../../Service/Provider"; import { animateScroll, scroller } from 'react-scroll'; import { ProhibitwordsService } from "../../Service/ProhibitwordsService"; import { EndpointID } from "../../Service/Const"; +import { ShowConversationOptions } from "../../EndpointCommon"; export class ChatVM extends ProviderListener { conversations: ConversationWrap[] = new Array() @@ -22,6 +23,7 @@ export class ChatVM extends ProviderListener { private channelListener!: ChannelInfoListener private messageDeleteListener!: MessageDeleteListener private conversationListID = "wk-conversationlist" + private _showGlobalSearch = false // 是否显示全局搜索 set showAddPopover(v: boolean) { @@ -33,6 +35,15 @@ export class ChatVM extends ProviderListener { return this._showAddPopover } + set showGlobalSearch(v: boolean) { + this._showGlobalSearch = v + this.notifyListener() + } + + get showGlobalSearch() { + return this._showGlobalSearch + } + set selectedConversation(v: ConversationWrap | undefined) { this._selectedConversation = v this.notifyListener() @@ -64,7 +75,7 @@ export class ChatVM extends ProviderListener { // 根据连接状态设置标题 this.setConnectTitleWithConnectStatus(WKSDK.shared().connectManager.status) - if(WKSDK.shared().connectManager.status == ConnectStatus.Connected){ // 如果已经连接则直接加载 + if (WKSDK.shared().connectManager.status == ConnectStatus.Connected) { // 如果已经连接则直接加载 this.reloadRequestConversationList() } @@ -87,7 +98,7 @@ export class ChatVM extends ProviderListener { } if (action === ConversationAction.add) { console.log("ConversationAction-----add") - if(conversation.lastMessage?.content && conversation.lastMessage?.contentType === MessageContentType.text) { + if (conversation.lastMessage?.content && conversation.lastMessage?.contentType === MessageContentType.text) { conversation.lastMessage.content.text = ProhibitwordsService.shared.filter(conversation.lastMessage?.content.text) } this.conversations = [new ConversationWrap(conversation), ...this.conversations] @@ -97,7 +108,7 @@ export class ChatVM extends ProviderListener { const existConversation = this.findConversation(conversation.channel) if (existConversation) { existConversation.conversation = conversation - if(existConversation.lastMessage?.content && existConversation.lastMessage?.contentType === MessageContentType.text) { + if (existConversation.lastMessage?.content && existConversation.lastMessage?.contentType === MessageContentType.text) { existConversation.lastMessage.content.text = ProhibitwordsService.shared.filter(existConversation.lastMessage?.content.text) } } @@ -183,11 +194,11 @@ export class ChatVM extends ProviderListener { } } - async clearMessages(channel: Channel) { - + async clearMessages(channel: Channel) { + const conversationWrap = this.findConversation(channel) if (!conversationWrap) { - return + return } await WKApp.conversationProvider.clearConversationMessages(conversationWrap.conversation) conversationWrap.conversation.lastMessage = undefined @@ -195,7 +206,7 @@ export class ChatVM extends ProviderListener { WKApp.endpointManager.invoke(EndpointID.clearChannelMessages, channel) this.sortConversations() this.notifyListener() - } + } setConnectTitleWithConnectStatus(connectStatus: ConnectStatus) { @@ -255,7 +266,7 @@ export class ChatVM extends ProviderListener { const conversations = await WKSDK.shared().conversationManager.sync({}) if (conversations && conversations.length > 0) { for (const conversation of conversations) { - if(conversation.lastMessage?.content && conversation.lastMessage?.contentType == MessageContentType.text) { + if (conversation.lastMessage?.content && conversation.lastMessage?.contentType == MessageContentType.text) { conversation.lastMessage.content.text = ProhibitwordsService.shared.filter(conversation.lastMessage.content.text) } conversationWraps.push(new ConversationWrap(conversation)) @@ -268,4 +279,32 @@ export class ChatVM extends ProviderListener { WKApp.menus.refresh() } +} + + +// 处理搜索内容点击事件 +export function handleGlobalSearchClick(item: any, type: string,hideModal?:()=>void) { + if (type === "contacts" || type === "group") { + if(hideModal){ + hideModal() + } + WKApp.endpoints.showConversation(new Channel(item.channel_id, item.channel_type)) + } else if (type === "message") { + const opts = new ShowConversationOptions() + opts.initLocateMessageSeq = item.message_seq + if(hideModal){ + hideModal() + } + WKApp.endpoints.showConversation(new Channel(item.channel.channel_id, item.channel.channel_type), opts) + } else if (type === "file") { + // 下载文件 + const payload = item.payload + let downloadURL = WKApp.dataSource.commonDataSource.getImageURL(payload.url || '') + if (downloadURL.indexOf("?") != -1) { + downloadURL += "&filename=" + payload.name + } else { + downloadURL += "?filename=" + payload.name + } + window.open(`${downloadURL}`, 'top'); + } } \ No newline at end of file diff --git a/packages/tsdaodaobase/src/module.tsx b/packages/tsdaodaobase/src/module.tsx index bc866bb..a13e302 100644 --- a/packages/tsdaodaobase/src/module.tsx +++ b/packages/tsdaodaobase/src/module.tsx @@ -80,6 +80,8 @@ import { ScreenshotCell, ScreenshotContent } from "./Messages/Screenshot"; import ImageToolbar from "./Components/ImageToolbar"; import { ProhibitwordsService } from "./Service/ProhibitwordsService"; import { SubscriberList } from "./Components/Subscribers/list"; +import GlobalSearch from "./Components/GlobalSearch"; +import { handleGlobalSearchClick } from "./Pages/Chat/vm"; export default class BaseModule implements IModule { messageTone?: Howl; @@ -1211,6 +1213,40 @@ export default class BaseModule implements IModule { 1000 ); + WKApp.shared.channelSettingRegister( + "channel.base.settingMessageHistory", + (context) => { + const data = context.routeData() as ChannelSettingRouteData; + const channel = data.channel + + return new Section({ + rows: [ + new Row({ + cell: ListItem, + properties: { + title: "查找聊天内容", + onClick: () => { + WKApp.shared.baseContext.showGlobalModal({ + body: { + handleGlobalSearchClick(item, type,()=>{ + WKApp.shared.baseContext.hideGlobalModal() + }) + }} />, + width: "80%", + height: "80%", + onCancel: () => { + WKApp.shared.baseContext.hideGlobalModal() + } + }) + }, + }, + }), + ], + }); + }, + 1100 + ); + WKApp.shared.channelSettingRegister( "channel.base.setting2", (context) => {