Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
1a7aa33d02
|
|
@ -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 |
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue