Compare commits

..

48 Commits

Author SHA1 Message Date
Athou
f361be0c72 release 5.10.0 2025-05-28 07:08:11 +02:00
renovate[bot]
1611dc5703 chore(deps): update docker/build-push-action digest to 2634353 2025-05-27 20:52:00 +00:00
renovate[bot]
04faad84a4 fix(deps): update mantine monorepo to ^8.0.2 2025-05-27 14:24:50 +00:00
renovate[bot]
19c42e5838 chore(deps): update dependency @types/react to ^19.1.6 2025-05-27 11:59:22 +00:00
Athou
4918b69d0a improve performance by enabling the react compiler (#1087) 2025-05-26 21:06:53 +02:00
Athou
c7cec464aa improve performance by avoiding some big re-renders (#1087) 2025-05-26 20:55:27 +02:00
renovate[bot]
91857c4d73 chore(deps): lock file maintenance 2025-05-26 01:42:10 +00:00
Jérémie Panzer
fc6f9f4258 Merge pull request #1799 from Athou/renovate/patch-react-router-monorepo
fix(deps): update dependency react-router-dom to ^7.6.1
2025-05-25 20:54:27 +02:00
renovate[bot]
34f9f9374a fix(deps): update dependency react-router-dom to ^7.6.1 2025-05-25 15:16:31 +00:00
renovate[bot]
0ae4c1621f fix(deps): update dependency io.dropwizard.metrics:metrics-json to v4.2.32 2025-05-25 01:30:25 +00:00
Athou
c393f5c045 improve aarch64 compatibility 2025-05-24 18:21:50 +02:00
Jérémie Panzer
1624290dc1 Merge pull request #1798 from Athou/renovate/rollup-plugin-visualizer-6.x
chore(deps): update dependency rollup-plugin-visualizer to v6
2025-05-24 16:31:45 +02:00
renovate[bot]
c6491990ac chore(deps): update dependency rollup-plugin-visualizer to v6 2025-05-24 13:46:34 +00:00
renovate[bot]
15dea17923 chore(deps): update dependency io.github.git-commit-id:git-commit-id-maven-plugin to v9.0.2 2025-05-23 21:45:42 +00:00
Athou
689d5ac7b2 clear indicator when entries are loaded 2025-05-23 22:37:28 +02:00
Athou
2142e20e7d cleanup 2025-05-23 16:03:16 +02:00
Jérémie Panzer
dc23126570 Merge pull request #1780 from Eshwar1212-maker/clean-red-dot
feat: red dot indicator for new unread articles
2025-05-23 15:54:22 +02:00
Jérémie Panzer
55856f9060 Merge pull request #1794 from Athou/renovate/vitejs-plugin-react-4.x
chore(deps): update dependency @vitejs/plugin-react to ^4.5.0
2025-05-23 09:20:28 +02:00
renovate[bot]
c756ce5fc8 chore(deps): update dependency @vitejs/plugin-react to ^4.5.0 2025-05-23 02:55:39 +00:00
Eshwar Tangirala
0546f25d55 Removed console.log 2025-05-22 20:13:16 -04:00
Eshwar Tangirala
7b33717333 Readjusted code to not use localstorage, and just used redux for indicator 2025-05-22 20:10:52 -04:00
Jérémie Panzer
6ea6d16e58 Merge pull request #1793 from Athou/renovate/org.apache.httpcomponents.client5-httpclient5-5.x
fix(deps): update dependency org.apache.httpcomponents.client5:httpclient5 to v5.5
2025-05-22 22:13:31 +02:00
renovate[bot]
a9b65c83aa fix(deps): update dependency org.apache.httpcomponents.client5:httpclient5 to v5.5 2025-05-22 17:14:03 +00:00
renovate[bot]
a497802b50 chore(deps): update dependency npm to v11.4.1 2025-05-22 05:05:19 +00:00
Jérémie Panzer
42b0428b9a Merge pull request #1792 from Athou/renovate/com.puppycrawl.tools-checkstyle-10.x
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10.24.0
2025-05-22 07:04:34 +02:00
Jérémie Panzer
931c553e1d Merge pull request #1791 from Athou/renovate/debian-12.x
chore(deps): update debian docker tag to v12.11
2025-05-22 07:03:52 +02:00
renovate[bot]
f3c0b92a3c chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10.24.0 2025-05-22 03:10:15 +00:00
renovate[bot]
970cabf241 chore(deps): update debian docker tag to v12.11 2025-05-22 03:10:12 +00:00
renovate[bot]
e321ecde5d chore(deps): update dependency @types/react to ^19.1.5 2025-05-21 17:09:30 +00:00
Jérémie Panzer
32ac326a77 Merge pull request #1790 from Athou/renovate/node-22.x
chore(deps): update node.js to v22.16.0
2025-05-21 19:08:34 +02:00
renovate[bot]
134dcd4466 chore(deps): update node.js to v22.16.0 2025-05-21 16:41:07 +00:00
renovate[bot]
26a44353d4 chore(deps): update dependency vitest to ^3.1.4 2025-05-19 18:09:09 +00:00
renovate[bot]
55acb3ef28 chore(deps): lock file maintenance 2025-05-19 03:45:55 +00:00
Athou
0e96307726 ignore scheme case 2025-05-18 17:40:06 +02:00
Eshwar Tangirala
0199a36238 Working on indicator feature for new unread feeds 2025-05-18 00:03:28 -04:00
Eshwar Tangirala
3f2f6e83fa Adding red dot indicator feature, got main components done 2025-05-15 20:19:05 -04:00
renovate[bot]
4fa780cac2 chore(deps): update docker/build-push-action digest to 1dc7386 2025-05-15 21:12:24 +00:00
Jérémie Panzer
edb0f655b0 Merge pull request #1786 from Athou/renovate/patch-quarkus.version
fix(deps): update quarkus.version to v3.22.3 (patch)
2025-05-15 23:11:52 +02:00
Jérémie Panzer
651ada7073 Merge pull request #1787 from Athou/renovate/npm-11.x
chore(deps): update dependency npm to v11.4.0
2025-05-15 23:11:36 +02:00
renovate[bot]
efb5d49d04 chore(deps): update dependency npm to v11.4.0 2025-05-15 20:08:41 +00:00
renovate[bot]
f78cc18b06 fix(deps): update quarkus.version to v3.22.3 2025-05-15 12:39:28 +00:00
renovate[bot]
8acffa11e5 fix(deps): update swagger.version to v2.2.32 2025-05-15 03:27:38 +00:00
renovate[bot]
f4246807ff chore(deps): update node.js to v22.15.1 2025-05-14 22:48:58 +00:00
renovate[bot]
abf6e7131b fix(deps): update dependency @reduxjs/toolkit to ^2.8.2 2025-05-14 19:06:20 +00:00
renovate[bot]
b2688520cc fix(deps): update mantine monorepo to ^8.0.1 2025-05-14 14:57:49 +00:00
Eshwar Tangirala
d6910aa1e8 Cleaned up UI for Indicator 2025-05-12 16:30:06 -04:00
Eshwar Tangirala
afc56c6053 feat: red dot indicator for new unread articles 2025-05-12 16:22:40 -04:00
Eshwar Tangirala
1bd504cbfb feat: red dot indicator for new unread articles 2025-05-12 16:22:40 -04:00
20 changed files with 548 additions and 317 deletions

View File

@@ -143,7 +143,7 @@ jobs:
## build but don't push for PRs and renovate
- name: Docker build - native
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
context: .
file: commafeed-server/src/main/docker/Dockerfile.native
@@ -151,7 +151,7 @@ jobs:
platforms: linux/amd64,linux/arm64/v8
- name: Docker build - jvm
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
context: .
file: commafeed-server/src/main/docker/Dockerfile.jvm
@@ -160,7 +160,7 @@ jobs:
## build and push tag
- name: Docker build and push tag - native
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
if: ${{ github.ref_type == 'tag' }}
with:
context: .
@@ -172,7 +172,7 @@ jobs:
athou/commafeed:${{ github.ref_name }}-${{ matrix.database }}
- name: Docker build and push tag - jvm
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
if: ${{ github.ref_type == 'tag' }}
with:
context: .
@@ -185,7 +185,7 @@ jobs:
## build and push master
- name: Docker build and push master - native
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
if: ${{ github.ref_name == 'master' }}
with:
context: .
@@ -195,7 +195,7 @@ jobs:
tags: athou/commafeed:master-${{ matrix.database }}
- name: Docker build and push master - jvm
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
if: ${{ github.ref_name == 'master' }}
with:
context: .

View File

@@ -1,5 +1,12 @@
# Changelog
## [5.10.0]
- Add an indicator next to each feed's unread count in the tree to show when new entries are discovered while the app is open (#1762)
- Feeds with uppercase HTTP:// or HTTPS:// URLs are now correctly handled again
- The aarch64 native executable now also works on the Raspberry Pi 5 (#1795)
- Improve general performance of the UI by reducing the number of re-renders, especially when a lot of entries are displayed (#1087)
## [5.9.0]
- A lot of CSS classes have been added to the elements of the application to ease custom CSS rules (#1757)

File diff suppressed because it is too large Load Diff

View File

@@ -19,14 +19,14 @@
"@fontsource/open-sans": "^5.2.5",
"@lingui/core": "^5.3.1",
"@lingui/react": "^5.3.1",
"@mantine/core": "^8.0.0",
"@mantine/form": "^8.0.0",
"@mantine/hooks": "^8.0.0",
"@mantine/modals": "^8.0.0",
"@mantine/notifications": "^8.0.0",
"@mantine/spotlight": "^8.0.0",
"@mantine/core": "^8.0.2",
"@mantine/form": "^8.0.2",
"@mantine/hooks": "^8.0.2",
"@mantine/modals": "^8.0.2",
"@mantine/notifications": "^8.0.2",
"@mantine/spotlight": "^8.0.2",
"@monaco-editor/react": "^4.7.0",
"@reduxjs/toolkit": "^2.8.1",
"@reduxjs/toolkit": "^2.8.2",
"axios": "^1.9.0",
"dayjs": "^1.11.13",
"escape-string-regexp": "^5.0.0",
@@ -43,7 +43,7 @@
"react-icons": "^5.5.0",
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.2.0",
"react-router-dom": "^7.6.0",
"react-router-dom": "^7.6.1",
"react-swipeable": "^7.0.2",
"redoc": "^2.5.0",
"style-to-object": "^1.0.8",
@@ -61,20 +61,21 @@
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/mousetrap": "^1.6.15",
"@types/react": "^19.1.4",
"@types/react": "^19.1.6",
"@types/react-dom": "^19.1.5",
"@types/react-infinite-scroller": "^1.2.5",
"@types/throttle-debounce": "^5.0.2",
"@types/tinycon": "^0.6.7",
"@vitejs/plugin-react": "^4.4.1",
"@vitejs/plugin-react": "^4.5.0",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-react-compiler": "^19.1.0-rc.2",
"jsdom": "^26.1.0",
"rollup-plugin-visualizer": "^5.14.0",
"rollup-plugin-visualizer": "^6.0.0",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-checker": "^0.9.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.3"
"vitest": "^3.1.4"
},
"overrides": {
"react-infinite-scroller": {

View File

@@ -6,16 +6,16 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>5.9.0</version>
<version>5.10.0</version>
</parent>
<artifactId>commafeed-client</artifactId>
<name>CommaFeed Client</name>
<properties>
<!-- renovate: datasource=node-version depName=node -->
<node.version>v22.15.0</node.version>
<node.version>v22.16.0</node.version>
<!-- renovate: datasource=npm depName=npm -->
<npm.version>11.3.0</npm.version>
<npm.version>11.4.1</npm.version>
</properties>
<build>

View File

@@ -143,11 +143,15 @@ function GoogleAnalyticsHandler() {
return null
}
function UnreadCountTitleHandler({ unreadCount, enabled }: { unreadCount: number; enabled?: boolean }) {
function UnreadCountTitleHandler({ enabled }: { enabled?: boolean }) {
const root = useAppSelector(state => state.tree.rootCategory)
const unreadCount = categoryUnreadCount(root)
return <title>{enabled && unreadCount > 0 ? `(${unreadCount}) CommaFeed` : "CommaFeed"}</title>
}
function UnreadCountFaviconHandler({ unreadCount, enabled }: { unreadCount: number; enabled?: boolean }) {
function UnreadCountFaviconHandler({ enabled }: { enabled?: boolean }) {
const root = useAppSelector(state => state.tree.rootCategory)
const unreadCount = categoryUnreadCount(root)
useEffect(() => {
if (enabled && unreadCount > 0) {
Tinycon.setBubble(unreadCount)
@@ -205,13 +209,10 @@ function CustomCssHandler() {
export function App() {
useI18n()
const root = useAppSelector(state => state.tree.rootCategory)
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
const dispatch = useAppDispatch()
const unreadCount = categoryUnreadCount(root)
useEffect(() => {
dispatch(reloadServerInfos())
}, [dispatch])
@@ -219,8 +220,8 @@ export function App() {
return (
<Providers>
<>
<UnreadCountTitleHandler unreadCount={unreadCount} enabled={unreadCountTitle} />
<UnreadCountFaviconHandler unreadCount={unreadCount} enabled={unreadCountFavicon} />
<UnreadCountTitleHandler enabled={unreadCountTitle} />
<UnreadCountFaviconHandler enabled={unreadCountFavicon} />
<BrowserExtensionBadgeUnreadCountHandler />
<CustomJsHandler />
<CustomCssHandler />

View File

@@ -1,12 +1,22 @@
import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
import { markEntry } from "app/entries/thunks"
import { loadEntries, markEntry } from "app/entries/thunks"
import { redirectTo } from "app/redirect/slice"
import { collapseTreeCategory, reloadTree } from "app/tree/thunks"
import type { Category } from "app/types"
import { visitCategoryTree } from "app/utils"
import type { Category, Subscription } from "app/types"
import { flattenCategoryTree, visitCategoryTree } from "app/utils"
export interface TreeSubscription extends Subscription {
// client-side only flag
hasNewEntries?: boolean
}
export interface TreeCategory extends Category {
feeds: TreeSubscription[]
children: TreeCategory[]
}
interface TreeState {
rootCategory?: Category
rootCategory?: TreeCategory
mobileMenuOpen: boolean
sidebarVisible: boolean
}
@@ -37,12 +47,27 @@ export const treeSlice = createSlice({
visitCategoryTree(state.rootCategory, c => {
for (const f of c.feeds.filter(f => f.id === action.payload.feedId)) {
f.unread += action.payload.amount
f.hasNewEntries = true
}
})
},
},
extraReducers: builder => {
builder.addCase(reloadTree.fulfilled, (state, action) => {
// set hasNewEntries to true if new unread > previous unread
if (state.rootCategory) {
const oldFeeds = flattenCategoryTree(state.rootCategory).flatMap(c => c.feeds)
const oldFeedsById = new Map(oldFeeds.map(f => [f.id, f]))
const newFeeds = flattenCategoryTree(action.payload).flatMap(c => c.feeds)
for (const newFeed of newFeeds) {
const oldFeed = oldFeedsById.get(newFeed.id)
if (oldFeed && newFeed.unread > oldFeed.unread) {
newFeed.hasNewEntries = true
}
}
}
state.rootCategory = action.payload
})
builder.addCase(collapseTreeCategory.pending, (state, action) => {
@@ -59,6 +84,25 @@ export const treeSlice = createSlice({
}
})
})
builder.addCase(loadEntries.fulfilled, (state, action) => {
if (!state.rootCategory) return
const { source } = action.meta.arg
if (source.type === "category") {
visitCategoryTree(state.rootCategory, c => {
if (c.id === source.id) {
for (const f of flattenCategoryTree(c).flatMap(c => c.feeds)) {
f.hasNewEntries = false
}
}
})
} else if (source.type === "feed") {
const feeds = flattenCategoryTree(state.rootCategory).flatMap(c => c.feeds)
for (const f of feeds.filter(f => f.id === +source.id)) {
f.hasNewEntries = false
}
}
})
builder.addCase(redirectTo, state => {
state.mobileMenuOpen = false
})

View File

@@ -1,8 +1,13 @@
import { configureStore } from "@reduxjs/toolkit"
import { client } from "app/client"
import { loadEntries } from "app/entries/thunks"
import { type RootState, reducers } from "app/store"
import { selectNextUnreadTreeItem } from "app/tree/thunks"
import type { Category, Subscription } from "app/types"
import { describe, expect, it } from "vitest"
import { newFeedEntriesDiscovered, selectNextUnreadTreeItem } from "app/tree/thunks"
import type { Category, Entries, Entry, Subscription } from "app/types"
import type { AxiosResponse } from "axios"
import { beforeEach, describe, expect, it, vi } from "vitest"
vi.mock(import("app/client"))
const createCategory = (id: string): Category => ({
id,
@@ -117,3 +122,51 @@ describe("selectNextUnreadTreeItem", () => {
expect(store.getState().redirect.to).toBe("/app/feed/3")
})
})
describe("hasNewEntries", () => {
beforeEach(() => {
vi.resetAllMocks()
})
it("sets and clear flag for a feed", async () => {
vi.mocked(client.feed.getEntries).mockResolvedValue({
data: {
entries: [{ id: "3" } as Entry],
hasMore: false,
name: "my-feed",
errorCount: 3,
feedLink: "https://mysite.com/feed",
timestamp: 123,
ignoredReadStatus: false,
},
} as AxiosResponse<Entries>)
const store = configureStore({
reducer: reducers,
preloadedState: {
tree: {
rootCategory: root,
},
entries: {
source: {
type: "feed",
id: "1",
},
},
} as RootState,
})
// initial state
expect(store.getState().tree.rootCategory?.children[0].feeds[0].unread).toBe(0)
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBeFalsy()
// increments unread count and sets hasNewEntries to true
await store.dispatch(newFeedEntriesDiscovered({ feedId: 1, amount: 3 }))
expect(store.getState().tree.rootCategory?.children[0].feeds[0].unread).toBe(3)
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBe(true)
// reload entries and sets hasNewEntries to false
await store.dispatch(loadEntries({ source: { type: "feed", id: "1" }, clearSearch: true }))
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBe(false)
})
})

View File

@@ -1,9 +1,10 @@
import type { TreeCategory } from "app/tree/slice"
import { throttle } from "throttle-debounce"
import type { Category } from "./types"
export function visitCategoryTree(
category: Category,
visitor: (category: Category) => void,
category: TreeCategory,
visitor: (category: TreeCategory) => void,
options?: {
childrenFirst?: boolean
}
@@ -19,13 +20,13 @@ export function visitCategoryTree(
if (childrenFirst) visitor(category)
}
export function flattenCategoryTree(category: Category): Category[] {
export function flattenCategoryTree(category: TreeCategory): TreeCategory[] {
const categories: Category[] = []
visitCategoryTree(category, c => categories.push(c))
return categories
}
export function categoryUnreadCount(category?: Category): number {
export function categoryUnreadCount(category?: TreeCategory): number {
if (!category) return 0
return flattenCategoryTree(category)
@@ -34,6 +35,14 @@ export function categoryUnreadCount(category?: Category): number {
.reduce((total, current) => total + current, 0)
}
export function categoryHasNewEntries(category?: TreeCategory): boolean {
if (!category) return false
return flattenCategoryTree(category)
.flatMap(c => c.feeds)
.some(f => f.hasNewEntries)
}
export const calculatePlaceholderSize = ({ width, height, maxWidth }: { width?: number; height?: number; maxWidth: number }) => {
const placeholderWidth = width && Math.min(width, maxWidth)
const placeholderHeight = height && width && width > maxWidth ? height * (maxWidth / width) : height

View File

@@ -10,9 +10,10 @@ import {
redirectToTagDetails,
} from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import type { TreeSubscription } from "app/tree/slice"
import { collapseTreeCategory } from "app/tree/thunks"
import type { Category, Subscription } from "app/types"
import { categoryUnreadCount, flattenCategoryTree } from "app/utils"
import { categoryHasNewEntries, categoryUnreadCount, flattenCategoryTree } from "app/utils"
import { Loader } from "components/Loader"
import { OnDesktop } from "components/responsive/OnDesktop"
import React from "react"
@@ -89,6 +90,7 @@ export function Tree() {
name={<Trans>All</Trans>}
icon={allIcon}
unread={categoryUnreadCount(root)}
hasNewEntries={categoryHasNewEntries(root)}
selected={source.type === "category" && source.id === Constants.categories.all.id}
expanded={false}
level={0}
@@ -103,6 +105,7 @@ export function Tree() {
name={<Trans>Starred</Trans>}
icon={starredIcon}
unread={0}
hasNewEntries={false}
selected={source.type === "category" && source.id === Constants.categories.starred.id}
expanded={false}
level={0}
@@ -122,6 +125,7 @@ export function Tree() {
name={category.name}
icon={category.expanded ? expandedIcon : collapsedIcon}
unread={categoryUnreadCount(category)}
hasNewEntries={categoryHasNewEntries(category)}
selected={source.type === "category" && source.id === category.id}
expanded={category.expanded}
level={level}
@@ -133,7 +137,7 @@ export function Tree() {
)
}
const feedNode = (feed: Subscription, level = 0) => {
const feedNode = (feed: TreeSubscription, level = 0) => {
if (!isFeedDisplayed(feed)) return null
return (
@@ -143,6 +147,7 @@ export function Tree() {
name={feed.name}
icon={feed.iconUrl}
unread={feed.unread}
hasNewEntries={!!feed.hasNewEntries}
selected={source.type === "feed" && source.id === String(feed.id)}
level={level}
hasError={feed.errorCount > errorThreshold}
@@ -159,6 +164,7 @@ export function Tree() {
name={tag}
icon={tagIcon}
unread={0}
hasNewEntries={false}
selected={source.type === "tag" && source.id === tag}
level={0}
hasError={false}

View File

@@ -15,6 +15,7 @@ interface TreeNodeProps {
expanded?: boolean
level: number
hasError: boolean
hasNewEntries: boolean
onClick: (e: React.MouseEvent, id: string) => void
onIconClick?: (e: React.MouseEvent, id: string) => void
}
@@ -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} showIndicator={props.hasNewEntries} />
</Box>
)}
</Box>

View File

@@ -1,4 +1,4 @@
import { Badge, Tooltip } from "@mantine/core"
import { Badge, Indicator, Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import { tss } from "tss"
@@ -10,7 +10,7 @@ const useStyles = tss.create(() => ({
},
}))
export function UnreadCount(props: { unreadCount: number }) {
export function UnreadCount(props: { unreadCount: number; showIndicator: boolean }) {
const { classes } = useStyles()
if (props.unreadCount <= 0) return null
@@ -18,9 +18,11 @@ export function UnreadCount(props: { unreadCount: number }) {
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.showIndicator} size={4} offset={10} position="middle-start">
<Badge className={`${classes.badge} cf-badge`} variant="light" fullWidth>
{count}
</Badge>
</Indicator>
</Tooltip>
)
}

View File

@@ -8,28 +8,28 @@ interface Step {
}
export const useAppLoading = () => {
const profile = useAppSelector(state => state.user.profile)
const settings = useAppSelector(state => state.user.settings)
const rootCategory = useAppSelector(state => state.tree.rootCategory)
const tags = useAppSelector(state => state.user.tags)
const profileLoaded = useAppSelector(state => !!state.user.profile)
const settingsLoaded = useAppSelector(state => !!state.user.settings)
const rootCategoryLoaded = useAppSelector(state => !!state.tree.rootCategory)
const tagsLoaded = useAppSelector(state => !!state.user.tags)
const { _ } = useLingui()
const steps: Step[] = [
{
label: _(msg`Loading settings...`),
done: !!settings,
done: settingsLoaded,
},
{
label: _(msg`Loading profile...`),
done: !!profile,
done: profileLoaded,
},
{
label: _(msg`Loading subscriptions...`),
done: !!rootCategory,
done: rootCategoryLoaded,
},
{
label: _(msg`Loading tags...`),
done: !!tags,
done: tagsLoaded,
},
]

View File

@@ -42,7 +42,9 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
const { id = Constants.categories.all.id } = useParams()
const viewport = useViewportSize()
const theme = useMantineTheme()
const rootCategory = useAppSelector(state => state.tree.rootCategory)
const noSubscriptions = useAppSelector(
state => state.tree.rootCategory && flattenCategoryTree(state.tree.rootCategory).every(c => c.feeds.length === 0)
)
const sourceLabel = useAppSelector(state => state.entries.sourceLabel)
const sourceWebsiteUrl = useAppSelector(state => state.entries.sourceWebsiteUrl)
const hasMore = useAppSelector(state => state.entries.hasMore)
@@ -83,7 +85,6 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
return () => promise.abort()
}, [dispatch, props.sourceType, id, location.state?.timestamp])
const noSubscriptions = rootCategory && flattenCategoryTree(rootCategory).every(c => c.feeds.length === 0)
if (noSubscriptions) return <NoSubscriptionHelp />
return (
// add some room at the bottom of the page in order to be able to scroll the current entry at the top of the page when expanding

View File

@@ -10,7 +10,7 @@ export default defineConfig(() => ({
plugins: [
react({
babel: {
plugins: ["@lingui/babel-plugin-lingui-macro"],
plugins: [["babel-plugin-react-compiler", { target: "19" }], "@lingui/babel-plugin-lingui-macro"],
},
}),
lingui(),

View File

@@ -6,16 +6,16 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>5.9.0</version>
<version>5.10.0</version>
</parent>
<artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name>
<properties>
<quarkus.version>3.22.2</quarkus.version>
<quarkus.version>3.22.3</quarkus.version>
<querydsl.version>6.11</querydsl.version>
<rome.version>2.1.0</rome.version>
<swagger.version>2.2.31</swagger.version>
<swagger.version>2.2.32</swagger.version>
<build.database>h2</build.database>
</properties>
@@ -221,7 +221,7 @@
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>9.0.1</version>
<version>9.0.2</version>
<executions>
<execution>
<goals>
@@ -269,7 +269,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.23.1</version>
<version>10.24.0</version>
</dependency>
</dependencies>
<executions>
@@ -327,7 +327,7 @@
<dependency>
<groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId>
<version>5.9.0</version>
<version>5.10.0</version>
</dependency>
<!-- compile-time processors -->
@@ -391,7 +391,7 @@
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-json</artifactId>
<version>4.2.30</version>
<version>4.2.32</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
@@ -483,7 +483,7 @@
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.4.4</version>
<version>5.5</version>
</dependency>
<!-- add brotli support for httpclient5 -->
<dependency>

View File

@@ -1,4 +1,4 @@
FROM debian:12.10@sha256:264982ff4d18000fa74540837e2c43ca5137a53a83f8f62c7b3803c0f0bdcd56
FROM debian:12.11@sha256:bd73076dc2cd9c88f48b5b358328f24f2a4289811bd73787c031e20db9f97123
ARG TARGETARCH
EXPOSE 8082

View File

@@ -151,7 +151,7 @@ public class HttpGetter {
}
private void ensureHttpScheme(String scheme) throws SchemeNotAllowedException {
if (!"http".equals(scheme) && !"https".equals(scheme)) {
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
throw new SchemeNotAllowedException(scheme);
}
}

View File

@@ -38,6 +38,8 @@ quarkus.shutdown.timeout=5s
# native
quarkus.native.march=compatibility
quarkus.native.add-all-charsets=true
# fix for https://github.com/Athou/commafeed/issues/1795
quarkus.native.additional-build-args=-H:PageSize=65536
# dev profile overrides

View File

@@ -5,7 +5,7 @@
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>5.9.0</version>
<version>5.10.0</version>
<name>CommaFeed</name>
<packaging>pom</packaging>