forked from Archives/Athou_commafeed
Merge pull request #1780 from Eshwar1212-maker/clean-red-dot
feat: red dot indicator for new unread articles
This commit is contained in:
@@ -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
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user