Merge pull request #1780 from Eshwar1212-maker/clean-red-dot

feat: red dot indicator for new unread articles
This commit is contained in:
Jérémie Panzer
2025-05-23 15:54:22 +02:00
committed by GitHub
7 changed files with 35 additions and 14 deletions

View File

@@ -3,6 +3,7 @@ import { client } from "app/client"
import { Constants } from "app/constants"
import { type EntrySource, type EntrySourceType, entriesSlice, setMarkAllAsReadConfirmationDialogOpen, setSearch } from "app/entries/slice"
import type { RootState } from "app/store"
import { setHasNewEntries } from "app/tree/slice"
import { reloadTree } from "app/tree/thunks"
import type { Entry, MarkRequest, TagRequest } from "app/types"
import { reloadTags } from "app/user/thunks"
@@ -26,6 +27,9 @@ export const loadEntries = createAppAsyncThunk(
const state = thunkApi.getState()
const endpoint = getEndpoint(arg.source.type)
const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0))
if (arg.source.type === "feed") {
thunkApi.dispatch(setHasNewEntries({ feedId: +arg.source.id, value: false }))
}
return result.data
}
)

View File

@@ -40,6 +40,15 @@ export const treeSlice = createSlice({
}
})
},
setHasNewEntries: (state, action: PayloadAction<{ feedId: number; value: boolean }>) => {
if (!state.rootCategory) return
visitCategoryTree(state.rootCategory, category => {
category.feeds = category.feeds.map(feed =>
feed.id === action.payload.feedId ? { ...feed, hasNewEntries: action.payload.value } : feed
)
})
},
},
extraReducers: builder => {
builder.addCase(reloadTree.fulfilled, (state, action) => {
@@ -65,4 +74,4 @@ export const treeSlice = createSlice({
},
})
export const { setMobileMenuOpen, toggleSidebar, incrementUnreadCount } = treeSlice.actions
export const { setMobileMenuOpen, toggleSidebar, incrementUnreadCount, setHasNewEntries } = treeSlice.actions

View File

@@ -1,7 +1,7 @@
import { createAppAsyncThunk } from "app/async-thunk"
import { client } from "app/client"
import { redirectToCategory, redirectToFeed } from "app/redirect/thunks"
import { incrementUnreadCount } from "app/tree/slice"
import { incrementUnreadCount, setHasNewEntries } from "app/tree/slice"
import type { CollapseRequest, Subscription } from "app/types"
import { flattenCategoryTree, visitCategoryTree } from "app/utils"
@@ -11,7 +11,6 @@ export const collapseTreeCategory = createAppAsyncThunk(
"tree/category/collapse",
async (req: CollapseRequest) => await client.category.collapse(req).then(r => r.data)
)
export const selectNextUnreadTreeItem = createAppAsyncThunk(
"tree/selectNextUnreadItem",
(
@@ -75,6 +74,7 @@ export const newFeedEntriesDiscovered = createAppAsyncThunk(
amount,
})
)
thunkApi.dispatch(setHasNewEntries({ feedId, value: true }))
}
}
)

View File

@@ -30,13 +30,17 @@ export interface Subscription {
filter?: string
}
export interface TreeSubscription extends Subscription {
hasNewEntries?: boolean
}
export interface Category {
id: string
parentId?: string
parentName?: string
name: string
children: Category[]
feeds: Subscription[]
feeds: TreeSubscription[]
expanded: boolean
position: number
}

View File

@@ -11,7 +11,7 @@ import {
} from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { collapseTreeCategory } from "app/tree/thunks"
import type { Category, Subscription } from "app/types"
import type { Category, Subscription, TreeSubscription } from "app/types"
import { categoryUnreadCount, flattenCategoryTree } from "app/utils"
import { Loader } from "components/Loader"
import { OnDesktop } from "components/responsive/OnDesktop"
@@ -133,7 +133,7 @@ export function Tree() {
)
}
const feedNode = (feed: Subscription, level = 0) => {
const feedNode = (feed: TreeSubscription, level = 0) => {
if (!isFeedDisplayed(feed)) return null
return (
@@ -148,6 +148,7 @@ export function Tree() {
hasError={feed.errorCount > errorThreshold}
onClick={feedClicked}
key={feed.id}
newMessages={feed.hasNewEntries}
/>
)
}

View File

@@ -15,6 +15,7 @@ interface TreeNodeProps {
expanded?: boolean
level: number
hasError: boolean
newMessages?: boolean
onClick: (e: React.MouseEvent, id: string) => void
onIconClick?: (e: React.MouseEvent, id: string) => void
}
@@ -64,12 +65,12 @@ export function TreeNode(props: TreeNodeProps) {
hasError: props.hasError,
hasUnread: props.unread > 0,
})
return (
<Box
py={1}
pl={props.level * 20}
className={`${classes.node} cf-treenode cf-treenode-${props.type}`}
onClick={(e: React.MouseEvent) => props.onClick(e, props.id)}
data-id={props.id}
data-type={props.type}
data-unread-count={props.unread}
@@ -80,7 +81,7 @@ export function TreeNode(props: TreeNodeProps) {
<Box className={classes.nodeText}>{props.name}</Box>
{!props.expanded && (
<Box className="cf-treenode-unread-count">
<UnreadCount unreadCount={props.unread} />
<UnreadCount unreadCount={props.unread} newMessages={props.id !== "all" ? props.newMessages : false} />
</Box>
)}
</Box>

View File

@@ -1,26 +1,28 @@
import { Badge, Tooltip } from "@mantine/core"
import { Badge, Indicator, Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import { tss } from "tss"
const useStyles = tss.create(() => ({
badge: {
width: "3.2rem",
// for some reason, mantine Badge has "cursor: 'default'"
cursor: "pointer",
},
}))
export function UnreadCount(props: { unreadCount: number }) {
export function UnreadCount(props: { unreadCount: number; newMessages: boolean | undefined }) {
const { classes } = useStyles()
if (props.unreadCount <= 0) return null
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
return (
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count} openDelay={Constants.tooltip.delay}>
<Badge className={`${classes.badge} cf-badge`} variant="light" fullWidth>
{count}
</Badge>
<Indicator disabled={!props.newMessages} size={4} offset={10} position="top-start" color="orange" withBorder={false} zIndex={5}>
<Badge className={`${classes.badge} cf-badge`} variant="light" fullWidth>
{count}
</Badge>
</Indicator>
</Tooltip>
)
}