| <div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div> | <div className='overflow-hidden text-ellipsis whitespace-nowrap'>{name}</div> | ||||
| </div> | </div> | ||||
| { | { | ||||
| !isSelected && ( | |||||
| <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}> | |||||
| <ItemOperation | |||||
| isPinned={isPinned} | |||||
| togglePin={togglePin} | |||||
| isShowDelete={!uninstallable} | |||||
| onDelete={() => onDelete(id)} | |||||
| /> | |||||
| </div> | |||||
| ) | |||||
| <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}> | |||||
| <ItemOperation | |||||
| isPinned={isPinned} | |||||
| togglePin={togglePin} | |||||
| isShowDelete={!uninstallable && !isSelected} | |||||
| onDelete={() => onDelete(id)} | |||||
| /> | |||||
| </div> | |||||
| } | } | ||||
| </div> | </div> | ||||
| ) | ) |
| * conversation info | * conversation info | ||||
| */ | */ | ||||
| const [allConversationList, setAllConversationList] = useState<ConversationItem[]>([]) | const [allConversationList, setAllConversationList] = useState<ConversationItem[]>([]) | ||||
| const [isClearConversationList, { setTrue: clearConversationListTrue, setFalse: clearConversationListFalse }] = useBoolean(false) | |||||
| const [isClearPinnedConversationList, { setTrue: clearPinnedConversationListTrue, setFalse: clearPinnedConversationListFalse }] = useBoolean(false) | |||||
| const { | const { | ||||
| conversationList, | conversationList, | ||||
| setConversationList, | setConversationList, | ||||
| const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true) | const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true) | ||||
| const onMoreLoaded = ({ data: conversations, has_more }: any) => { | const onMoreLoaded = ({ data: conversations, has_more }: any) => { | ||||
| setHasMore(has_more) | setHasMore(has_more) | ||||
| setConversationList([...conversationList, ...conversations]) | |||||
| if (isClearConversationList) { | |||||
| setConversationList(conversations) | |||||
| clearConversationListFalse() | |||||
| } | |||||
| else { | |||||
| setConversationList([...conversationList, ...conversations]) | |||||
| } | |||||
| } | } | ||||
| const onPinnedMoreLoaded = ({ data: conversations, has_more }: any) => { | const onPinnedMoreLoaded = ({ data: conversations, has_more }: any) => { | ||||
| setHasPinnedMore(has_more) | setHasPinnedMore(has_more) | ||||
| setPinnedConversationList([...pinnedConversationList, ...conversations]) | |||||
| if (isClearPinnedConversationList) { | |||||
| setPinnedConversationList(conversations) | |||||
| clearPinnedConversationListFalse() | |||||
| } | |||||
| else { | |||||
| setPinnedConversationList([...pinnedConversationList, ...conversations]) | |||||
| } | |||||
| } | } | ||||
| const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0) | const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0) | ||||
| const noticeUpdateList = () => { | const noticeUpdateList = () => { | ||||
| setConversationList([]) | |||||
| setHasMore(true) | setHasMore(true) | ||||
| setPinnedConversationList([]) | |||||
| clearConversationListTrue() | |||||
| setHasPinnedMore(true) | setHasPinnedMore(true) | ||||
| clearPinnedConversationListTrue() | |||||
| setControlUpdateConversationList(Date.now()) | setControlUpdateConversationList(Date.now()) | ||||
| } | } | ||||
| const handlePin = async (id: string) => { | const handlePin = async (id: string) => { | ||||
| await delConversation(isInstalledApp, installedAppInfo?.id, toDeleteConversationId) | await delConversation(isInstalledApp, installedAppInfo?.id, toDeleteConversationId) | ||||
| notify({ type: 'success', message: t('common.api.success') }) | notify({ type: 'success', message: t('common.api.success') }) | ||||
| hideConfirm() | hideConfirm() | ||||
| if (currConversationId === toDeleteConversationId) | |||||
| handleConversationIdChange('-1') | |||||
| noticeUpdateList() | noticeUpdateList() | ||||
| } | } | ||||
| return ( | return ( | ||||
| <Sidebar | <Sidebar | ||||
| list={conversationList} | list={conversationList} | ||||
| isClearConversationList={isClearConversationList} | |||||
| pinnedList={pinnedConversationList} | pinnedList={pinnedConversationList} | ||||
| isClearPinnedConversationList={isClearPinnedConversationList} | |||||
| onMoreLoaded={onMoreLoaded} | onMoreLoaded={onMoreLoaded} | ||||
| onPinnedMoreLoaded={onPinnedMoreLoaded} | onPinnedMoreLoaded={onPinnedMoreLoaded} | ||||
| isNoMore={!hasMore} | isNoMore={!hasMore} |
| currentId: string | currentId: string | ||||
| onCurrentIdChange: (id: string) => void | onCurrentIdChange: (id: string) => void | ||||
| list: ConversationItem[] | list: ConversationItem[] | ||||
| isClearConversationList: boolean | |||||
| pinnedList: ConversationItem[] | pinnedList: ConversationItem[] | ||||
| isClearPinnedConversationList: boolean | |||||
| isInstalledApp: boolean | isInstalledApp: boolean | ||||
| installedAppId?: string | installedAppId?: string | ||||
| siteInfo: SiteInfo | siteInfo: SiteInfo | ||||
| currentId, | currentId, | ||||
| onCurrentIdChange, | onCurrentIdChange, | ||||
| list, | list, | ||||
| isClearConversationList, | |||||
| pinnedList, | pinnedList, | ||||
| isClearPinnedConversationList, | |||||
| isInstalledApp, | isInstalledApp, | ||||
| installedAppId, | installedAppId, | ||||
| siteInfo, | siteInfo, | ||||
| <PencilSquareIcon className="mr-2 h-4 w-4" /> {t('share.chat.newChat')} | <PencilSquareIcon className="mr-2 h-4 w-4" /> {t('share.chat.newChat')} | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <div className='flex-grow h-0 overflow-y-auto overflow-x-hidden'> | |||||
| <div className={'flex-grow flex flex-col h-0 overflow-y-auto overflow-x-hidden'}> | |||||
| {/* pinned list */} | {/* pinned list */} | ||||
| {hasPinned && ( | {hasPinned && ( | ||||
| <div className='mt-4 px-4'> | |||||
| <div className={cn('mt-4 px-4', list.length === 0 && 'flex flex-col flex-grow')}> | |||||
| <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.pinnedTitle')}</div> | <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.pinnedTitle')}</div> | ||||
| <List | <List | ||||
| className={maxListHeight} | |||||
| className={cn(list.length > 0 ? maxListHeight : 'flex-grow')} | |||||
| currentId={currentId} | currentId={currentId} | ||||
| onCurrentIdChange={onCurrentIdChange} | onCurrentIdChange={onCurrentIdChange} | ||||
| list={pinnedList} | list={pinnedList} | ||||
| isClearConversationList={isClearPinnedConversationList} | |||||
| isInstalledApp={isInstalledApp} | isInstalledApp={isInstalledApp} | ||||
| installedAppId={installedAppId} | installedAppId={installedAppId} | ||||
| onMoreLoaded={onPinnedMoreLoaded} | onMoreLoaded={onPinnedMoreLoaded} | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {/* unpinned list */} | {/* unpinned list */} | ||||
| <div className='mt-4 px-4'> | |||||
| {hasPinned && ( | |||||
| <div className={cn('mt-4 px-4', !hasPinned && 'flex flex-col flex-grow')}> | |||||
| {(hasPinned && list.length > 0) && ( | |||||
| <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.unpinnedTitle')}</div> | <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.unpinnedTitle')}</div> | ||||
| )} | )} | ||||
| <List | <List | ||||
| currentId={currentId} | currentId={currentId} | ||||
| onCurrentIdChange={onCurrentIdChange} | onCurrentIdChange={onCurrentIdChange} | ||||
| list={list} | list={list} | ||||
| isClearConversationList={isClearConversationList} | |||||
| isInstalledApp={isInstalledApp} | isInstalledApp={isInstalledApp} | ||||
| installedAppId={installedAppId} | installedAppId={installedAppId} | ||||
| onMoreLoaded={onMoreLoaded} | onMoreLoaded={onMoreLoaded} | ||||
| onDelete={onDelete} | onDelete={onDelete} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className="flex flex-shrink-0 pr-4 pb-4 pl-4"> | <div className="flex flex-shrink-0 pr-4 pb-4 pl-4"> | ||||
| <div className="text-gray-400 font-normal text-xs">© {copyRight} {(new Date()).getFullYear()}</div> | <div className="text-gray-400 font-normal text-xs">© {copyRight} {(new Date()).getFullYear()}</div> |
| currentId: string | currentId: string | ||||
| onCurrentIdChange: (id: string) => void | onCurrentIdChange: (id: string) => void | ||||
| list: ConversationItem[] | list: ConversationItem[] | ||||
| isClearConversationList: boolean | |||||
| isInstalledApp: boolean | isInstalledApp: boolean | ||||
| installedAppId?: string | installedAppId?: string | ||||
| onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void | onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void | ||||
| currentId, | currentId, | ||||
| onCurrentIdChange, | onCurrentIdChange, | ||||
| list, | list, | ||||
| isClearConversationList, | |||||
| isInstalledApp, | isInstalledApp, | ||||
| installedAppId, | installedAppId, | ||||
| onMoreLoaded, | onMoreLoaded, | ||||
| useInfiniteScroll( | useInfiniteScroll( | ||||
| async () => { | async () => { | ||||
| if (!isNoMore) { | if (!isNoMore) { | ||||
| const lastId = list[list.length - 1]?.id | |||||
| const lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined | |||||
| const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned) | const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned) | ||||
| onMoreLoaded({ data: conversations, has_more }) | onMoreLoaded({ data: conversations, has_more }) | ||||
| } | } | ||||
| return ( | return ( | ||||
| <nav | <nav | ||||
| ref={listRef} | ref={listRef} | ||||
| className={cn(className, 'shrink-0 space-y-1 bg-white pb-[60px] overflow-y-auto')} | |||||
| className={cn(className, 'shrink-0 space-y-1 bg-white pb-[85px] overflow-y-auto')} | |||||
| > | > | ||||
| {list.map((item) => { | {list.map((item) => { | ||||
| const isCurrent = item.id === currentId | const isCurrent = item.id === currentId | ||||
| <span>{item.name}</span> | <span>{item.name}</span> | ||||
| </div> | </div> | ||||
| { | |||||
| !isCurrent && ( | |||||
| <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}> | |||||
| <ItemOperation | |||||
| isPinned={isPinned} | |||||
| togglePin={() => onPinChanged(item.id)} | |||||
| isShowDelete | |||||
| onDelete={() => onDelete(item.id)} | |||||
| /> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| {item.id !== '-1' && ( | |||||
| <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}> | |||||
| <ItemOperation | |||||
| isPinned={isPinned} | |||||
| togglePin={() => onPinChanged(item.id)} | |||||
| isShowDelete | |||||
| onDelete={() => onDelete(item.id)} | |||||
| /> | |||||
| </div> | |||||
| )} | |||||
| </div> | </div> | ||||
| ) | ) | ||||
| })} | })} |