Merge branch 'main' into feature/actions

This commit is contained in:
DIYgod 2024-06-11 21:49:11 +08:00
commit 5e5af68c8c
No known key found for this signature in database
7 changed files with 149 additions and 136 deletions

View File

@ -1,12 +1,10 @@
import { useAsRead } from "@renderer/hooks/useAsRead"
import { useEntryActions } from "@renderer/hooks/useEntryActions"
import { apiClient } from "@renderer/lib/api-fetch"
import { useEntryActions, useRead } from "@renderer/hooks/useEntryActions"
import { views } from "@renderer/lib/constants"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { cn } from "@renderer/lib/utils"
import { feedActions, useFeedStore } from "@renderer/store"
import { entryActions, useEntry } from "@renderer/store/entry"
import { useMutation } from "@tanstack/react-query"
import { useEntry } from "@renderer/store/entry"
import { ReactVirtuosoItemPlaceholder } from "../ui/placeholder"
@ -29,20 +27,7 @@ export function EntryItemWrapper({
const asRead = useAsRead(entry)
const read = useMutation({
mutationFn: async () =>
apiClient.reads.$post({
json: {
entryIds: [entry.entries.id],
},
}),
onMutate: () => {
entryActions.optimisticUpdate(entry.entries.id, {
read: true,
})
},
// TODO fallback
})
const markReadMutation = useRead(entry)
// NOTE: prevent 0 height element, react virtuoso will not stop render any more
if (!entry) return <ReactVirtuosoItemPlaceholder />
@ -52,15 +37,17 @@ export function EntryItemWrapper({
<div
className={cn(
"rounded-md bg-background transition-colors",
!views[view || 0].wideMode && activeEntry === entry.entries.id && "bg-theme-item-active",
!views[view || 0].wideMode &&
activeEntry === entry.entries.id &&
"bg-theme-item-active",
asRead ? "text-zinc-500/90" : "text-zinc-900 dark:text-white/90",
)}
// ref={ref}
onClick={(e) => {
e.stopPropagation()
feedActions.setActiveEntry(entry.entries.id)
if (!entry.read) {
read.mutate()
if (!asRead) {
markReadMutation.mutate()
}
}}
onDoubleClick={() =>

View File

@ -5,7 +5,11 @@ import type { FeedViewType } from "@renderer/lib/enum"
import { cn } from "@renderer/lib/utils"
import type { FeedListModel, SubscriptionResponse } from "@renderer/models"
import { Queries } from "@renderer/queries"
import { feedActions, useFeedActiveList, useUnreadStore } from "@renderer/store"
import {
feedActions,
useFeedActiveList,
useUnreadStore,
} from "@renderer/store"
import { useMemo, useState } from "react"
import { Link } from "react-router-dom"
import { parse } from "tldts"
@ -13,11 +17,11 @@ import { parse } from "tldts"
import { FeedCategory } from "./category"
const useData = (view?: FeedViewType) => {
const subscriptions = useBizQuery(Queries.subscription.byView(view))
const query = useBizQuery(Queries.subscription.byView(view))
// TODO Refactor this into category store
return useMemo(() => {
if (!subscriptions.data) return null
if (!query.data) return null
const categories = {
list: {},
} as {
@ -29,10 +33,10 @@ const useData = (view?: FeedViewType) => {
>
}
const domains: Record<string, number> = {}
const data = structuredClone(subscriptions.data)
const subscriptions = structuredClone(query.data)
if (subscriptions) {
for (const subscription of data.subscriptions) {
if (query) {
for (const subscription of subscriptions) {
if (!subscription.category && subscription.feeds.siteUrl) {
const { domain } = parse(subscription.feeds.siteUrl)
if (domain) {
@ -44,8 +48,8 @@ const useData = (view?: FeedViewType) => {
}
}
}
if (subscriptions) {
for (const subscription of data.subscriptions) {
if (query) {
for (const subscription of subscriptions) {
if (!subscription.category) {
if (subscription.feeds.siteUrl) {
const { domain } = parse(subscription.feeds.siteUrl)
@ -64,8 +68,6 @@ const useData = (view?: FeedViewType) => {
}
}
const unread = data.unreads[subscription.feedId] || 0
subscription.unread = unread
categories.list[subscription.category].list.push(subscription)
}
}
@ -77,7 +79,7 @@ const useData = (view?: FeedViewType) => {
return {
list,
} as FeedListModel
}, [subscriptions])
}, [query])
}
export function FeedList({
className,
@ -92,6 +94,8 @@ export function FeedList({
const data = useData(view)
const activeList = useFeedActiveList()
useBizQuery(Queries.subscription.unreadAll())
const totalUnread = useUnreadStore((state) => {
let unread = 0
data?.list.forEach((a) => {
@ -154,8 +158,7 @@ export function FeedList({
<div
className={cn(
"flex h-8 w-full items-center rounded-md px-2.5 transition-colors",
activeList?.id === "collections" &&
"bg-native-active",
activeList?.id === "collections" && "bg-native-active",
)}
onClick={(e) => {
e.stopPropagation()
@ -172,7 +175,7 @@ export function FeedList({
<i className="i-mingcute-star-fill mr-2 text-orange-500" />
Starred
</div>
{data?.list?.length ? (
{data?.list?.length ?
sortedByUnread?.map((category) => (
<FeedCategory
key={category.name}
@ -180,20 +183,18 @@ export function FeedList({
view={view}
expansion={expansion}
/>
))
) : (
!hideTitle && (
<div className="flex h-full flex-1 items-center text-zinc-500">
<Link
to="/discover"
className="-mt-36 flex h-full flex-1 flex-col items-center justify-center gap-2"
>
<i className="i-mingcute-add-line text-3xl " />
Add some feeds
</Link>
</div>
)
)}
)) :
!hideTitle && (
<div className="flex h-full flex-1 items-center text-zinc-500">
<Link
to="/discover"
className="-mt-36 flex h-full flex-1 flex-col items-center justify-center gap-2"
>
<i className="i-mingcute-add-line text-3xl " />
Add some feeds
</Link>
</div>
)}
</div>
)
}

View File

@ -31,7 +31,7 @@ declare const routes: hono_hono_base.HonoBase<hono.Env, {
$get: {
input: {
query: {
view: string;
view?: string | undefined;
};
};
output: {

View File

@ -7,6 +7,87 @@ import type { FetchError } from "ofetch"
import { ofetch } from "ofetch"
import { toast } from "sonner"
export const useCollect = (entry: EntryModel | undefined) =>
useMutation({
mutationFn: async () =>
entry &&
apiClient.collections.$post({
json: {
entryId: entry?.entries.id,
},
}),
onMutate() {
if (!entry) return
entryActions.optimisticUpdate(entry.entries.id, {
collections: {
createdAt: new Date().toISOString(),
},
})
},
onSuccess: () => {
toast("Collected.", {
duration: 1000,
})
},
})
export const useUnCollect = (entry: EntryModel | undefined) =>
useMutation({
mutationFn: async () =>
entry &&
apiClient.collections.$delete({
json: {
entryId: entry?.entries.id,
},
}),
onMutate() {
if (!entry) return
entryActions.optimisticUpdate(entry.entries.id, {
collections: undefined,
})
},
onSuccess: () => {
toast("Uncollected.", {
duration: 1000,
})
},
})
export const useRead = (entry: EntryModel | undefined) =>
useMutation({
mutationFn: async () =>
entry &&
apiClient.reads.$post({
json: {
entryIds: [entry.entries.id],
},
}),
onMutate: () => {
if (!entry) return
entryActions.markRead(entry.feeds.id, entry.entries.id, true)
},
})
export const useUnread = (entry: EntryModel | undefined) =>
useMutation({
mutationFn: async () =>
entry &&
apiClient.reads.$delete({
json: {
entryId: entry.entries.id,
},
}),
onMutate: () => {
if (!entry) return
entryActions.markRead(entry.feeds.id, entry.entries.id, false)
},
})
export const useEntryActions = ({
view,
entry,
@ -27,76 +108,10 @@ export const useEntryActions = ({
},
})
const collect = useMutation({
mutationFn: async () =>
entry && apiClient.collections.$post({
json: {
entryId: entry?.entries.id,
},
}),
onMutate() {
if (!entry) return
entryActions.optimisticUpdate(entry.entries.id, {
collections: {
createdAt: new Date().toISOString(),
},
})
},
onSuccess: () => {
toast("Collected.", {
duration: 1000,
})
},
})
const uncollect = useMutation({
mutationFn: async () =>
entry && apiClient.collections.$delete({
json: {
entryId: entry?.entries.id,
},
}),
onMutate() {
if (!entry) return
entryActions.optimisticUpdate(entry.entries.id, {
collections: undefined,
})
},
onSuccess: () => {
toast("Uncollected.", {
duration: 1000,
})
},
})
const read = useMutation({
mutationFn: async () =>
entry &&
apiClient.reads.$post({
json: {
entryIds: [entry.entries.id],
},
}),
onMutate: () => {
if (!entry) return
entryActions.markRead(entry.feeds.id, entry.entries.id, true)
},
})
const unread = useMutation({
mutationFn: async () => entry && apiClient.reads.$delete({
json: {
entryId: entry.entries.id,
},
}),
onMutate: () => {
if (!entry) return
entryActions.markRead(entry.feeds.id, entry.entries.id, false)
},
})
const collect = useCollect(entry)
const uncollect = useUnCollect(entry)
const read = useRead(entry)
const unread = useUnread(entry)
if (!entry?.entries.url || view === undefined) return { items: [] }

View File

@ -7,16 +7,7 @@ export const subscription = {
byView: (view?: FeedViewType) =>
defineQuery(
["subscriptions", view],
async () => {
const [subscriptions, unreads] = await Promise.all([
subscriptionActions.fetchByView(view),
unreadActions.fetchUnreadByView(view),
])
return {
subscriptions,
unreads,
}
},
async () => subscriptionActions.fetchByView(view),
{
rootKey: ["subscriptions"],
},
@ -29,4 +20,7 @@ export const subscription = {
return res.data
}),
unreadAll: () =>
defineQuery(["unread-all"], async () => unreadActions.fetchUnreadAll()),
}

View File

@ -127,6 +127,7 @@ export const useEntryStore = createZustandStore<EntryState & EntryActions>(
},
markRead: (feedId: string, entryId: string, read: boolean) => {
console.log(feedId, "feedId")
unreadActions.incrementByFeedId(feedId, read ? -1 : 1)
entryActions.optimisticUpdate(entryId, {
read,

View File

@ -10,6 +10,7 @@ interface UnreadState {
interface UnreadActions {
updateByFeedId: (feedId: string, unread: number) => void
fetchUnreadByView: (view?: FeedViewType) => Promise<Record<string, number>>
fetchUnreadAll: () => Promise<Record<string, number>>
incrementByFeedId: (feedId: string, inc: number) => void
internal_reset: () => void
@ -19,7 +20,7 @@ export const useUnreadStore = createZustandStore<UnreadState & UnreadActions>(
{
version: 0,
},
)((set, get) => ({
)((set) => ({
data: {},
internal_reset() {
@ -32,14 +33,28 @@ export const useUnreadStore = createZustandStore<UnreadState & UnreadActions>(
})
const { data } = unread
set((state) =>
produce(state, (state) => {
get().internal_reset()
for (const feedId in data) {
state.data[feedId] = data[feedId]
return state
for (const [key, value] of Object.entries(data)) {
state.data[key] = value
}
}),
)
return data
},
async fetchUnreadAll() {
const unread = await apiClient.reads.$get({
query: {},
})
const { data } = unread
this.internal_reset()
set((state) =>
produce(state, (state) => {
for (const [key, value] of Object.entries(data)) {
state.data[key] = value
}
return state
}),
)
return data