diff --git a/commafeed-client/src/app/tree/slice.ts b/commafeed-client/src/app/tree/slice.ts
index 323c0195..c9a0d5e8 100644
--- a/commafeed-client/src/app/tree/slice.ts
+++ b/commafeed-client/src/app/tree/slice.ts
@@ -44,19 +44,25 @@ export const treeSlice = createSlice({
extraReducers: builder => {
builder.addCase(reloadTree.fulfilled, (state, action) => {
visitCategoryTree(action.payload, category => {
- category.feeds = category.feeds.map(feed => {
- const storageKey = `feed-${feed.id}-unread`
- const prevUnread = parseInt(localStorage.getItem(storageKey) || "0", 10)
- const hasNewEntries = feed.unread > prevUnread
+ category.feeds = category.feeds.map(feed => {
+ const storageKey = `feed-${feed.id}-unread`
+ const existing = localStorage.getItem(storageKey)
+ const prevUnread = Number.parseInt(existing || "0", 10)
+ const isNewFeed = existing === null
- localStorage.setItem(storageKey, feed.unread.toString())
+ const hasNewEntries = isNewFeed ? true : feed.unread > prevUnread
- return {
- ...feed,
- hasNewEntries
- }
+ if (!isNewFeed) {
+ localStorage.setItem(storageKey, feed.unread.toString())
+ }
+
+ return {
+ ...feed,
+ hasNewEntries,
+ }
+ })
})
- })
+
state.rootCategory = action.payload
})
builder.addCase(collapseTreeCategory.pending, (state, action) => {
diff --git a/commafeed-client/src/components/sidebar/Tree.tsx b/commafeed-client/src/components/sidebar/Tree.tsx
index a965c2ed..0d805737 100644
--- a/commafeed-client/src/components/sidebar/Tree.tsx
+++ b/commafeed-client/src/components/sidebar/Tree.tsx
@@ -1,216 +1,195 @@
-import { Trans } from "@lingui/react/macro";
-import { Box, Stack } from "@mantine/core";
-import { Constants } from "app/constants";
+import { Trans } from "@lingui/react/macro"
+import { Box, Stack } from "@mantine/core"
+import { Constants } from "app/constants"
import {
- redirectToCategory,
- redirectToCategoryDetails,
- redirectToFeed,
- redirectToFeedDetails,
- redirectToTag,
- redirectToTagDetails,
-} from "app/redirect/thunks";
-import { useAppDispatch, useAppSelector } from "app/store";
-import { collapseTreeCategory } from "app/tree/thunks";
-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";
-import React, { useEffect, useState } from "react";
-import {
- TbChevronDown,
- TbChevronRight,
- TbInbox,
- TbStar,
- TbTag,
-} from "react-icons/tb";
-import { TreeNode } from "./TreeNode";
-import { TreeSearch } from "./TreeSearch";
+ redirectToCategory,
+ redirectToCategoryDetails,
+ redirectToFeed,
+ redirectToFeedDetails,
+ redirectToTag,
+ redirectToTagDetails,
+} from "app/redirect/thunks"
+import { useAppDispatch, useAppSelector } from "app/store"
+import { collapseTreeCategory } from "app/tree/thunks"
+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"
+import React from "react"
+import { TbChevronDown, TbChevronRight, TbInbox, TbStar, TbTag } from "react-icons/tb"
+import { TreeNode } from "./TreeNode"
+import { TreeSearch } from "./TreeSearch"
-const allIcon = ;
-const starredIcon = ;
-const tagIcon = ;
-const expandedIcon = ;
-const collapsedIcon = ;
+const allIcon =
+const starredIcon =
+const tagIcon =
+const expandedIcon =
+const collapsedIcon =
-const errorThreshold = 9;
+const errorThreshold = 9
export function Tree() {
- const root = useAppSelector((state) => state.tree.rootCategory);
- const source = useAppSelector((state) => state.entries.source);
- const tags = useAppSelector((state) => state.user.tags);
- const showRead = useAppSelector((state) => state.user.settings?.showRead);
- const dispatch = useAppDispatch();
+ const root = useAppSelector(state => state.tree.rootCategory)
+ const source = useAppSelector(state => state.entries.source)
+ const tags = useAppSelector(state => state.user.tags)
+ const showRead = useAppSelector(state => state.user.settings?.showRead)
+ const dispatch = useAppDispatch()
- const isFeedDisplayed = (feed: Subscription) => {
- const isCurrentFeed =
- source.type === "feed" && source.id === String(feed.id);
- return isCurrentFeed || feed.unread > 0 || showRead;
- };
-
- const isCategoryDisplayed = (category: Category): boolean => {
- const isCurrentCategory =
- source.type === "category" && source.id === category.id;
- return (
- isCurrentCategory ||
- showRead ||
- category.children.some((c) => isCategoryDisplayed(c)) ||
- category.feeds.some((f) => isFeedDisplayed(f))
- );
- };
-
- const feedClicked = (e: React.MouseEvent, id: string) => {
- if (e.detail === 2) {
- dispatch(redirectToFeedDetails(id));
- } else {
- dispatch(redirectToFeed(id));
+ const isFeedDisplayed = (feed: Subscription) => {
+ const isCurrentFeed = source.type === "feed" && source.id === String(feed.id)
+ return isCurrentFeed || feed.unread > 0 || showRead
}
- };
- const categoryClicked = (e: React.MouseEvent, id: string) => {
- if (e.detail === 2) {
- dispatch(redirectToCategoryDetails(id));
- } else {
- dispatch(redirectToCategory(id));
+
+ const isCategoryDisplayed = (category: Category): boolean => {
+ const isCurrentCategory = source.type === "category" && source.id === category.id
+ return (
+ isCurrentCategory ||
+ showRead ||
+ category.children.some(c => isCategoryDisplayed(c)) ||
+ category.feeds.some(f => isFeedDisplayed(f))
+ )
}
- };
- const categoryIconClicked = (e: React.MouseEvent, category: Category) => {
- e.stopPropagation();
- dispatch(
- collapseTreeCategory({
- id: +category.id,
- collapse: category.expanded,
- })
- );
- };
- const tagClicked = (e: React.MouseEvent, id: string) => {
- if (e.detail === 2) {
- dispatch(redirectToTagDetails(id));
- } else {
- dispatch(redirectToTag(id));
+ const feedClicked = (e: React.MouseEvent, id: string) => {
+ if (e.detail === 2) {
+ dispatch(redirectToFeedDetails(id))
+ } else {
+ dispatch(redirectToFeed(id))
+ }
}
- };
+ const categoryClicked = (e: React.MouseEvent, id: string) => {
+ if (e.detail === 2) {
+ dispatch(redirectToCategoryDetails(id))
+ } else {
+ dispatch(redirectToCategory(id))
+ }
+ }
+ const categoryIconClicked = (e: React.MouseEvent, category: Category) => {
+ e.stopPropagation()
- console.log(root?.feeds.map(f => f.hasNewEntries));
+ dispatch(
+ collapseTreeCategory({
+ id: +category.id,
+ collapse: category.expanded,
+ })
+ )
+ }
+ const tagClicked = (e: React.MouseEvent, id: string) => {
+ if (e.detail === 2) {
+ dispatch(redirectToTagDetails(id))
+ } else {
+ dispatch(redirectToTag(id))
+ }
+ }
- const allCategoryNode = () => (
+ const allCategoryNode = () => (
+ All}
+ icon={allIcon}
+ unread={categoryUnreadCount(root)}
+ selected={source.type === "category" && source.id === Constants.categories.all.id}
+ expanded={false}
+ level={0}
+ hasError={false}
+ onClick={categoryClicked}
+ />
+ )
+ const starredCategoryNode = () => (
+ Starred}
+ icon={starredIcon}
+ unread={0}
+ selected={source.type === "category" && source.id === Constants.categories.starred.id}
+ expanded={false}
+ level={0}
+ hasError={false}
+ onClick={categoryClicked}
+ />
+ )
- All}
- icon={allIcon}
- unread={categoryUnreadCount(root)}
- selected={
- source.type === "category" && source.id === Constants.categories.all.id
- }
- expanded={false}
- level={0}
- hasError={false}
- onClick={categoryClicked}
- />
- );
- const starredCategoryNode = () => (
- Starred}
- icon={starredIcon}
- unread={0}
- selected={
- source.type === "category" &&
- source.id === Constants.categories.starred.id
- }
- expanded={false}
- level={0}
- hasError={false}
- onClick={categoryClicked}
- />
- );
+ const categoryNode = (category: Category, level = 0) => {
+ if (!isCategoryDisplayed(category)) return null
- const categoryNode = (category: Category, level = 0) => {
- if (!isCategoryDisplayed(category)) return null;
+ const hasError = !category.expanded && flattenCategoryTree(category).some(c => c.feeds.some(f => f.errorCount > errorThreshold))
+ return (
+ categoryIconClicked(e, category)}
+ key={category.id}
+ />
+ )
+ }
- const hasError =
- !category.expanded &&
- flattenCategoryTree(category).some((c) =>
- c.feeds.some((f) => f.errorCount > errorThreshold)
- );
+ const feedNode = (feed: TreeSubscription, level = 0) => {
+ if (!isFeedDisplayed(feed)) return null
+
+ return (
+ errorThreshold}
+ onClick={feedClicked}
+ key={feed.id}
+ newMessages={feed.hasNewEntries}
+ />
+ )
+ }
+
+ const tagNode = (tag: string) => (
+
+ )
+
+ const recursiveCategoryNode = (category: Category, level = 0) => (
+
+ {categoryNode(category, level)}
+ {category.expanded && category.children.map(c => recursiveCategoryNode(c, level + 1))}
+ {category.expanded && category.feeds.map(f => feedNode(f, level + 1))}
+
+ )
+
+ if (!root) return
+ const feeds = flattenCategoryTree(root).flatMap(c => c.feeds)
return (
- categoryIconClicked(e, category)}
- key={category.id}
- />
- );
- };
-
- const feedNode = (feed: TreeSubscription, level = 0) => {
- if (!isFeedDisplayed(feed)) return null;
-
- return (
- errorThreshold}
- onClick={feedClicked}
- key={feed.id}
- newMessages={feed.hasNewEntries}
- />
- );
- };
-
- const tagNode = (tag: string) => (
-
- );
-
- const recursiveCategoryNode = (category: Category, level = 0) => (
-
- {categoryNode(category, level)}
- {category.expanded &&
- category.children.map((c) => recursiveCategoryNode(c, level + 1))}
- {category.expanded && category.feeds.map((f) => feedNode(f, level + 1))}
-
- );
-
- if (!root) return ;
- const feeds = flattenCategoryTree(root).flatMap((c) => c.feeds);
- return (
-
-
-
-
-
- {allCategoryNode()}
- {starredCategoryNode()}
- {root.children.map((c) => recursiveCategoryNode(c))}
- {root.feeds.map((f) => feedNode(f))}
- {tags?.map((tag) => tagNode(tag))}
-
-
- );
+
+
+
+
+
+ {allCategoryNode()}
+ {starredCategoryNode()}
+ {root.children.map(c => recursiveCategoryNode(c))}
+ {root.feeds.map(f => feedNode(f))}
+ {tags?.map(tag => tagNode(tag))}
+
+
+ )
}
diff --git a/commafeed-client/src/components/sidebar/TreeNode.tsx b/commafeed-client/src/components/sidebar/TreeNode.tsx
index 443daf77..56baa1c1 100644
--- a/commafeed-client/src/components/sidebar/TreeNode.tsx
+++ b/commafeed-client/src/components/sidebar/TreeNode.tsx
@@ -1,107 +1,93 @@
-import { Box, Center, Indicator } from "@mantine/core";
-import type { EntrySourceType } from "app/entries/slice";
-import { FeedFavicon } from "components/content/FeedFavicon";
-import type React from "react";
-import { tss } from "tss";
-import { UnreadCount } from "./UnreadCount";
+import { Box, Center } from "@mantine/core"
+import type { EntrySourceType } from "app/entries/slice"
+import { FeedFavicon } from "components/content/FeedFavicon"
+import type React from "react"
+import { tss } from "tss"
+import { UnreadCount } from "./UnreadCount"
interface TreeNodeProps {
- id: string;
- type: EntrySourceType;
- name: React.ReactNode;
- icon: React.ReactNode;
- unread: number;
- selected: boolean;
- expanded?: boolean;
- level: number;
- hasError: boolean;
- newMessages?: boolean
- onClick: (e: React.MouseEvent, id: string) => void;
- onIconClick?: (e: React.MouseEvent, id: string) => void;
+ id: string
+ type: EntrySourceType
+ name: React.ReactNode
+ icon: React.ReactNode
+ unread: number
+ selected: boolean
+ expanded?: boolean
+ level: number
+ hasError: boolean
+ newMessages?: boolean
+ onClick: (e: React.MouseEvent, id: string) => void
+ onIconClick?: (e: React.MouseEvent, id: string) => void
}
const useStyles = tss
- .withParams<{
- selected: boolean;
- hasError: boolean;
- hasUnread: boolean;
- }>()
- .create(({ theme, colorScheme, selected, hasError, hasUnread }) => {
- let backgroundColor = "inherit";
- if (selected)
- backgroundColor =
- colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1];
+ .withParams<{
+ selected: boolean
+ hasError: boolean
+ hasUnread: boolean
+ }>()
+ .create(({ theme, colorScheme, selected, hasError, hasUnread }) => {
+ let backgroundColor = "inherit"
+ if (selected) backgroundColor = colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]
- let color: string;
- if (hasError) {
- color = theme.colors.red[6];
- } else if (colorScheme === "dark") {
- color = hasUnread ? theme.colors.dark[0] : theme.colors.dark[3];
- } else {
- color = hasUnread ? theme.black : theme.colors.gray[6];
- }
+ let color: string
+ if (hasError) {
+ color = theme.colors.red[6]
+ } else if (colorScheme === "dark") {
+ color = hasUnread ? theme.colors.dark[0] : theme.colors.dark[3]
+ } else {
+ color = hasUnread ? theme.black : theme.colors.gray[6]
+ }
- return {
- node: {
- display: "flex",
- alignItems: "center",
- cursor: "pointer",
- color,
- backgroundColor,
- "&:hover": {
- backgroundColor:
- colorScheme === "dark"
- ? theme.colors.dark[6]
- : theme.colors.gray[0],
- },
- },
- nodeText: {
- flexGrow: 1,
- whiteSpace: "nowrap",
- overflow: "hidden",
- textOverflow: "ellipsis",
- },
- };
- });
+ return {
+ node: {
+ display: "flex",
+ alignItems: "center",
+ cursor: "pointer",
+ color,
+ backgroundColor,
+ "&:hover": {
+ backgroundColor: colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[0],
+ },
+ },
+ nodeText: {
+ flexGrow: 1,
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ },
+ }
+ })
export function TreeNode(props: TreeNodeProps) {
- const { classes } = useStyles({
- selected: props.selected,
- hasError: props.hasError,
- hasUnread: props.unread > 0,
- });
- return (
- props.onClick(e, props.id)}
- data-id={props.id}
- data-type={props.type}
- data-unread-count={props.unread}
- >
- props.onIconClick?.(e, props.id)}
- className="cf-treenode-icon"
- >
-
- {typeof props.icon === "string" ? (
-
- ) : (
- props.icon
- )}
-
-
- {props.name}
- {!props.expanded && (
-
-
+ const { classes } = useStyles({
+ selected: props.selected,
+ hasError: props.hasError,
+ hasUnread: props.unread > 0,
+ })
+
+ return (
+ {
+ props.onClick(e, props.id)
+ props.type === "feed" && localStorage.setItem(`feed-${props.id}-unread`, props.unread.toString())
+ }}
+ data-id={props.id}
+ data-type={props.type}
+ data-unread-count={props.unread}
+ >
+ props.onIconClick?.(e, props.id)} className="cf-treenode-icon">
+ {typeof props.icon === "string" ? : props.icon}
+
+ {props.name}
+ {!props.expanded && (
+
+
+
+ )}
- )}
-
- );
+ )
}
diff --git a/commafeed-client/src/components/sidebar/UnreadCount.tsx b/commafeed-client/src/components/sidebar/UnreadCount.tsx
index 6607b5fd..a1e6e154 100644
--- a/commafeed-client/src/components/sidebar/UnreadCount.tsx
+++ b/commafeed-client/src/components/sidebar/UnreadCount.tsx
@@ -9,16 +9,13 @@ const useStyles = tss.create(() => ({
},
}))
-export function UnreadCount(props: { unreadCount: number, newMessages: boolean | undefined }) {
+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
- console.log(props.newMessages);
-
-
return (