Compare commits

..

84 Commits
5.2.0 ... 5.3.4

Author SHA1 Message Date
Athou
42015015a5 release 5.3.4 2024-10-23 20:01:40 +02:00
Athou
f3d2808f7d add support for Internationalized Domain Names (#1588) 2024-10-23 19:58:47 +02:00
renovate[bot]
906c353a7f chore(deps): update dependency @types/react to ^18.3.12 2024-10-23 06:05:51 +00:00
renovate[bot]
93dea83cd3 chore(deps): update dependency vite to ^5.4.10 2024-10-23 03:47:21 +00:00
renovate[bot]
1fc76ce1ad chore(deps): update dependency org.apache.maven.plugins:maven-help-plugin to v3.5.1 2024-10-21 19:53:59 +00:00
Athou
a337b01bc7 release 5.3.3 2024-10-21 21:53:28 +02:00
Athou
689e5c0004 remove image bottom margins (#1587) 2024-10-21 21:51:46 +02:00
Athou
d5a3c81c85 release 5.3.2 2024-10-21 21:20:27 +02:00
Athou
8230fde5d2 preserve style attribute from images (#1587) 2024-10-21 21:15:57 +02:00
renovate[bot]
b35513ea84 chore(deps): lock file maintenance 2024-10-21 01:29:16 +00:00
renovate[bot]
42a7785ca1 chore(deps): update dependency @vitejs/plugin-react to ^4.3.3 2024-10-19 18:29:57 +00:00
Jérémie Panzer
ea5ee4f04f Merge pull request #1586 from Athou/renovate/com.microsoft.playwright-playwright-1.x
chore(deps): update dependency com.microsoft.playwright:playwright to v1.48.0
2024-10-18 05:53:42 +02:00
renovate[bot]
3e14b12d4f chore(deps): update dependency com.microsoft.playwright:playwright to v1.48.0 2024-10-17 21:46:18 +00:00
renovate[bot]
78cc30f828 chore(deps): update dependency @biomejs/biome to v1.9.4 2024-10-17 21:46:15 +00:00
renovate[bot]
6091c84e60 fix(deps): update mantine monorepo to ^7.13.3 2024-10-17 09:14:54 +00:00
Jérémie Panzer
6ea95ad254 Merge pull request #1585 from Athou/renovate/redoc-2.x
fix(deps): update dependency redoc to ^2.2.0
2024-10-16 14:24:28 +02:00
renovate[bot]
7f888d926e fix(deps): update dependency redoc to ^2.2.0 2024-10-16 11:00:17 +00:00
Jérémie Panzer
5e4e02474f Merge pull request #1583 from Athou/renovate/linguijs-monorepo
chore(deps): update linguijs monorepo to ^4.13.0 (minor)
2024-10-15 16:07:30 +02:00
renovate[bot]
bff8611b42 chore(deps): update linguijs monorepo to ^4.13.0 2024-10-15 10:56:26 +00:00
renovate[bot]
f674048af3 fix(deps): update dependency org.passay:passay to v1.6.6 2024-10-15 02:21:46 +00:00
Jérémie Panzer
0265c24cf9 Merge pull request #1582 from Athou/renovate/reduxjs-toolkit-2.x
fix(deps): update dependency @reduxjs/toolkit to ^2.3.0
2024-10-15 04:21:26 +02:00
renovate[bot]
f8c3a229ec fix(deps): update dependency @reduxjs/toolkit to ^2.3.0 2024-10-14 21:53:07 +00:00
renovate[bot]
c424f40420 chore(deps): update dependency vitest to ^2.1.3 2024-10-14 21:52:47 +00:00
renovate[bot]
b77666cfe5 chore(deps): update dependency vite to ^5.4.9 2024-10-14 19:06:28 +00:00
renovate[bot]
193d1604d9 chore(deps): lock file maintenance 2024-10-14 03:20:28 +00:00
renovate[bot]
4efc6296b5 chore(deps): lock file maintenance 2024-10-14 01:22:19 +00:00
Jérémie Panzer
f753a4bdda Merge pull request #1578 from Athou/renovate/react-router-monorepo
fix(deps): update dependency react-router-dom to ^6.27.0
2024-10-12 07:25:02 +02:00
Jérémie Panzer
afaaaf9657 Merge pull request #1577 from Athou/renovate/linguijs-monorepo
chore(deps): update linguijs monorepo to ^4.12.0 (minor)
2024-10-12 07:24:54 +02:00
renovate[bot]
4d46896bf0 fix(deps): update dependency react-router-dom to ^6.27.0 2024-10-11 20:09:41 +00:00
renovate[bot]
2ad28c927f chore(deps): update linguijs monorepo to ^4.12.0 2024-10-11 15:36:55 +00:00
renovate[bot]
b9680a66ef chore(deps): update dependency @types/react-dom to ^18.3.1 2024-10-11 15:36:38 +00:00
Athou
4f687d5857 indicate that -Xmx also works for the native package (#1539) 2024-10-10 15:29:18 +02:00
renovate[bot]
9cca026834 chore(deps): update dependency typescript to ^5.6.3 2024-10-09 01:04:51 +00:00
renovate[bot]
058a9cd192 fix(deps): update dependency @reduxjs/toolkit to ^2.2.8 2024-10-08 07:21:11 +00:00
renovate[bot]
57d2ede86e chore(deps): update dependency org.apache.maven.plugins:maven-failsafe-plugin to v3.5.1 2024-10-07 13:07:50 +00:00
renovate[bot]
e3abea4ec5 chore(deps): lock file maintenance 2024-10-07 00:38:09 +00:00
renovate[bot]
b831f1f35c chore(deps): update dependency org.apache.maven.plugins:maven-surefire-plugin to v3.5.1 2024-10-06 12:53:45 +00:00
Athou
74bce1308c release 5.3.1 2024-10-04 20:33:56 +02:00
Athou
98cfa6d2c8 add regression test 2024-10-04 20:24:43 +02:00
Athou
99a02a2186 fix issue with some HTTP feeds (#1572) 2024-10-04 20:20:02 +02:00
Jérémie Panzer
3431a813b1 Merge pull request #1574 from Athou/renovate/npm-10.x
chore(deps): update dependency npm to v10.9.0
2024-10-04 07:51:15 +02:00
renovate[bot]
e9e0e8d32b chore(deps): update dependency npm to v10.9.0 2024-10-03 19:31:06 +00:00
renovate[bot]
2d14409d35 fix(deps): update dependency io.dropwizard.metrics:metrics-json to v4.2.28 2024-10-03 19:31:04 +00:00
Jérémie Panzer
a8200e5c58 Merge pull request #1573 from Athou/renovate/node-20.x
chore(deps): update dependency node to v20.18.0
2024-10-03 21:30:44 +02:00
renovate[bot]
79a8df8b06 chore(deps): update dependency node to v20.18.0 2024-10-03 19:00:34 +00:00
renovate[bot]
061a5f0262 fix(deps): update mantine monorepo to ^7.13.2 2024-10-03 13:32:54 +00:00
renovate[bot]
821bdb3b0f chore(deps): update dependency vitest to ^2.1.2 2024-10-02 21:46:25 +00:00
renovate[bot]
606dfa9299 chore(deps): update dependency @types/react to ^18.3.11 2024-10-02 18:42:46 +00:00
renovate[bot]
131357c616 fix(deps): update swagger.version to v2.2.25 2024-10-02 13:17:53 +00:00
renovate[bot]
f6d3493bad chore(deps): update dependency @biomejs/biome to v1.9.3 2024-10-01 14:28:45 +00:00
renovate[bot]
0c6104e25b fix(deps): update mantine monorepo to ^7.13.1 2024-09-30 09:40:08 +00:00
Jérémie Panzer
d73735a35d Merge pull request #1571 from Athou/renovate/vitejs-plugin-react-4.3.x
chore(deps): update dependency @vitejs/plugin-react to ^4.3.2
2024-09-30 06:31:07 +02:00
renovate[bot]
e725d2d6b6 chore(deps): update dependency @vitejs/plugin-react to ^4.3.2 2024-09-30 04:02:46 +00:00
renovate[bot]
f0e1279d68 chore(deps): lock file maintenance 2024-09-30 00:36:34 +00:00
renovate[bot]
c74c74d2c4 chore(deps): update dependency com.puppycrawl.tools:checkstyle to v10.18.2 2024-09-29 19:11:52 +00:00
renovate[bot]
aa70cf5dcd chore(deps): update dependency @types/react to ^18.3.10 2024-09-27 16:41:55 +00:00
Jérémie Panzer
1055259627 Merge pull request #1569 from canoine/patch-1
Update fr/messages.po
2024-09-26 22:35:33 +02:00
canoine
302d37b6ef Update fr/messages.po
French translation update
2024-09-26 21:41:37 +02:00
Jérémie Panzer
8532a73d94 Merge pull request #1565 from Athou/renovate/quarkus.version
chore(deps): update quarkus.version to v3.15.1 (minor)
2024-09-26 10:25:09 +02:00
Athou
ffafb272cb update docs 2024-09-26 10:08:52 +02:00
Jérémie Panzer
22e0171a34 Merge pull request #1566 from dai/master
chore(ja): translated some strings
2024-09-26 10:03:32 +02:00
renovate[bot]
2b410f040c Update quarkus.version to v3.15.1 2024-09-26 08:02:28 +00:00
dai
259e8ad4e5 chore: translated some strings
Chore: Translated some new strings and reworked some wording.
2024-09-26 14:25:19 +09:00
Jérémie Panzer
21244dd9f5 Merge pull request #1564 from Athou/renovate/mantine-monorepo
Update mantine monorepo to ^7.13.0 (minor)
2024-09-25 12:52:53 +02:00
renovate[bot]
bc6206180d Update mantine monorepo to ^7.13.0 2024-09-25 09:53:00 +00:00
renovate[bot]
6e22d21358 Update dependency vite to ^5.4.8 2024-09-25 05:04:17 +00:00
renovate[bot]
95bdb4e700 Update dependency @types/react to ^18.3.9 2024-09-24 15:24:13 +00:00
Athou
9b7dbc68ab release 5.3.0 2024-09-24 07:58:43 +02:00
Athou
dc86c9b0db also manually load more entries if needed when pressing the next entry button in the header (#1557) 2024-09-24 07:54:50 +02:00
renovate[bot]
cb92ed753f Update swagger.version to v2.2.24 2024-09-23 16:36:50 +00:00
renovate[bot]
10a085e24e Lock file maintenance 2024-09-23 00:08:26 +00:00
renovate[bot]
3400a39edf Update dependency jsdom to ^25.0.1 2024-09-22 07:08:58 +00:00
Athou
21efffa345 Update dependency io.github.hakky54:sslcontext-kickstart-for-apache5 to v8.3.7
Update dependency org.apache.httpcomponents.client5:httpclient5 to v5.4
2024-09-22 09:07:20 +02:00
renovate[bot]
e2e80ba7e5 Update dependency com.github.eirslett:frontend-maven-plugin to v1.15.1 2024-09-21 18:11:14 +00:00
Jérémie Panzer
d988dba66e Merge pull request #1563 from Athou/renovate/querydsl.version
Update querydsl.version to v6.8 (minor)
2024-09-21 15:42:21 +02:00
renovate[bot]
403201fbff Update querydsl.version to v6.8 2024-09-21 13:25:28 +00:00
Athou
3cc93b51bb set default cooldown duration to 0 so it's not a breaking change 2024-09-21 10:57:16 +02:00
Athou
6a7d83bb45 show an error if force fetching feeds is not yet available 2024-09-21 10:31:17 +02:00
Athou
19c8db8b31 add a cooldown on the force refresh action (#1556) 2024-09-21 08:24:14 +02:00
renovate[bot]
0d75688ec8 Update dependency vite to ^5.4.7 2024-09-20 16:39:11 +00:00
renovate[bot]
e01dcb2f5b Update dependency @types/react to ^18.3.8 2024-09-19 22:25:45 +00:00
Jérémie Panzer
57757e2c14 Merge pull request #1561 from Athou/renovate/monaco-editor-0.x
Update dependency monaco-editor to ^0.52.0
2024-09-19 17:59:58 +02:00
renovate[bot]
779cd2fcfe Update dependency monaco-editor to ^0.52.0 2024-09-19 13:51:05 +00:00
renovate[bot]
94919f22e4 Update dependency @biomejs/biome to v1.9.2 2024-09-19 13:51:00 +00:00
64 changed files with 980 additions and 714 deletions

View File

@@ -1,5 +1,26 @@
# Changelog
## [5.3.4]
- Added support for Internationalized Domain Names (#1588)
## [5.3.3]
- Removed image bottom margins (#1587)
## [5.3.2]
- Fixed an issue that could cause some images from not being rendered correctly (#1587)
## [5.3.1]
- Fixed an issue that could cause some HTTP feeds to return a 400 error (#1572)
## [5.3.0]
- Added a setting to set a cooldown on the "fetch all my feeds" action, disabled by default (#1556)
- Fixed an issue that could cause entries to not correctly load when using the "next" header button (#1557)
## [5.2.0]
- Added an option to keep a number of entries above the selected entry when scrolling

View File

@@ -114,18 +114,18 @@ The default user is `admin` and the default password is `admin`.
When CommaFeed is up and running, you can subscribe to [this feed](https://github.com/Athou/commafeed/releases.atom) to be notified of new releases.
### Memory management (`jvm` package only)
### Memory management
The Java Virtual Machine (JVM) is rather greedy by default and will not release unused memory to the
operating system. This is because acquiring memory from the operating system is a relatively expensive operation.
This can be problematic on systems with limited memory.
#### Hard limit
#### Hard limit (`native` and `jvm` packages)
The JVM can be configured to use a maximum amount of memory with the `-Xmx` parameter.
For example, to limit the JVM to 256MB of memory, use `-Xmx256m`.
#### Dynamic sizing
#### Dynamic sizing (`jvm` package)
In addition to the previous setting, the JVM can be configured to release unused memory to the operating system with the
following parameters:
@@ -137,7 +137,7 @@ and [here](https://docs.oracle.com/en/java/javase/17/gctuning/factors-affecting-
more
information.
#### OpenJ9
#### OpenJ9 (`jvm` package)
The [OpenJ9](https://eclipse.dev/openj9/) JVM is a more memory-efficient alternative to the HotSpot JVM, at the cost of
slightly slower throughput.

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.1/schema.json",
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"formatter": {
"indentStyle": "space",
"indentWidth": 4,

File diff suppressed because it is too large Load Diff

View File

@@ -17,22 +17,22 @@
"dependencies": {
"@emotion/react": "^11.13.3",
"@fontsource/open-sans": "^5.1.0",
"@lingui/core": "^4.11.4",
"@lingui/macro": "^4.11.4",
"@lingui/react": "^4.11.4",
"@mantine/core": "^7.12.2",
"@mantine/form": "^7.12.2",
"@mantine/hooks": "^7.12.2",
"@mantine/modals": "^7.12.2",
"@mantine/notifications": "^7.12.2",
"@mantine/spotlight": "^7.12.2",
"@lingui/core": "^4.13.0",
"@lingui/macro": "^4.13.0",
"@lingui/react": "^4.13.0",
"@mantine/core": "^7.13.3",
"@mantine/form": "^7.13.3",
"@mantine/hooks": "^7.13.3",
"@mantine/modals": "^7.13.3",
"@mantine/notifications": "^7.13.3",
"@mantine/spotlight": "^7.13.3",
"@monaco-editor/react": "^4.6.0",
"@reduxjs/toolkit": "^2.2.7",
"@reduxjs/toolkit": "^2.3.0",
"axios": "^1.7.7",
"dayjs": "^1.11.13",
"escape-string-regexp": "^5.0.0",
"interweave": "^13.1.0",
"monaco-editor": "^0.51.0",
"monaco-editor": "^0.52.0",
"mousetrap": "^1.6.5",
"react": "^18.3.1",
"react-async-hook": "^4.0.0",
@@ -45,35 +45,36 @@
"react-icons": "^5.3.0",
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.1.2",
"react-router-dom": "^6.26.2",
"react-router-dom": "^6.27.0",
"react-swipeable": "^7.0.1",
"redoc": "^2.1.5",
"redoc": "^2.2.0",
"style-to-object": "^1.0.8",
"throttle-debounce": "^5.0.2",
"tinycon": "^0.6.8",
"tss-react": "^4.9.13",
"websocket-heartbeat-js": "^1.1.3"
},
"devDependencies": {
"@biomejs/biome": "^1.9.1",
"@lingui/cli": "^4.11.4",
"@lingui/vite-plugin": "^4.11.4",
"@biomejs/biome": "^1.9.4",
"@lingui/cli": "^4.13.0",
"@lingui/vite-plugin": "^4.13.0",
"@types/mousetrap": "^1.6.15",
"@types/react": "^18.3.7",
"@types/react-dom": "^18.3.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-helmet": "^6.1.11",
"@types/react-infinite-scroller": "^1.2.5",
"@types/swagger-ui-react": "^4.18.3",
"@types/throttle-debounce": "^5.0.2",
"@types/tinycon": "^0.6.5",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react": "^4.3.3",
"babel-plugin-macros": "^3.1.0",
"jsdom": "^25.0.0",
"jsdom": "^25.0.1",
"rollup-plugin-visualizer": "^5.12.0",
"typescript": "^5.6.2",
"vite": "^5.4.6",
"typescript": "^5.6.3",
"vite": "^5.4.10",
"vite-plugin-checker": "^0.8.0",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^2.1.1",
"vitest": "^2.1.3",
"vitest-mock-extended": "^2.0.2"
}
}

View File

@@ -6,16 +6,16 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>5.2.0</version>
<version>5.3.4</version>
</parent>
<artifactId>commafeed-client</artifactId>
<name>CommaFeed Client</name>
<properties>
<!-- renovate: datasource=node-version depName=node -->
<node.version>v20.17.0</node.version>
<node.version>v20.18.0</node.version>
<!-- renovate: datasource=npm depName=npm -->
<npm.version>10.8.3</npm.version>
<npm.version>10.9.0</npm.version>
</properties>
<build>
@@ -23,7 +23,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.15.0</version>
<version>1.15.1</version>
<?m2e ignore?>
<executions>
<execution>

View File

@@ -230,7 +230,7 @@ export const selectPreviousEntry = createAppAsyncThunk(
)
export const selectNextEntry = createAppAsyncThunk(
"entries/entry/selectNext",
(
async (
arg: {
expand: boolean
markAsRead: boolean
@@ -239,12 +239,20 @@ export const selectNextEntry = createAppAsyncThunk(
thunkApi
) => {
const state = thunkApi.getState()
const { entries } = state.entries
const { entries, hasMore, loading } = state.entries
const nextIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) + 1
if (nextIndex < entries.length) {
// load more entries if needed
// this can happen if the last entry is too large to fit on the screen and the infinite loader doesn't trigger
if (nextIndex >= entries.length && hasMore && !loading) {
await thunkApi.dispatch(loadMoreEntries())
}
const entriesAfterLoading = thunkApi.getState().entries.entries
if (nextIndex < entriesAfterLoading.length) {
thunkApi.dispatch(
selectEntry({
entry: entries[nextIndex],
entry: entriesAfterLoading[nextIndex],
expand: arg.expand,
markAsRead: arg.markAsRead,
scrollToEntry: arg.scrollToEntry,

View File

@@ -220,6 +220,7 @@ export interface ServerInfo {
websocketEnabled: boolean
websocketPingInterval: number
treeReloadInterval: number
forceRefreshCooldownDuration: number
}
export interface SharingSettings {
@@ -287,6 +288,7 @@ export interface UserModel {
created: number
lastLogin?: number
admin: boolean
lastForceRefresh?: number
}
export interface AdminSaveUserRequest {

View File

@@ -9,6 +9,7 @@ interface ImageWithPlaceholderWhileLoadingProps {
title?: string
width?: number
height?: number | "auto"
style?: React.CSSProperties
placeholderWidth?: number
placeholderHeight?: number
placeholderBackgroundColor?: string
@@ -42,6 +43,7 @@ export function ImageWithPlaceholderWhileLoading({
src,
title,
width,
style,
}: ImageWithPlaceholderWhileLoadingProps) {
const { classes } = useStyles({
placeholderWidth,
@@ -68,7 +70,7 @@ export function ImageWithPlaceholderWhileLoading({
width={width}
height={height}
onLoad={() => setLoading(false)}
style={{ display: loading ? "none" : "block" }}
style={{ ...style, display: loading ? "none" : (style?.display ?? "initial") }}
/>
</>
)

View File

@@ -2,14 +2,10 @@ import { Trans } from "@lingui/macro"
import { Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import dayjs from "dayjs"
import { useEffect, useState } from "react"
import { useNow } from "hooks/useNow"
export function RelativeDate(props: { date: Date | number | undefined }) {
const [now, setNow] = useState(new Date())
useEffect(() => {
const interval = setInterval(() => setNow(new Date()), 60 * 1000)
return () => clearInterval(interval)
}, [])
const now = useNow(60 * 1000)
if (!props.date) return <Trans>N/A</Trans>
const date = dayjs(props.date)

View File

@@ -1,11 +1,24 @@
import { TypographyStylesProvider } from "@mantine/core"
import type { ReactNode } from "react"
import { tss } from "tss"
/**
* This component is used to provide basic styles to html typography elements.
*
* see https://mantine.dev/core/typography-styles-provider/
*/
const useStyles = tss.create(() => ({
// override mantine default typography styles
content: {
paddingLeft: 0,
"& img": {
marginBottom: 0,
},
},
}))
export const BasicHtmlStyles = (props: { children: ReactNode }) => {
return <TypographyStylesProvider pl={0}>{props.children}</TypographyStylesProvider>
const { classes } = useStyles()
return <TypographyStylesProvider className={classes.content}>{props.children}</TypographyStylesProvider>
}

View File

@@ -6,6 +6,7 @@ import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import escapeStringRegexp from "escape-string-regexp"
import { type ChildrenNode, Interweave, type MatchResponse, Matcher, type Node, type TransformCallback } from "interweave"
import React from "react"
import styleToObject from "style-to-object"
import { tss } from "tss"
export interface ContentProps {
@@ -42,6 +43,7 @@ const transform: TransformCallback = node => {
const nodeHeight = node.getAttribute("height")
const width = nodeWidth ? Number.parseInt(nodeWidth, 10) : undefined
const height = nodeHeight ? Number.parseInt(nodeHeight, 10) : undefined
const style = styleToObject(node.getAttribute("style") ?? "") ?? undefined
const placeholderSize = calculatePlaceholderSize({
width,
height,
@@ -55,6 +57,7 @@ const transform: TransformCallback = node => {
title={title}
width={width}
height="auto"
style={style}
placeholderWidth={placeholderSize.width}
placeholderHeight={placeholderSize.height}
/>

View File

@@ -128,36 +128,28 @@ export function FeedEntries() {
}, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry])
useMousetrap("r", async () => await dispatch(reloadEntries()))
useMousetrap("j", async () => {
// load more entries if needed
// this can happen if the last entry is too large to fit on the screen and the infinite loader doesn't trigger
if (hasMore && !loading && selectedEntry === entries[entries.length - 1]) {
await dispatch(loadMoreEntries())
}
await dispatch(
selectNextEntry({
expand: true,
markAsRead: true,
scrollToEntry: true,
})
)
})
useMousetrap("n", async () => {
// load more entries if needed
// this can happen if the last entry is too large to fit on the screen and the infinite loader doesn't trigger
if (hasMore && !loading && selectedEntry === entries[entries.length - 1]) {
await dispatch(loadMoreEntries())
}
await dispatch(
selectNextEntry({
expand: false,
markAsRead: false,
scrollToEntry: true,
})
)
})
useMousetrap(
"j",
async () =>
await dispatch(
selectNextEntry({
expand: true,
markAsRead: true,
scrollToEntry: true,
})
)
)
useMousetrap(
"n",
async () =>
await dispatch(
selectNextEntry({
expand: false,
markAsRead: false,
scrollToEntry: true,
})
)
)
useMousetrap(
"k",
async () =>

View File

@@ -15,6 +15,9 @@ import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetr
import { useAppDispatch, useAppSelector } from "app/store"
import type { ViewMode } from "app/types"
import { setViewMode } from "app/user/slice"
import { reloadProfile } from "app/user/thunks"
import dayjs from "dayjs"
import { useNow } from "hooks/useNow"
import { type ReactNode, useState } from "react"
import {
TbChartLine,
@@ -92,12 +95,19 @@ const viewModeData: ViewModeControlItem[] = [
export function ProfileMenu(props: ProfileMenuProps) {
const [opened, setOpened] = useState(false)
const now = useNow()
const profile = useAppSelector(state => state.user.profile)
const admin = useAppSelector(state => state.user.profile?.admin)
const viewMode = useAppSelector(state => state.user.localSettings.viewMode)
const forceRefreshCooldownDuration = useAppSelector(state => state.server.serverInfos?.forceRefreshCooldownDuration)
const dispatch = useAppDispatch()
const { colorScheme, setColorScheme } = useMantineColorScheme()
const nextAvailableForceRefresh = profile?.lastForceRefresh
? profile.lastForceRefresh + (forceRefreshCooldownDuration ?? 0)
: now.getTime()
const forceRefreshEnabled = nextAvailableForceRefresh <= now.getTime()
const logout = () => {
window.location.href = "logout"
}
@@ -118,18 +128,32 @@ export function ProfileMenu(props: ProfileMenuProps) {
</Menu.Item>
<Menu.Item
leftSection={<TbWorldDownload size={iconSize} />}
onClick={async () =>
await client.feed.refreshAll().then(() => {
disabled={!forceRefreshEnabled}
onClick={async () => {
setOpened(false)
try {
await client.feed.refreshAll()
// reload profile to update last force refresh timestamp
await dispatch(reloadProfile())
showNotification({
message: <Trans>Your feeds have been queued for refresh.</Trans>,
color: "green",
autoClose: 1000,
})
setOpened(false)
})
}
} catch (error) {
showNotification({
message: <Trans>Force fetching feeds is not yet available.</Trans>,
color: "red",
autoClose: 2000,
})
}
}}
>
<Trans>Fetch all my feeds now</Trans>
{!forceRefreshEnabled && <span> ({dayjs.duration(nextAvailableForceRefresh - now.getTime()).format("HH:mm:ss")})</span>}
</Menu.Item>
<Divider />

View File

@@ -0,0 +1,10 @@
import { useEffect, useState } from "react"
export const useNow = (interval = 1000): Date => {
const [time, setTime] = useState(new Date())
useEffect(() => {
const t = setInterval(() => setTime(new Date()), interval)
return () => clearInterval(t)
}, [interval])
return time
}

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "تصفية التعبير"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "هل نسيت كلمة المرور؟"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Expressió de filtratge"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Heu oblidat la contrasenya?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filtrování výrazu"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Zapomněli jste heslo?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Hidlo mynegiant"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Wedi anghofio cyfrinair?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filtrerende udtryk"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Glemt adgangskode?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filterausdruck"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Passwort vergessen?"

View File

@@ -376,6 +376,10 @@ msgstr "Fever API URL"
msgid "Filtering expression"
msgstr "Filtering expression"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr "Force fetching feeds is not yet available."
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Forgot password?"

View File

@@ -377,6 +377,10 @@ msgstr "URL de la API de Fever"
msgid "Filtering expression"
msgstr "Expresión de filtrado"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "¿Olvidaste la contraseña?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "بیان فیلتر"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "رمز عبور را فراموش کرده اید؟"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Suodattava lauseke"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Unohditko salasanan?"

View File

@@ -323,7 +323,7 @@ msgstr "Entrez votre mot de passe actuel pour changer les paramètres du profil"
#: src/components/settings/DisplaySettings.tsx
msgid "Entries to keep above the selected entry when scrolling"
msgstr ""
msgstr "Nombre d'entrées à conserver au-dessus de l'entrée sélectionnée lors d'un défilement"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
@@ -376,6 +376,10 @@ msgstr "URL API Fever"
msgid "Filtering expression"
msgstr "Expression de filtrage"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr "La récupération forcée des flux n'est pas encore disponible."
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Mot de passe oublié ?"
@@ -614,7 +618,7 @@ msgstr "Sur mobile, afficher les boutons d'action en bas de l'écran"
#: src/components/settings/DisplaySettings.tsx
msgid "Only applies to compact, cozy and detailed modes"
msgstr ""
msgstr "Ne fonctionne que dans les modes Compact, Cozy, et Vue détaillée"
#: src/pages/ErrorPage.tsx
msgid "Oops!"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Expresión de filtrado"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Esqueceches o contrasinal?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Szűrő kifejezés"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Elfelejtette a jelszavát?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Memfilter ekspresi"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Lupa kata sandi?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Espressione filtrante"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Password dimenticata?"

View File

@@ -58,7 +58,7 @@ msgstr "ユーザー追加"
#: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx
msgid "Admin"
msgstr "管理"
msgstr "管理"
#: src/app/constants.ts
#: src/components/content/add/CategorySelect.tsx
@@ -323,7 +323,7 @@ msgstr "プロファイル設定を変更するには、現在のパスワード
#: src/components/settings/DisplaySettings.tsx
msgid "Entries to keep above the selected entry when scrolling"
msgstr ""
msgstr "エントリーを選択したとき、読みやすさに応じたスクロール調整を行います。"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
@@ -376,6 +376,10 @@ msgstr "Fever API URL"
msgid "Filtering expression"
msgstr "フィルタリング式"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr "フィードの強制フェッチはまだ利用できません。"
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "パスワードをお忘れですか?"
@@ -398,7 +402,7 @@ msgstr "生成されたフィードURL"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr "Go to {0}"
msgstr "{0} に移動"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Go to the All view"
@@ -418,11 +422,11 @@ msgstr "ID"
#: src/pages/app/FeedDetailsPage.tsx
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "空でない場合は、'true' または 'false' に評価される式。 'false' の場合、このフィードの新しいエントリは自動的に既読としてマークされます。"
msgstr "空でない場合は、'true' または 'false' に評価される式。 'false' の場合、このフィードの新しいエントリは自動的に既読としてマークされます。"
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr "エントリが画面に完全に収まらない場合"
msgstr "エントリが画面に完全に収まらない場合"
#: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
@@ -614,7 +618,7 @@ msgstr "モバイルでは、画面の下部にアクションボタンを表示
#: src/components/settings/DisplaySettings.tsx
msgid "Only applies to compact, cozy and detailed modes"
msgstr ""
msgstr "これはコンパクト/cozy/詳細モードでのみ適用されます"
#: src/pages/ErrorPage.tsx
msgid "Oops!"
@@ -751,7 +755,7 @@ msgstr "保存"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr "選択されたエントリーをページの上部にスクロールする"
msgstr "エントリーを選択したときのスクロール調整"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries"
@@ -774,11 +778,11 @@ msgstr "検索には少なくとも3文字が必要です"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Set focus on next entry without opening it"
msgstr "次のエントリーを開かずにフォーカスを設定する"
msgstr "次のエントリーを開かずにフォーカスする"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Set focus on previous entry without opening it"
msgstr "前のエントリーを開かずにフォーカスを設定する"
msgstr "前のエントリーを開かずにフォーカスする"
#: src/components/header/ProfileMenu.tsx
msgid "Settings"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "필터링 표현식"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "비밀번호를 잊으셨나요?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Ungkapan penapisan"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Lupa kata laluan?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filtrerende uttrykk"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Glemt passord?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Uitdrukking filteren"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Wachtwoord vergeten?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filtrerende uttrykk"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Glemt passord?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Wyrażenie filtrujące"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Zapomniałeś hasła?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filtrando expressão"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Esqueceu a senha?"

View File

@@ -376,6 +376,10 @@ msgstr "Ссылка Fever API"
msgid "Filtering expression"
msgstr "Выражение фильтрации"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Забыли пароль?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filtrovanie výrazu"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Zabudli ste heslo?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filtrerande uttryck"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Glömt lösenord?"

View File

@@ -376,6 +376,10 @@ msgstr ""
msgid "Filtering expression"
msgstr "Filtreleme ifadesi"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "Parolanızı mı unuttunuz?"

View File

@@ -376,6 +376,10 @@ msgstr "Fever API 网址"
msgid "Filtering expression"
msgstr "过滤表达式"
#: src/components/header/ProfileMenu.tsx
msgid "Force fetching feeds is not yet available."
msgstr ""
#: src/pages/auth/LoginPage.tsx
msgid "Forgot password?"
msgstr "忘记密码?"

View File

@@ -6,11 +6,13 @@ import "react-contexify/ReactContexify.css"
import { App } from "App"
import { store } from "app/store"
import dayjs from "dayjs"
import duration from "dayjs/plugin/duration"
import relativeTime from "dayjs/plugin/relativeTime"
import ReactDOM from "react-dom/client"
import { Provider } from "react-redux"
dayjs.extend(relativeTime)
dayjs.extend(duration)
const root = document.getElementById("root")
root &&

View File

@@ -8,7 +8,7 @@ h|[.header-title]##Configuration property##
h|Type
h|Default
a| [[commafeed-server_commafeed-hide-from-web-crawlers]] [.property-path]##`commafeed.hide-from-web-crawlers`##
a| [[commafeed-server_commafeed-hide-from-web-crawlers]] [.property-path]##link:#commafeed-server_commafeed-hide-from-web-crawlers[`commafeed.hide-from-web-crawlers`]##
[.description]
--
@@ -25,7 +25,7 @@ endif::add-copy-button-to-env-var[]
|boolean
|`true`
a| [[commafeed-server_commafeed-image-proxy-enabled]] [.property-path]##`commafeed.image-proxy-enabled`##
a| [[commafeed-server_commafeed-image-proxy-enabled]] [.property-path]##link:#commafeed-server_commafeed-image-proxy-enabled[`commafeed.image-proxy-enabled`]##
[.description]
--
@@ -42,7 +42,7 @@ endif::add-copy-button-to-env-var[]
|boolean
|`false`
a| [[commafeed-server_commafeed-password-recovery-enabled]] [.property-path]##`commafeed.password-recovery-enabled`##
a| [[commafeed-server_commafeed-password-recovery-enabled]] [.property-path]##link:#commafeed-server_commafeed-password-recovery-enabled[`commafeed.password-recovery-enabled`]##
[.description]
--
@@ -59,7 +59,7 @@ endif::add-copy-button-to-env-var[]
|boolean
|`false`
a| [[commafeed-server_commafeed-announcement]] [.property-path]##`commafeed.announcement`##
a| [[commafeed-server_commafeed-announcement]] [.property-path]##link:#commafeed-server_commafeed-announcement[`commafeed.announcement`]##
[.description]
--
@@ -76,7 +76,7 @@ endif::add-copy-button-to-env-var[]
|string
|
a| [[commafeed-server_commafeed-google-analytics-tracking-code]] [.property-path]##`commafeed.google-analytics-tracking-code`##
a| [[commafeed-server_commafeed-google-analytics-tracking-code]] [.property-path]##link:#commafeed-server_commafeed-google-analytics-tracking-code[`commafeed.google-analytics-tracking-code`]##
[.description]
--
@@ -93,7 +93,7 @@ endif::add-copy-button-to-env-var[]
|string
|
a| [[commafeed-server_commafeed-google-auth-key]] [.property-path]##`commafeed.google-auth-key`##
a| [[commafeed-server_commafeed-google-auth-key]] [.property-path]##link:#commafeed-server_commafeed-google-auth-key[`commafeed.google-auth-key`]##
[.description]
--
@@ -110,11 +110,11 @@ endif::add-copy-button-to-env-var[]
|string
|
h|[[commafeed-server_section_commafeed-http-client]] [.section-name.section-level0]##HTTP client configuration##
h|[[commafeed-server_section_commafeed-http-client]] [.section-name.section-level0]##link:#commafeed-server_section_commafeed-http-client[HTTP client configuration]##
h|Type
h|Default
a| [[commafeed-server_commafeed-http-client-user-agent]] [.property-path]##`commafeed.http-client.user-agent`##
a| [[commafeed-server_commafeed-http-client-user-agent]] [.property-path]##link:#commafeed-server_commafeed-http-client-user-agent[`commafeed.http-client.user-agent`]##
[.description]
--
@@ -131,7 +131,7 @@ endif::add-copy-button-to-env-var[]
|string
|
a| [[commafeed-server_commafeed-http-client-connect-timeout]] [.property-path]##`commafeed.http-client.connect-timeout`##
a| [[commafeed-server_commafeed-http-client-connect-timeout]] [.property-path]##link:#commafeed-server_commafeed-http-client-connect-timeout[`commafeed.http-client.connect-timeout`]##
[.description]
--
@@ -148,7 +148,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`5S`
a| [[commafeed-server_commafeed-http-client-ssl-handshake-timeout]] [.property-path]##`commafeed.http-client.ssl-handshake-timeout`##
a| [[commafeed-server_commafeed-http-client-ssl-handshake-timeout]] [.property-path]##link:#commafeed-server_commafeed-http-client-ssl-handshake-timeout[`commafeed.http-client.ssl-handshake-timeout`]##
[.description]
--
@@ -165,7 +165,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`5S`
a| [[commafeed-server_commafeed-http-client-socket-timeout]] [.property-path]##`commafeed.http-client.socket-timeout`##
a| [[commafeed-server_commafeed-http-client-socket-timeout]] [.property-path]##link:#commafeed-server_commafeed-http-client-socket-timeout[`commafeed.http-client.socket-timeout`]##
[.description]
--
@@ -182,7 +182,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`10S`
a| [[commafeed-server_commafeed-http-client-response-timeout]] [.property-path]##`commafeed.http-client.response-timeout`##
a| [[commafeed-server_commafeed-http-client-response-timeout]] [.property-path]##link:#commafeed-server_commafeed-http-client-response-timeout[`commafeed.http-client.response-timeout`]##
[.description]
--
@@ -199,7 +199,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`10S`
a| [[commafeed-server_commafeed-http-client-connection-time-to-live]] [.property-path]##`commafeed.http-client.connection-time-to-live`##
a| [[commafeed-server_commafeed-http-client-connection-time-to-live]] [.property-path]##link:#commafeed-server_commafeed-http-client-connection-time-to-live[`commafeed.http-client.connection-time-to-live`]##
[.description]
--
@@ -216,7 +216,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`30S`
a| [[commafeed-server_commafeed-http-client-idle-connections-eviction-interval]] [.property-path]##`commafeed.http-client.idle-connections-eviction-interval`##
a| [[commafeed-server_commafeed-http-client-idle-connections-eviction-interval]] [.property-path]##link:#commafeed-server_commafeed-http-client-idle-connections-eviction-interval[`commafeed.http-client.idle-connections-eviction-interval`]##
[.description]
--
@@ -233,7 +233,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`1M`
a| [[commafeed-server_commafeed-http-client-max-response-size]] [.property-path]##`commafeed.http-client.max-response-size`##
a| [[commafeed-server_commafeed-http-client-max-response-size]] [.property-path]##link:#commafeed-server_commafeed-http-client-max-response-size[`commafeed.http-client.max-response-size`]##
[.description]
--
@@ -250,11 +250,11 @@ endif::add-copy-button-to-env-var[]
|MemorySize link:#memory-size-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the MemorySize format]]
|`5M`
h|[[commafeed-server_section_commafeed-http-client-cache]] [.section-name.section-level1]##HTTP client cache configuration##
h|[[commafeed-server_section_commafeed-http-client-cache]] [.section-name.section-level1]##link:#commafeed-server_section_commafeed-http-client-cache[HTTP client cache configuration]##
h|Type
h|Default
a| [[commafeed-server_commafeed-http-client-cache-enabled]] [.property-path]##`commafeed.http-client.cache.enabled`##
a| [[commafeed-server_commafeed-http-client-cache-enabled]] [.property-path]##link:#commafeed-server_commafeed-http-client-cache-enabled[`commafeed.http-client.cache.enabled`]##
[.description]
--
@@ -271,7 +271,7 @@ endif::add-copy-button-to-env-var[]
|boolean
|`true`
a| [[commafeed-server_commafeed-http-client-cache-maximum-memory-size]] [.property-path]##`commafeed.http-client.cache.maximum-memory-size`##
a| [[commafeed-server_commafeed-http-client-cache-maximum-memory-size]] [.property-path]##link:#commafeed-server_commafeed-http-client-cache-maximum-memory-size[`commafeed.http-client.cache.maximum-memory-size`]##
[.description]
--
@@ -288,7 +288,7 @@ endif::add-copy-button-to-env-var[]
|MemorySize link:#memory-size-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the MemorySize format]]
|`10M`
a| [[commafeed-server_commafeed-http-client-cache-expiration]] [.property-path]##`commafeed.http-client.cache.expiration`##
a| [[commafeed-server_commafeed-http-client-cache-expiration]] [.property-path]##link:#commafeed-server_commafeed-http-client-cache-expiration[`commafeed.http-client.cache.expiration`]##
[.description]
--
@@ -307,11 +307,11 @@ endif::add-copy-button-to-env-var[]
h|[[commafeed-server_section_commafeed-feed-refresh]] [.section-name.section-level0]##Feed refresh engine settings##
h|[[commafeed-server_section_commafeed-feed-refresh]] [.section-name.section-level0]##link:#commafeed-server_section_commafeed-feed-refresh[Feed refresh engine settings]##
h|Type
h|Default
a| [[commafeed-server_commafeed-feed-refresh-interval]] [.property-path]##`commafeed.feed-refresh.interval`##
a| [[commafeed-server_commafeed-feed-refresh-interval]] [.property-path]##link:#commafeed-server_commafeed-feed-refresh-interval[`commafeed.feed-refresh.interval`]##
[.description]
--
@@ -328,7 +328,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`5M`
a| [[commafeed-server_commafeed-feed-refresh-interval-empirical]] [.property-path]##`commafeed.feed-refresh.interval-empirical`##
a| [[commafeed-server_commafeed-feed-refresh-interval-empirical]] [.property-path]##link:#commafeed-server_commafeed-feed-refresh-interval-empirical[`commafeed.feed-refresh.interval-empirical`]##
[.description]
--
@@ -345,7 +345,7 @@ endif::add-copy-button-to-env-var[]
|boolean
|`false`
a| [[commafeed-server_commafeed-feed-refresh-http-threads]] [.property-path]##`commafeed.feed-refresh.http-threads`##
a| [[commafeed-server_commafeed-feed-refresh-http-threads]] [.property-path]##link:#commafeed-server_commafeed-feed-refresh-http-threads[`commafeed.feed-refresh.http-threads`]##
[.description]
--
@@ -362,7 +362,7 @@ endif::add-copy-button-to-env-var[]
|int
|`3`
a| [[commafeed-server_commafeed-feed-refresh-database-threads]] [.property-path]##`commafeed.feed-refresh.database-threads`##
a| [[commafeed-server_commafeed-feed-refresh-database-threads]] [.property-path]##link:#commafeed-server_commafeed-feed-refresh-database-threads[`commafeed.feed-refresh.database-threads`]##
[.description]
--
@@ -379,7 +379,7 @@ endif::add-copy-button-to-env-var[]
|int
|`1`
a| [[commafeed-server_commafeed-feed-refresh-user-inactivity-period]] [.property-path]##`commafeed.feed-refresh.user-inactivity-period`##
a| [[commafeed-server_commafeed-feed-refresh-user-inactivity-period]] [.property-path]##link:#commafeed-server_commafeed-feed-refresh-user-inactivity-period[`commafeed.feed-refresh.user-inactivity-period`]##
[.description]
--
@@ -396,7 +396,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`0S`
a| [[commafeed-server_commafeed-feed-refresh-filtering-expression-evaluation-timeout]] [.property-path]##`commafeed.feed-refresh.filtering-expression-evaluation-timeout`##
a| [[commafeed-server_commafeed-feed-refresh-filtering-expression-evaluation-timeout]] [.property-path]##link:#commafeed-server_commafeed-feed-refresh-filtering-expression-evaluation-timeout[`commafeed.feed-refresh.filtering-expression-evaluation-timeout`]##
[.description]
--
@@ -413,12 +413,29 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`500MS`
a| [[commafeed-server_commafeed-feed-refresh-force-refresh-cooldown-duration]] [.property-path]##link:#commafeed-server_commafeed-feed-refresh-force-refresh-cooldown-duration[`commafeed.feed-refresh.force-refresh-cooldown-duration`]##
h|[[commafeed-server_section_commafeed-database]] [.section-name.section-level0]##Database settings##
[.description]
--
Duration after which the "Fetch all my feeds now" action is available again after use to avoid spamming feeds.
ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++COMMAFEED_FEED_REFRESH_FORCE_REFRESH_COOLDOWN_DURATION+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++COMMAFEED_FEED_REFRESH_FORCE_REFRESH_COOLDOWN_DURATION+++`
endif::add-copy-button-to-env-var[]
--
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`0S`
h|[[commafeed-server_section_commafeed-database]] [.section-name.section-level0]##link:#commafeed-server_section_commafeed-database[Database settings]##
h|Type
h|Default
a| [[commafeed-server_commafeed-database-query-timeout]] [.property-path]##`commafeed.database.query-timeout`##
a| [[commafeed-server_commafeed-database-query-timeout]] [.property-path]##link:#commafeed-server_commafeed-database-query-timeout[`commafeed.database.query-timeout`]##
[.description]
--
@@ -435,11 +452,11 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`0S`
h|[[commafeed-server_section_commafeed-database-cleanup]] [.section-name.section-level1]##Database cleanup settings##
h|[[commafeed-server_section_commafeed-database-cleanup]] [.section-name.section-level1]##link:#commafeed-server_section_commafeed-database-cleanup[Database cleanup settings]##
h|Type
h|Default
a| [[commafeed-server_commafeed-database-cleanup-entries-max-age]] [.property-path]##`commafeed.database.cleanup.entries-max-age`##
a| [[commafeed-server_commafeed-database-cleanup-entries-max-age]] [.property-path]##link:#commafeed-server_commafeed-database-cleanup-entries-max-age[`commafeed.database.cleanup.entries-max-age`]##
[.description]
--
@@ -456,7 +473,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`365D`
a| [[commafeed-server_commafeed-database-cleanup-statuses-max-age]] [.property-path]##`commafeed.database.cleanup.statuses-max-age`##
a| [[commafeed-server_commafeed-database-cleanup-statuses-max-age]] [.property-path]##link:#commafeed-server_commafeed-database-cleanup-statuses-max-age[`commafeed.database.cleanup.statuses-max-age`]##
[.description]
--
@@ -473,7 +490,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`0S`
a| [[commafeed-server_commafeed-database-cleanup-max-feed-capacity]] [.property-path]##`commafeed.database.cleanup.max-feed-capacity`##
a| [[commafeed-server_commafeed-database-cleanup-max-feed-capacity]] [.property-path]##link:#commafeed-server_commafeed-database-cleanup-max-feed-capacity[`commafeed.database.cleanup.max-feed-capacity`]##
[.description]
--
@@ -490,7 +507,7 @@ endif::add-copy-button-to-env-var[]
|int
|`500`
a| [[commafeed-server_commafeed-database-cleanup-max-feeds-per-user]] [.property-path]##`commafeed.database.cleanup.max-feeds-per-user`##
a| [[commafeed-server_commafeed-database-cleanup-max-feeds-per-user]] [.property-path]##link:#commafeed-server_commafeed-database-cleanup-max-feeds-per-user[`commafeed.database.cleanup.max-feeds-per-user`]##
[.description]
--
@@ -507,7 +524,7 @@ endif::add-copy-button-to-env-var[]
|int
|`0`
a| [[commafeed-server_commafeed-database-cleanup-batch-size]] [.property-path]##`commafeed.database.cleanup.batch-size`##
a| [[commafeed-server_commafeed-database-cleanup-batch-size]] [.property-path]##link:#commafeed-server_commafeed-database-cleanup-batch-size[`commafeed.database.cleanup.batch-size`]##
[.description]
--
@@ -526,11 +543,11 @@ endif::add-copy-button-to-env-var[]
h|[[commafeed-server_section_commafeed-users]] [.section-name.section-level0]##Users settings##
h|[[commafeed-server_section_commafeed-users]] [.section-name.section-level0]##link:#commafeed-server_section_commafeed-users[Users settings]##
h|Type
h|Default
a| [[commafeed-server_commafeed-users-allow-registrations]] [.property-path]##`commafeed.users.allow-registrations`##
a| [[commafeed-server_commafeed-users-allow-registrations]] [.property-path]##link:#commafeed-server_commafeed-users-allow-registrations[`commafeed.users.allow-registrations`]##
[.description]
--
@@ -547,7 +564,7 @@ endif::add-copy-button-to-env-var[]
|boolean
|`false`
a| [[commafeed-server_commafeed-users-strict-password-policy]] [.property-path]##`commafeed.users.strict-password-policy`##
a| [[commafeed-server_commafeed-users-strict-password-policy]] [.property-path]##link:#commafeed-server_commafeed-users-strict-password-policy[`commafeed.users.strict-password-policy`]##
[.description]
--
@@ -564,7 +581,7 @@ endif::add-copy-button-to-env-var[]
|boolean
|`true`
a| [[commafeed-server_commafeed-users-create-demo-account]] [.property-path]##`commafeed.users.create-demo-account`##
a| [[commafeed-server_commafeed-users-create-demo-account]] [.property-path]##link:#commafeed-server_commafeed-users-create-demo-account[`commafeed.users.create-demo-account`]##
[.description]
--
@@ -582,11 +599,11 @@ endif::add-copy-button-to-env-var[]
|`false`
h|[[commafeed-server_section_commafeed-websocket]] [.section-name.section-level0]##Websocket settings##
h|[[commafeed-server_section_commafeed-websocket]] [.section-name.section-level0]##link:#commafeed-server_section_commafeed-websocket[Websocket settings]##
h|Type
h|Default
a| [[commafeed-server_commafeed-websocket-enabled]] [.property-path]##`commafeed.websocket.enabled`##
a| [[commafeed-server_commafeed-websocket-enabled]] [.property-path]##link:#commafeed-server_commafeed-websocket-enabled[`commafeed.websocket.enabled`]##
[.description]
--
@@ -603,7 +620,7 @@ endif::add-copy-button-to-env-var[]
|boolean
|`true`
a| [[commafeed-server_commafeed-websocket-ping-interval]] [.property-path]##`commafeed.websocket.ping-interval`##
a| [[commafeed-server_commafeed-websocket-ping-interval]] [.property-path]##link:#commafeed-server_commafeed-websocket-ping-interval[`commafeed.websocket.ping-interval`]##
[.description]
--
@@ -620,7 +637,7 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-{summaryTableId}[icon:question-circle[title=More information about the Duration format]]
|`15M`
a| [[commafeed-server_commafeed-websocket-tree-reload-interval]] [.property-path]##`commafeed.websocket.tree-reload-interval`##
a| [[commafeed-server_commafeed-websocket-tree-reload-interval]] [.property-path]##link:#commafeed-server_commafeed-websocket-tree-reload-interval[`commafeed.websocket.tree-reload-interval`]##
[.description]
--

View File

@@ -6,16 +6,16 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>5.2.0</version>
<version>5.3.4</version>
</parent>
<artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name>
<properties>
<quarkus.version>3.14.4</quarkus.version>
<querydsl.version>6.7</querydsl.version>
<quarkus.version>3.15.1</quarkus.version>
<querydsl.version>6.8</querydsl.version>
<rome.version>2.1.0</rome.version>
<swagger.version>2.2.23</swagger.version>
<swagger.version>2.2.25</swagger.version>
<build.database>h2</build.database>
</properties>
@@ -43,7 +43,7 @@
<plugins>
<plugin>
<artifactId>maven-help-plugin</artifactId>
<version>3.5.0</version>
<version>3.5.1</version>
<executions>
<execution>
<phase>initialize</phase>
@@ -135,7 +135,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
<version>3.5.1</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
@@ -146,7 +146,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.0</version>
<version>3.5.1</version>
<executions>
<execution>
<goals>
@@ -238,7 +238,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.18.1</version>
<version>10.18.2</version>
</dependency>
</dependencies>
<executions>
@@ -294,7 +294,7 @@
<dependency>
<groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId>
<version>5.2.0</version>
<version>5.3.4</version>
</dependency>
<!-- compile-time processors -->
@@ -358,7 +358,7 @@
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-json</artifactId>
<version>4.2.27</version>
<version>4.2.28</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
@@ -405,7 +405,7 @@
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>1.6.5</version>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>com.rometools</groupId>
@@ -450,7 +450,7 @@
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
<version>5.4</version>
</dependency>
<!-- add brotli support for httpclient5 -->
<dependency>
@@ -461,7 +461,7 @@
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-apache5</artifactId>
<version>8.3.6</version>
<version>8.3.7</version>
</dependency>
<!-- test dependencies -->
@@ -489,7 +489,7 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.47.0</version>
<version>1.48.0</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -209,6 +209,12 @@ public interface CommaFeedConfiguration {
*/
@WithDefault("500ms")
Duration filteringExpressionEvaluationTimeout();
/**
* Duration after which the "Fetch all my feeds now" action is available again after use to avoid spamming feeds.
*/
@WithDefault("0")
Duration forceRefreshCooldownDuration();
}
interface Database {

View File

@@ -18,7 +18,8 @@ public class JacksonCustomizer implements ObjectMapperCustomizer {
objectMapper.registerModule(new JavaTimeModule());
// read and write instants as milliseconds instead of nanoseconds
objectMapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)
.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
// add support for serializing metrics

View File

@@ -2,7 +2,10 @@ package com.commafeed.backend;
import java.io.IOException;
import java.io.InputStream;
import java.net.IDN;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -10,6 +13,8 @@ import java.util.Optional;
import java.util.concurrent.ExecutionException;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.config.TlsConfig;
@@ -126,7 +131,13 @@ public class HttpGetter {
log.debug("fetching {}", request.getUrl());
HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(RequestConfig.custom().setResponseTimeout(Timeout.of(config.httpClient().responseTimeout())).build());
context.setRequestConfig(RequestConfig.custom()
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
// causes issues with some feeds
// see https://github.com/Athou/commafeed/issues/1572
// and https://issues.apache.org/jira/browse/HTTPCLIENT-2344
.setProtocolUpgradeEnabled(false)
.build());
return client.execute(request.toClassicHttpRequest(), context, resp -> {
byte[] content = resp.getEntity() == null ? null
@@ -176,7 +187,7 @@ public class HttpGetter {
int poolSize = config.feedRefresh().httpThreads();
return PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(Apache5SslUtils.toSocketFactory(sslFactory))
.setTlsSocketStrategy(Apache5SslUtils.toTlsSocketStrategy(sslFactory))
.setDefaultConnectionConfig(ConnectionConfig.custom()
.setConnectTimeout(Timeout.of(config.httpClient().connectTimeout()))
.setSocketTimeout(Timeout.of(config.httpClient().socketTimeout()))
@@ -185,6 +196,7 @@ public class HttpGetter {
.setDefaultTlsConfig(TlsConfig.custom().setHandshakeTimeout(Timeout.of(config.httpClient().sslHandshakeTimeout())).build())
.setMaxConnPerRoute(poolSize)
.setMaxConnTotal(poolSize)
.setDnsResolver(new InternationalizedDomainNameToAsciiDnsResolver(SystemDefaultDnsResolver.INSTANCE))
.build();
}
@@ -221,6 +233,18 @@ public class HttpGetter {
.build();
}
private record InternationalizedDomainNameToAsciiDnsResolver(DnsResolver delegate) implements DnsResolver {
@Override
public InetAddress[] resolve(String host) throws UnknownHostException {
return delegate.resolve(IDN.toASCII(host));
}
@Override
public String resolveCanonicalHostname(String host) throws UnknownHostException {
return delegate.resolveCanonicalHostname(IDN.toASCII(host));
}
}
@Getter
public static class NotModifiedException extends Exception {
private static final long serialVersionUID = 1L;

View File

@@ -52,4 +52,7 @@ public class User extends AbstractModel {
@Column
private Instant recoverPasswordTokenDate;
@Column
private Instant lastForceRefresh;
}

View File

@@ -98,12 +98,19 @@ public class FeedSubscriptionService {
}
}
public void refreshAll(User user) {
public void refreshAll(User user) throws ForceFeedRefreshTooSoonException {
Instant lastForceRefresh = user.getLastForceRefresh();
if (lastForceRefresh != null && lastForceRefresh.plus(config.feedRefresh().forceRefreshCooldownDuration()).isAfter(Instant.now())) {
throw new ForceFeedRefreshTooSoonException();
}
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
for (FeedSubscription sub : subs) {
Feed feed = sub.getFeed();
feedRefreshEngine.refreshImmediately(feed);
}
user.setLastForceRefresh(Instant.now());
}
public void refreshAllUpForRefresh(User user) {
@@ -130,4 +137,11 @@ public class FeedSubscriptionService {
}
}
@SuppressWarnings("serial")
public static class ForceFeedRefreshTooSoonException extends Exception {
private ForceFeedRefreshTooSoonException() {
super();
}
}
}

View File

@@ -43,4 +43,7 @@ public class ServerInfo implements Serializable {
@Schema(requiredMode = RequiredMode.REQUIRED)
private long treeReloadInterval;
@Schema(requiredMode = RequiredMode.REQUIRED)
private long forceRefreshCooldownDuration;
}

View File

@@ -41,4 +41,7 @@ public class UserModel implements Serializable {
@Schema(description = "user is admin", requiredMode = RequiredMode.REQUIRED)
private boolean admin;
@Schema(description = "user last force refresh", type = "number")
private Instant lastForceRefresh;
}

View File

@@ -10,6 +10,7 @@ import java.util.Objects;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.hc.core5.http.HttpStatus;
import org.jboss.resteasy.reactive.Cache;
import org.jboss.resteasy.reactive.RestForm;
@@ -40,6 +41,7 @@ import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterEx
import com.commafeed.backend.service.FeedEntryService;
import com.commafeed.backend.service.FeedService;
import com.commafeed.backend.service.FeedSubscriptionService;
import com.commafeed.backend.service.FeedSubscriptionService.ForceFeedRefreshTooSoonException;
import com.commafeed.frontend.model.Entries;
import com.commafeed.frontend.model.Entry;
import com.commafeed.frontend.model.FeedInfo;
@@ -276,8 +278,13 @@ public class FeedREST {
@Operation(summary = "Queue all feeds of the user for refresh", description = "Manually add all feeds of the user to the refresh queue")
public Response queueAllForRefresh() {
User user = authenticationContext.getCurrentUser();
feedSubscriptionService.refreshAll(user);
return Response.ok().build();
try {
feedSubscriptionService.refreshAll(user);
return Response.ok().build();
} catch (ForceFeedRefreshTooSoonException e) {
return Response.status(HttpStatus.SC_TOO_MANY_REQUESTS).build();
}
}
@Path("/refresh")

View File

@@ -61,6 +61,7 @@ public class ServerREST {
infos.setWebsocketEnabled(config.websocket().enabled());
infos.setWebsocketPingInterval(config.websocket().pingInterval().toMillis());
infos.setTreeReloadInterval(config.websocket().treeReloadInterval().toMillis());
infos.setForceRefreshCooldownDuration(config.feedRefresh().forceRefreshCooldownDuration().toMillis());
return Response.ok(infos).build();
}

View File

@@ -212,6 +212,7 @@ public class UserREST {
userModel.setEmail(user.getEmail());
userModel.setEnabled(!user.isDisabled());
userModel.setApiKey(user.getApiKey());
userModel.setLastForceRefresh(user.getLastForceRefresh());
for (UserRole role : userRoleDAO.findAll(user)) {
if (role.getRole() == Role.ADMIN) {
userModel.setAdmin(true);

View File

@@ -49,6 +49,7 @@ quarkus.native.add-all-charsets=true
%test.commafeed.users.allow-registrations=true
%test.commafeed.password-recovery-enabled=true
%test.commafeed.http-client.cache.enabled=false
%test.commafeed.feed-refresh.force-refresh-cooldown-duration=1m
# prod profile overrides

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="lastForceRefresh" author="athou">
<addColumn tableName="USERS">
<column name="lastForceRefresh" type="${timestamp_type}" />
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -33,5 +33,6 @@
<include file="changelogs/db.changelog-4.4.xml" />
<include file="changelogs/db.changelog-5.1.xml" />
<include file="changelogs/db.changelog-5.2.xml" />
<include file="changelogs/db.changelog-5.3.xml" />
</databaseChangeLog>

View File

@@ -239,6 +239,24 @@ class HttpGetterTest {
Assertions.assertEquals("ok", new String(result.getContent()));
}
@Test
void doesNotUseUpgradeProtocolHeader() {
AtomicInteger calls = new AtomicInteger();
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
calls.incrementAndGet();
if (req.containsHeader(HttpHeaders.UPGRADE)) {
throw new Exception("upgrade header should not be sent by the client");
}
return HttpResponse.response().withBody("ok");
});
Assertions.assertDoesNotThrow(() -> getter.get(this.feedUrl));
Assertions.assertEquals(1, calls.get());
}
@Nested
class Compression {

View File

@@ -125,7 +125,7 @@ public abstract class BaseIT {
.as(Entries.class);
}
protected void forceRefreshAllFeeds() {
RestAssured.given().get("rest/feed/refreshAll").then().statusCode(HttpStatus.SC_OK);
protected int forceRefreshAllFeeds() {
return RestAssured.given().get("rest/feed/refreshAll").then().extract().statusCode();
}
}

View File

@@ -183,11 +183,13 @@ class FeedIT extends BaseIT {
// mariadb/mysql timestamp precision is 1 second
Instant threshold = Instant.now().minus(Duration.ofSeconds(1));
forceRefreshAllFeeds();
Assertions.assertEquals(HttpStatus.SC_OK, forceRefreshAllFeeds());
Awaitility.await()
.atMost(Duration.ofSeconds(15))
.until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(threshold));
Assertions.assertEquals(HttpStatus.SC_TOO_MANY_REQUESTS, forceRefreshAllFeeds());
}
}

View File

@@ -21,6 +21,7 @@ class ServerIT extends BaseIT {
Assertions.assertTrue(serverInfos.isWebsocketEnabled());
Assertions.assertEquals(900000, serverInfos.getWebsocketPingInterval());
Assertions.assertEquals(30000, serverInfos.getTreeReloadInterval());
Assertions.assertEquals(60000, serverInfos.getForceRefreshCooldownDuration());
}
}

View File

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