Merge remote-tracking branch 'origin/main'

This commit is contained in:
DIYgod 2024-07-15 23:52:35 +08:00
commit 1a7aa33d02
No known key found for this signature in database
6 changed files with 121 additions and 26 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path stroke="#10161F" stroke-width="2" d="M21 12c0 3-4.03 6.5-9 6.5S3 15 3 12s4.03-6.5 9-6.5S21 9 21 12Z"/><path stroke="#10161F" stroke-width="2" d="M14 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"/></svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@ -26,6 +26,11 @@ export const setHydrated = (v: boolean) => {
_isHydrated = v
}
/**
* @description Check if database data is hydrated to store, or current database is ready.
* If users disabled data persist, it's always false, that means you can't do operation with database.
*
*/
export const isHydrated = () => _isHydrated
export const hydrateDatabaseToStore = async () => {

View File

@ -146,3 +146,13 @@ export const pluralize = (
}
return postfix(noun, rule)
}
export const omitObjectUndefinedValue = (obj: Record<string, any>) => {
const newObj = {}
for (const key in obj) {
if (obj[key] !== undefined) {
newObj[key] = obj[key]
}
}
return newObj
}

View File

@ -73,10 +73,26 @@ export const useEntriesByView = () => {
view,
...(unreadOnly === true && { read: false }),
})
const entries = useEntryIdsByFeedIdOrView(isAllFeeds ? view : feedId!, {
unread: unreadOnly,
view,
})
const remoteEntryIds = query.data?.pages
?.map((page) => page.data?.map((entry) => entry.entries.id))
.flat() as string[]
const currentEntries = useEntryIdsByFeedIdOrView(
isAllFeeds ? view : feedId!,
{
unread: unreadOnly,
view,
},
)
// If remote data is not available, we use the local data, get the local data length
// FIXME: remote first, then local store data
// NOTE: We still can't use the store's data handling directly.
// Imagine that the local data may be persistent, and then if there are incremental updates to the data on the server side,
// then we have no way to incrementally update the data.
// We need to add an interface to incrementally update the data based on the version hash.
const entries = remoteEntryIds || currentEntries
useHotkeys(
shortcuts.entries.refetch.key,
@ -93,7 +109,7 @@ export const useEntriesByView = () => {
useEffect(() => {
prevEntries.current = []
}, [routeParams.feedId, routeParams.view])
const localEntries = useMemo(() => {
const mergedEntries = useMemo(() => {
if (!unreadOnly) {
prevEntries.current = []
return entries
@ -102,35 +118,22 @@ export const useEntriesByView = () => {
prevEntries.current = entries
return entries
}
if (entries.length > prevEntries.current.length) {
prevEntries.current = entries
return entries
}
// merge the new entries with the old entries, and unique them
const nextIds = [...new Set([...prevEntries.current, ...entries])]
prevEntries.current = nextIds
return nextIds
}, [entries, prevEntries, unreadOnly])
const sortLocalEntries = () =>
const sortEntries = () =>
isCollection ?
sortEntriesIdByStarAt(localEntries) :
sortEntriesIdByEntryPublishedAt(localEntries)
const remoteEntryIds = query.data?.pages
?.map((page) => page.data?.map((entry) => entry.entries.id))
.flat() as string[]
sortEntriesIdByStarAt(mergedEntries) :
sortEntriesIdByEntryPublishedAt(mergedEntries)
return {
...query,
// If remote data is not available, we use the local data, get the local data length
// FIXME: remote first, then local store data
// NOTE: We still can't use the store's data handling directly.
// Imagine that the local data may be persistent, and then if there are incremental updates to the data on the server side,
// then we have no way to incrementally update the data.
// We need to add an interface to incrementally update the data based on the version hash.
entriesIds: remoteEntryIds ?? sortLocalEntries(),
totalCount: query.data?.pages?.[0]?.total ?? localEntries.length,
entriesIds: sortEntries(),
totalCount: query.data?.pages?.[0]?.total ?? mergedEntries.length,
}
}

View File

@ -1,7 +1,10 @@
import { useUISettingKey } from "@renderer/atoms/settings/ui"
import { useUser } from "@renderer/atoms/user"
import { m } from "@renderer/components/common/Motion"
import { Logo } from "@renderer/components/icons/logo"
import { AutoResizeHeight } from "@renderer/components/ui/auto-resize-height"
import { Avatar, AvatarFallback, AvatarImage } from "@renderer/components/ui/avatar"
import { Tooltip, TooltipContent, TooltipTrigger } from "@renderer/components/ui/tooltip"
import { useAuthQuery, useTitle } from "@renderer/hooks/common"
import { stopPropagation } from "@renderer/lib/dom"
import { parseHtml } from "@renderer/lib/parse-html"
@ -42,6 +45,8 @@ export const EntryContent = ({ entryId }: { entryId: ActiveEntryId }) => {
}
function EntryContentRender({ entryId }: { entryId: string }) {
const user = useUser()
const { error, data } = useAuthQuery(Queries.entries.byId(entryId), {
staleTime: 300_000,
meta: {
@ -143,6 +148,67 @@ function EntryContentRender({ entryId }: { entryId: string }) {
{entry.entries.publishedAt &&
new Date(entry.entries.publishedAt).toLocaleString()}
</div>
<div className="mt-2 flex items-center gap-2 text-[13px] text-zinc-500">
<div className="flex items-center gap-1 font-medium">
<i className="i-mgc-eye-2-cute-re" />
<span>
{(
(data?.entries.entryReadHistories.readCount ?? 0) +
(data?.entries.entryReadHistories.users.every((u) => u.id !== user?.id) ? 1 : 0) // if no me, +1
).toLocaleString()}
</span>
</div>
<div className="flex items-center">
{[
{
id: user?.id,
name: user?.name ?? null,
image: user?.image ?? null,
handle: user?.handle ?? null,
},
] // myself first
.concat(
data?.entries.entryReadHistories.users.filter(
(u) => u.id !== user?.id,
) ?? [],
) // then others
.slice(0, 10) // only show 10
.concat(
data?.entries.entryReadHistories.readCount &&
data.entries.entryReadHistories.readCount > 10 ?
[
{
id: "more",
name: `+${
data?.entries.entryReadHistories.readCount - 10
}`,
image: null,
handle: null,
},
] :
[],
) // show more count
.map((user, i) => (
<Tooltip key={user.id}>
<TooltipTrigger>
<div
style={{
transform: `translateX(-${i * 5}px)`,
}}
>
<Avatar className="aspect-square size-6 border border-black dark:border-white">
<AvatarImage src={user?.image || undefined} />
<AvatarFallback>
{user.name?.slice(0, 2)}
</AvatarFallback>
</Avatar>
</div>
</TooltipTrigger>
<TooltipContent side="top">{user.name}</TooltipContent>
</Tooltip>
))}
</div>
</div>
</a>
<WrappedElementProvider boundingDetection>
<TitleMetaHandler entryId={entry.entries.id} />

View File

@ -1,5 +1,8 @@
import { apiClient } from "@renderer/lib/api-fetch"
import { getEntriesParams } from "@renderer/lib/utils"
import {
getEntriesParams,
omitObjectUndefinedValue,
} from "@renderer/lib/utils"
import type {
CombinedEntryModel,
EntryModel,
@ -7,7 +10,7 @@ import type {
} from "@renderer/models"
import { EntryService } from "@renderer/services"
import { produce } from "immer"
import { merge, omit } from "lodash-es"
import { isNil, merge, omit } from "lodash-es"
import { isHydrated } from "../../initialize/hydrate"
import { feedActions } from "../feed"
@ -98,7 +101,8 @@ class EntryActions {
produce(state, (draft) => {
const entry = draft.flatMapEntries[entryId]
if (!entry) return
Object.assign(entry, changed)
Object.assign(entry, omitObjectUndefinedValue(changed))
return draft
}),
)
@ -204,6 +208,12 @@ class EntryActions {
this.patch(entryId, {
read,
})
if (!isHydrated()) return
if (!isNil(read)) {
EntryService.bulkStoreReadStatus({
[entryId]: read,
})
}
}
markReadByFeedId(feedId: string) {