Skip to content

Commit

Permalink
feat: render mark as read (#111)
Browse files Browse the repository at this point in the history
* feat: render mark as read

Signed-off-by: Innei <[email protected]>

* fix: mark read when active

Signed-off-by: Innei <[email protected]>

* fix: default

Signed-off-by: Innei <[email protected]>

---------

Signed-off-by: Innei <[email protected]>
  • Loading branch information
Innei authored Jul 5, 2024
1 parent 296c285 commit 246efa2
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 205 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@
],
"cSpell.words": [
"rsshub"
]
],
"editor.foldingImportsByDefault": true
}
5 changes: 4 additions & 1 deletion src/renderer/src/atoms/settings/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { createSettingAtom } from "./helper"
const createDefaultSettings = () => ({
dataPersist: true,

// view
unreadOnly: false,
// mark unread
scrollMarkUnread: true,
hoverMarkUnread: true,
hoverMarkUnread: false,
renderMarkUnread: true,
})
export const {
useSettingKey: useGeneralSettingKey,
Expand Down
14 changes: 9 additions & 5 deletions src/renderer/src/modules/entry-column/helper.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { apiClient } from "@renderer/lib/api-fetch"
import { entryActions } from "@renderer/store/entry"
import { entryActions, getEntry } from "@renderer/store/entry"
import { create, keyResolver, windowScheduler } from "@yornaath/batshit"

type EntryId = string
type FeedId = string
const unread = create({
fetcher: async (ids: [FeedId, EntryId][]) => {
await apiClient.reads.$post({ json: { entryIds: ids.map((i) => i[1]) } })
for (const [feedId, entryId] of ids) {
entryActions.markRead(feedId, entryId, true)
}

return []
},
resolver: keyResolver("id"),
scheduler: windowScheduler(1000),
})

export const batchMarkUnread = unread.fetch
export const batchMarkUnread = (...args: Parameters<typeof unread.fetch>) => {
const [, entryId] = args[0]
const currentIsRead = getEntry(entryId)?.read
if (currentIsRead) return
entryActions.markRead(args[0][0], args[0][1], true)
return unread.fetch.apply(null, args)
}
158 changes: 158 additions & 0 deletions src/renderer/src/modules/entry-column/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { useGeneralSettingKey } from "@renderer/atoms/settings/general"
import {
useRouteParamsSelector,
useRouteParms,
} from "@renderer/hooks/biz/useRouteParams"
import { levels, ROUTE_FEED_PENDING, views } from "@renderer/lib/constants"
import { shortcuts } from "@renderer/lib/shortcuts"
import { useEntries } from "@renderer/queries/entries"
import { entryActions, useEntryIdsByFeedIdOrView } from "@renderer/store/entry"
import { useFolderFeedsByFeedId } from "@renderer/store/subscription"
import { useCallback, useEffect, useMemo, useRef } from "react"
import { useHotkeys } from "react-hotkeys-hook"
import type { ListRange } from "react-virtuoso"
import { useDebounceCallback } from "usehooks-ts"

import { batchMarkUnread } from "./helper"

export const useEntryMarkReadHandler = (entriesIds: string[]) => {
const renderAsRead = useGeneralSettingKey("renderMarkUnread")
const scrollMarkUnread = useGeneralSettingKey("scrollMarkUnread")
const feedView = useRouteParamsSelector((params) => params.view)

const handleMarkReadInRange = useDebounceCallback(
async ({ startIndex }: ListRange) => {
const idSlice = entriesIds?.slice(0, startIndex)

if (!idSlice) return
batchMarkRead(idSlice)
},
1000,
{ leading: false },
)

const handleRenderAsRead = useCallback(
async ({ startIndex, endIndex }: ListRange) => {
const idSlice = entriesIds?.slice(startIndex, endIndex)

if (!idSlice) return
batchMarkRead(idSlice)
},
[entriesIds],
)

return useMemo(() => {
if (views[feedView].wideMode && renderAsRead) {
return handleRenderAsRead
}

if (scrollMarkUnread) {
return handleMarkReadInRange
}
return
}, [
feedView,
handleMarkReadInRange,
handleRenderAsRead,
renderAsRead,
scrollMarkUnread,
])
}
export const useEntriesByView = () => {
const routeParams = useRouteParms()
const unreadOnly = useGeneralSettingKey("unreadOnly")

const { level, feedId, view } = routeParams

const folderIds = useFolderFeedsByFeedId(feedId)

const query = useEntries({
level,
id: level === levels.folder ? folderIds?.join(",") : feedId,
view,
...(unreadOnly === true && { read: false }),
})
const entries = useEntryIdsByFeedIdOrView(
feedId === ROUTE_FEED_PENDING ? view : feedId!,
{
unread: unreadOnly,
view,
},
)

useHotkeys(
shortcuts.entries.refetch.key,
() => {
query.refetch()
},
{ scopes: ["home"] },
)

// in unread only entries only can grow the data, but not shrink
// so we memo this previous data to avoid the flicker
const prevEntries = useRef(entries)

useEffect(() => {
prevEntries.current = []
}, [routeParams.feedId, routeParams.view])
const localEntries = useMemo(() => {
if (!unreadOnly) {
prevEntries.current = []
return entries
}
if (!prevEntries.current) {
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 remoteEntryIds = useMemo(
() =>
query.data ?
query.data.pages.reduce((acc, page) => {
if (!page.data) return acc
acc.push(...page.data.map((entry) => entry.entries.id))
return acc
}, [] as string[]) :
null,
[query.data],
)

return {
...query,

// If remote data is not available, we use the local data, get the local data length
totalCount: query.data?.pages?.[0]?.total ?? localEntries.length,
entriesIds:
// NOTE: if we use the remote data, priority will be given to the remote data, local data maybe had sort issue
remoteEntryIds ?? localEntries,
}
}

function batchMarkRead(ids: string[]) {
const batchLikeIds = [] as [string, string][]
const entriesId2Map = entryActions.getFlattenMapEntries()
for (const id of ids) {
const entry = entriesId2Map[id]

if (!entry) continue
const isRead = entry.read
if (!isRead) {
batchLikeIds.push([entry.feeds.id, id])
}
}

if (batchLikeIds.length > 0) {
for (const [feedId, id] of batchLikeIds) {
batchMarkUnread([feedId, id])
}
}
}
Loading

0 comments on commit 246efa2

Please sign in to comment.