mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 15:25:50 +00:00
feat(sns): incremental contact post-count ranking in filter list
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React from 'react'
|
||||||
import { Search, Calendar, User, X, Filter, Check } from 'lucide-react'
|
import { Search, Calendar, User, X, Loader2 } from 'lucide-react'
|
||||||
import { Avatar } from '../Avatar'
|
import { Avatar } from '../Avatar'
|
||||||
// import JumpToDateDialog from '../JumpToDateDialog' // Assuming this is imported from parent or moved
|
// import JumpToDateDialog from '../JumpToDateDialog' // Assuming this is imported from parent or moved
|
||||||
|
|
||||||
@@ -7,6 +7,14 @@ interface Contact {
|
|||||||
username: string
|
username: string
|
||||||
displayName: string
|
displayName: string
|
||||||
avatarUrl?: string
|
avatarUrl?: string
|
||||||
|
postCount?: number
|
||||||
|
postCountStatus?: 'idle' | 'loading' | 'ready'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContactsCountProgress {
|
||||||
|
resolved: number
|
||||||
|
total: number
|
||||||
|
running: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SnsFilterPanelProps {
|
interface SnsFilterPanelProps {
|
||||||
@@ -21,6 +29,7 @@ interface SnsFilterPanelProps {
|
|||||||
contactSearch: string
|
contactSearch: string
|
||||||
setContactSearch: (val: string) => void
|
setContactSearch: (val: string) => void
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
contactsCountProgress?: ContactsCountProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
||||||
@@ -34,11 +43,12 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
contacts,
|
contacts,
|
||||||
contactSearch,
|
contactSearch,
|
||||||
setContactSearch,
|
setContactSearch,
|
||||||
loading
|
loading,
|
||||||
|
contactsCountProgress
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const filteredContacts = contacts.filter(c =>
|
const filteredContacts = contacts.filter(c =>
|
||||||
c.displayName.toLowerCase().includes(contactSearch.toLowerCase()) ||
|
(c.displayName || '').toLowerCase().includes(contactSearch.toLowerCase()) ||
|
||||||
c.username.toLowerCase().includes(contactSearch.toLowerCase())
|
c.username.toLowerCase().includes(contactSearch.toLowerCase())
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -152,8 +162,17 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{contactsCountProgress && contactsCountProgress.total > 0 && (
|
||||||
|
<div className="contact-count-progress">
|
||||||
|
{contactsCountProgress.running
|
||||||
|
? `朋友圈条数统计中 ${contactsCountProgress.resolved}/${contactsCountProgress.total}`
|
||||||
|
: `朋友圈条数已统计 ${contactsCountProgress.total}/${contactsCountProgress.total}`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="contact-list-scroll">
|
<div className="contact-list-scroll">
|
||||||
{filteredContacts.map(contact => {
|
{filteredContacts.map(contact => {
|
||||||
|
const isPostCountReady = contact.postCountStatus === 'ready'
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={contact.username}
|
key={contact.username}
|
||||||
@@ -164,6 +183,15 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
<div className="contact-meta">
|
<div className="contact-meta">
|
||||||
<span className="contact-name">{contact.displayName}</span>
|
<span className="contact-name">{contact.displayName}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="contact-post-count-wrap">
|
||||||
|
{isPostCountReady ? (
|
||||||
|
<span className="contact-post-count">{Math.max(0, Math.floor(Number(contact.postCount || 0)))}条</span>
|
||||||
|
) : (
|
||||||
|
<span className="contact-post-count-loading" title="统计中">
|
||||||
|
<Loader2 size={13} className="spinning" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1098,6 +1098,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-count-progress {
|
||||||
|
padding: 8px 16px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
border-bottom: 1px dashed color-mix(in srgb, var(--border-color) 70%, transparent);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
.contact-list-scroll {
|
.contact-list-scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -1175,6 +1183,40 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-post-count-wrap {
|
||||||
|
margin-left: 8px;
|
||||||
|
min-width: 46px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-post-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-post-count-loading {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.spinning {
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
.contact-post-count {
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,25 @@ import * as configService from '../services/config'
|
|||||||
const SNS_PAGE_CACHE_TTL_MS = 24 * 60 * 60 * 1000
|
const SNS_PAGE_CACHE_TTL_MS = 24 * 60 * 60 * 1000
|
||||||
const SNS_PAGE_CACHE_POST_LIMIT = 200
|
const SNS_PAGE_CACHE_POST_LIMIT = 200
|
||||||
const SNS_PAGE_CACHE_SCOPE_FALLBACK = '__default__'
|
const SNS_PAGE_CACHE_SCOPE_FALLBACK = '__default__'
|
||||||
|
const CONTACT_COUNT_SORT_DEBOUNCE_MS = 200
|
||||||
|
const CONTACT_COUNT_BATCH_SIZE = 10
|
||||||
|
|
||||||
|
type ContactPostCountStatus = 'idle' | 'loading' | 'ready'
|
||||||
|
|
||||||
interface Contact {
|
interface Contact {
|
||||||
username: string
|
username: string
|
||||||
displayName: string
|
displayName: string
|
||||||
avatarUrl?: string
|
avatarUrl?: string
|
||||||
type?: 'friend' | 'former_friend' | 'sns_only'
|
type?: 'friend' | 'former_friend' | 'sns_only'
|
||||||
|
lastSessionTimestamp?: number
|
||||||
|
postCount?: number
|
||||||
|
postCountStatus?: ContactPostCountStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContactsCountProgress {
|
||||||
|
resolved: number
|
||||||
|
total: number
|
||||||
|
running: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SnsOverviewStats {
|
interface SnsOverviewStats {
|
||||||
@@ -58,6 +71,11 @@ export default function SnsPage() {
|
|||||||
const [contacts, setContacts] = useState<Contact[]>([])
|
const [contacts, setContacts] = useState<Contact[]>([])
|
||||||
const [contactSearch, setContactSearch] = useState('')
|
const [contactSearch, setContactSearch] = useState('')
|
||||||
const [contactsLoading, setContactsLoading] = useState(false)
|
const [contactsLoading, setContactsLoading] = useState(false)
|
||||||
|
const [contactsCountProgress, setContactsCountProgress] = useState<ContactsCountProgress>({
|
||||||
|
resolved: 0,
|
||||||
|
total: 0,
|
||||||
|
running: false
|
||||||
|
})
|
||||||
|
|
||||||
// UI states
|
// UI states
|
||||||
const [showJumpDialog, setShowJumpDialog] = useState(false)
|
const [showJumpDialog, setShowJumpDialog] = useState(false)
|
||||||
@@ -103,6 +121,8 @@ export default function SnsPage() {
|
|||||||
const cacheScopeKeyRef = useRef('')
|
const cacheScopeKeyRef = useRef('')
|
||||||
const scrollAdjustmentRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null)
|
const scrollAdjustmentRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null)
|
||||||
const contactsLoadTokenRef = useRef(0)
|
const contactsLoadTokenRef = useRef(0)
|
||||||
|
const contactsCountHydrationTokenRef = useRef(0)
|
||||||
|
const contactsCountBatchTimerRef = useRef<number | null>(null)
|
||||||
const authorTimelinePostsRef = useRef<SnsPost[]>([])
|
const authorTimelinePostsRef = useRef<SnsPost[]>([])
|
||||||
const authorTimelineLoadingRef = useRef(false)
|
const authorTimelineLoadingRef = useRef(false)
|
||||||
const authorTimelineRequestTokenRef = useRef(0)
|
const authorTimelineRequestTokenRef = useRef(0)
|
||||||
@@ -165,6 +185,31 @@ export default function SnsPage() {
|
|||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizePostCount = useCallback((value: unknown): number => {
|
||||||
|
const numeric = Number(value)
|
||||||
|
if (!Number.isFinite(numeric)) return 0
|
||||||
|
return Math.max(0, Math.floor(numeric))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const compareContactsForRanking = useCallback((a: Contact, b: Contact): number => {
|
||||||
|
const aReady = a.postCountStatus === 'ready'
|
||||||
|
const bReady = b.postCountStatus === 'ready'
|
||||||
|
if (aReady && bReady) {
|
||||||
|
const countDiff = normalizePostCount(b.postCount) - normalizePostCount(a.postCount)
|
||||||
|
if (countDiff !== 0) return countDiff
|
||||||
|
} else if (aReady !== bReady) {
|
||||||
|
return aReady ? -1 : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const tsDiff = Number(b.lastSessionTimestamp || 0) - Number(a.lastSessionTimestamp || 0)
|
||||||
|
if (tsDiff !== 0) return tsDiff
|
||||||
|
return (a.displayName || a.username).localeCompare((b.displayName || b.username), 'zh-Hans-CN')
|
||||||
|
}, [normalizePostCount])
|
||||||
|
|
||||||
|
const sortContactsForRanking = useCallback((input: Contact[]): Contact[] => {
|
||||||
|
return [...input].sort(compareContactsForRanking)
|
||||||
|
}, [compareContactsForRanking])
|
||||||
|
|
||||||
const isDefaultViewNow = useCallback(() => {
|
const isDefaultViewNow = useCallback(() => {
|
||||||
return selectedUsernamesRef.current.length === 0 && !searchKeywordRef.current.trim() && !jumpTargetDateRef.current
|
return selectedUsernamesRef.current.length === 0 && !searchKeywordRef.current.trim() && !jumpTargetDateRef.current
|
||||||
}, [])
|
}, [])
|
||||||
@@ -423,13 +468,145 @@ export default function SnsPage() {
|
|||||||
}
|
}
|
||||||
}, [jumpTargetDate, persistSnsPageCache, searchKeyword, selectedUsernames])
|
}, [jumpTargetDate, persistSnsPageCache, searchKeyword, selectedUsernames])
|
||||||
|
|
||||||
// Load Contacts(仅加载好友/曾经好友,不再统计朋友圈条数)
|
const stopContactsCountHydration = useCallback((resetProgress = false) => {
|
||||||
|
contactsCountHydrationTokenRef.current += 1
|
||||||
|
if (contactsCountBatchTimerRef.current) {
|
||||||
|
window.clearTimeout(contactsCountBatchTimerRef.current)
|
||||||
|
contactsCountBatchTimerRef.current = null
|
||||||
|
}
|
||||||
|
if (resetProgress) {
|
||||||
|
setContactsCountProgress({
|
||||||
|
resolved: 0,
|
||||||
|
total: 0,
|
||||||
|
running: false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setContactsCountProgress((prev) => ({ ...prev, running: false }))
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const hydrateContactPostCounts = useCallback(async (usernames: string[]) => {
|
||||||
|
const targets = usernames
|
||||||
|
.map((username) => String(username || '').trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
stopContactsCountHydration(true)
|
||||||
|
if (targets.length === 0) return
|
||||||
|
|
||||||
|
const runToken = ++contactsCountHydrationTokenRef.current
|
||||||
|
const totalTargets = targets.length
|
||||||
|
const targetSet = new Set(targets)
|
||||||
|
|
||||||
|
setContacts((prev) => {
|
||||||
|
let changed = false
|
||||||
|
const next = prev.map((contact) => {
|
||||||
|
if (!targetSet.has(contact.username)) return contact
|
||||||
|
if (contact.postCountStatus === 'loading' && typeof contact.postCount !== 'number') return contact
|
||||||
|
changed = true
|
||||||
|
return {
|
||||||
|
...contact,
|
||||||
|
postCount: undefined,
|
||||||
|
postCountStatus: 'loading' as ContactPostCountStatus
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return changed ? sortContactsForRanking(next) : prev
|
||||||
|
})
|
||||||
|
setContactsCountProgress({
|
||||||
|
resolved: 0,
|
||||||
|
total: totalTargets,
|
||||||
|
running: true
|
||||||
|
})
|
||||||
|
|
||||||
|
let normalizedCounts: Record<string, number> = {}
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.sns.getUserPostCounts()
|
||||||
|
if (runToken !== contactsCountHydrationTokenRef.current) return
|
||||||
|
if (result.success && result.counts) {
|
||||||
|
normalizedCounts = Object.fromEntries(
|
||||||
|
Object.entries(result.counts).map(([username, value]) => [username, normalizePostCount(value)])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load contact post counts:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolved = 0
|
||||||
|
let cursor = 0
|
||||||
|
const applyBatch = () => {
|
||||||
|
if (runToken !== contactsCountHydrationTokenRef.current) return
|
||||||
|
|
||||||
|
const batch = targets.slice(cursor, cursor + CONTACT_COUNT_BATCH_SIZE)
|
||||||
|
if (batch.length === 0) {
|
||||||
|
setContactsCountProgress({
|
||||||
|
resolved: totalTargets,
|
||||||
|
total: totalTargets,
|
||||||
|
running: false
|
||||||
|
})
|
||||||
|
contactsCountBatchTimerRef.current = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchSet = new Set(batch)
|
||||||
|
setContacts((prev) => {
|
||||||
|
let changed = false
|
||||||
|
const next = prev.map((contact) => {
|
||||||
|
if (!batchSet.has(contact.username)) return contact
|
||||||
|
const nextCount = normalizePostCount(normalizedCounts[contact.username])
|
||||||
|
if (contact.postCountStatus === 'ready' && contact.postCount === nextCount) return contact
|
||||||
|
changed = true
|
||||||
|
return {
|
||||||
|
...contact,
|
||||||
|
postCount: nextCount,
|
||||||
|
postCountStatus: 'ready' as ContactPostCountStatus
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return changed ? sortContactsForRanking(next) : prev
|
||||||
|
})
|
||||||
|
|
||||||
|
resolved += batch.length
|
||||||
|
cursor += batch.length
|
||||||
|
setContactsCountProgress({
|
||||||
|
resolved,
|
||||||
|
total: totalTargets,
|
||||||
|
running: resolved < totalTargets
|
||||||
|
})
|
||||||
|
|
||||||
|
if (cursor < totalTargets) {
|
||||||
|
contactsCountBatchTimerRef.current = window.setTimeout(applyBatch, CONTACT_COUNT_SORT_DEBOUNCE_MS)
|
||||||
|
} else {
|
||||||
|
contactsCountBatchTimerRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyBatch()
|
||||||
|
}, [normalizePostCount, sortContactsForRanking, stopContactsCountHydration])
|
||||||
|
|
||||||
|
// Load Contacts(先按最近会话显示联系人,再异步统计朋友圈条数并增量排序)
|
||||||
const loadContacts = useCallback(async () => {
|
const loadContacts = useCallback(async () => {
|
||||||
const requestToken = ++contactsLoadTokenRef.current
|
const requestToken = ++contactsLoadTokenRef.current
|
||||||
|
stopContactsCountHydration(true)
|
||||||
setContactsLoading(true)
|
setContactsLoading(true)
|
||||||
try {
|
try {
|
||||||
const contactsResult = await window.electronAPI.chat.getContacts()
|
const [contactsResult, sessionsResult] = await Promise.all([
|
||||||
|
window.electronAPI.chat.getContacts(),
|
||||||
|
window.electronAPI.chat.getSessions()
|
||||||
|
])
|
||||||
const contactMap = new Map<string, Contact>()
|
const contactMap = new Map<string, Contact>()
|
||||||
|
const sessionTimestampMap = new Map<string, number>()
|
||||||
|
|
||||||
|
if (sessionsResult.success && Array.isArray(sessionsResult.sessions)) {
|
||||||
|
for (const session of sessionsResult.sessions) {
|
||||||
|
const username = String(session?.username || '').trim()
|
||||||
|
if (!username) continue
|
||||||
|
const ts = Math.max(
|
||||||
|
Number(session?.sortTimestamp || 0),
|
||||||
|
Number(session?.lastTimestamp || 0)
|
||||||
|
)
|
||||||
|
const prevTs = Number(sessionTimestampMap.get(username) || 0)
|
||||||
|
if (ts > prevTs) {
|
||||||
|
sessionTimestampMap.set(username, ts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (contactsResult.success && contactsResult.contacts) {
|
if (contactsResult.success && contactsResult.contacts) {
|
||||||
for (const c of contactsResult.contacts) {
|
for (const c of contactsResult.contacts) {
|
||||||
@@ -438,16 +615,19 @@ export default function SnsPage() {
|
|||||||
username: c.username,
|
username: c.username,
|
||||||
displayName: c.displayName,
|
displayName: c.displayName,
|
||||||
avatarUrl: c.avatarUrl,
|
avatarUrl: c.avatarUrl,
|
||||||
type: c.type === 'former_friend' ? 'former_friend' : 'friend'
|
type: c.type === 'former_friend' ? 'former_friend' : 'friend',
|
||||||
|
lastSessionTimestamp: Number(sessionTimestampMap.get(c.username) || 0),
|
||||||
|
postCount: undefined,
|
||||||
|
postCountStatus: 'idle'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let contactsList = Array.from(contactMap.values())
|
let contactsList = sortContactsForRanking(Array.from(contactMap.values()))
|
||||||
|
|
||||||
if (requestToken !== contactsLoadTokenRef.current) return
|
if (requestToken !== contactsLoadTokenRef.current) return
|
||||||
setContacts(contactsList)
|
setContacts(contactsList)
|
||||||
|
void hydrateContactPostCounts(contactsList.map(contact => contact.username))
|
||||||
|
|
||||||
const allUsernames = contactsList.map(c => c.username)
|
const allUsernames = contactsList.map(c => c.username)
|
||||||
|
|
||||||
@@ -455,7 +635,7 @@ export default function SnsPage() {
|
|||||||
if (allUsernames.length > 0) {
|
if (allUsernames.length > 0) {
|
||||||
const enriched = await window.electronAPI.chat.enrichSessionsContactInfo(allUsernames)
|
const enriched = await window.electronAPI.chat.enrichSessionsContactInfo(allUsernames)
|
||||||
if (enriched.success && enriched.contacts) {
|
if (enriched.success && enriched.contacts) {
|
||||||
contactsList = contactsList.map(contact => {
|
contactsList = contactsList.map((contact) => {
|
||||||
const extra = enriched.contacts?.[contact.username]
|
const extra = enriched.contacts?.[contact.username]
|
||||||
if (!extra) return contact
|
if (!extra) return contact
|
||||||
return {
|
return {
|
||||||
@@ -465,18 +645,31 @@ export default function SnsPage() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (requestToken !== contactsLoadTokenRef.current) return
|
if (requestToken !== contactsLoadTokenRef.current) return
|
||||||
setContacts(contactsList)
|
setContacts((prev) => {
|
||||||
|
const prevMap = new Map(prev.map((contact) => [contact.username, contact]))
|
||||||
|
const merged = contactsList.map((contact) => {
|
||||||
|
const previous = prevMap.get(contact.username)
|
||||||
|
return {
|
||||||
|
...contact,
|
||||||
|
lastSessionTimestamp: previous?.lastSessionTimestamp ?? contact.lastSessionTimestamp,
|
||||||
|
postCount: previous?.postCount,
|
||||||
|
postCountStatus: previous?.postCountStatus ?? contact.postCountStatus
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sortContactsForRanking(merged)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (requestToken !== contactsLoadTokenRef.current) return
|
if (requestToken !== contactsLoadTokenRef.current) return
|
||||||
console.error('Failed to load contacts:', error)
|
console.error('Failed to load contacts:', error)
|
||||||
|
stopContactsCountHydration(true)
|
||||||
} finally {
|
} finally {
|
||||||
if (requestToken === contactsLoadTokenRef.current) {
|
if (requestToken === contactsLoadTokenRef.current) {
|
||||||
setContactsLoading(false)
|
setContactsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [hydrateContactPostCounts, sortContactsForRanking, stopContactsCountHydration])
|
||||||
|
|
||||||
const closeAuthorTimeline = useCallback(() => {
|
const closeAuthorTimeline = useCallback(() => {
|
||||||
authorTimelineRequestTokenRef.current += 1
|
authorTimelineRequestTokenRef.current += 1
|
||||||
@@ -631,10 +824,22 @@ export default function SnsPage() {
|
|||||||
loadOverviewStats()
|
loadOverviewStats()
|
||||||
}, [hydrateSnsPageCache, loadContacts, loadOverviewStats])
|
}, [hydrateSnsPageCache, loadContacts, loadOverviewStats])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
contactsCountHydrationTokenRef.current += 1
|
||||||
|
if (contactsCountBatchTimerRef.current) {
|
||||||
|
window.clearTimeout(contactsCountBatchTimerRef.current)
|
||||||
|
contactsCountBatchTimerRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleChange = () => {
|
const handleChange = () => {
|
||||||
cacheScopeKeyRef.current = ''
|
cacheScopeKeyRef.current = ''
|
||||||
// wxid changed, reset everything
|
// wxid changed, reset everything
|
||||||
|
stopContactsCountHydration(true)
|
||||||
|
setContacts([])
|
||||||
setPosts([]); setHasMore(true); setHasNewer(false);
|
setPosts([]); setHasMore(true); setHasNewer(false);
|
||||||
setSelectedUsernames([]); setSearchKeyword(''); setJumpTargetDate(undefined);
|
setSelectedUsernames([]); setSearchKeyword(''); setJumpTargetDate(undefined);
|
||||||
void hydrateSnsPageCache()
|
void hydrateSnsPageCache()
|
||||||
@@ -644,7 +849,7 @@ export default function SnsPage() {
|
|||||||
}
|
}
|
||||||
window.addEventListener('wxid-changed', handleChange as EventListener)
|
window.addEventListener('wxid-changed', handleChange as EventListener)
|
||||||
return () => window.removeEventListener('wxid-changed', handleChange as EventListener)
|
return () => window.removeEventListener('wxid-changed', handleChange as EventListener)
|
||||||
}, [hydrateSnsPageCache, loadContacts, loadOverviewStats, loadPosts])
|
}, [hydrateSnsPageCache, loadContacts, loadOverviewStats, loadPosts, stopContactsCountHydration])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
@@ -858,6 +1063,7 @@ export default function SnsPage() {
|
|||||||
contactSearch={contactSearch}
|
contactSearch={contactSearch}
|
||||||
setContactSearch={setContactSearch}
|
setContactSearch={setContactSearch}
|
||||||
loading={contactsLoading}
|
loading={contactsLoading}
|
||||||
|
contactsCountProgress={contactsCountProgress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Dialogs and Overlays */}
|
{/* Dialogs and Overlays */}
|
||||||
|
|||||||
Reference in New Issue
Block a user