Compare commits

..

224 Commits

Author SHA1 Message Date
Athou
8146c69ebf release 5.12.0 2025-11-20 06:58:55 +01:00
renovate[bot]
78ece1abf2 chore(deps): update dependency npm to v11.6.3 2025-11-20 01:58:00 +00:00
renovate[bot]
baab35c4c5 fix(deps): update quarkus.version to v3.29.4 2025-11-19 21:01:43 +00:00
Athou
357f9d46f9 enable the disablePullToRefresh setting by default to mimic what was done before the setting was added 2025-11-19 15:10:29 +01:00
Jérémie Panzer
4eb26302a7 Merge pull request #1967 from Athou/renovate/com.diffplug.spotless-spotless-maven-plugin-3.x
chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v3.1.0
2025-11-19 09:06:06 +01:00
renovate[bot]
a2071d9527 chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v3.1.0 2025-11-19 01:30:34 +00:00
Jérémie Panzer
65c32c52ff Merge pull request #1966 from Athou/renovate/debian-13.x
chore(deps): update debian docker tag to v13.2
2025-11-18 08:08:02 +01:00
renovate[bot]
fa4353f47d chore(deps): update debian docker tag to v13.2 2025-11-18 06:13:55 +00:00
renovate[bot]
46fea1a3e5 chore(deps): update dependency vitest to ^4.0.10 2025-11-18 04:25:51 +00:00
renovate[bot]
497cf111d1 chore(deps): update dependency @types/react to ^19.2.6 2025-11-18 03:19:16 +00:00
renovate[bot]
b1f2fd26e3 chore(deps): update actions/checkout digest to 93cb6ef 2025-11-17 22:43:16 +00:00
renovate[bot]
ae60d4a60f chore(deps): update dependency @biomejs/biome to v2.3.6 2025-11-17 11:13:56 +00:00
Athou
ae78e4691d make "disable pull to refresh" a setting (#1168) 2025-11-17 09:02:52 +01:00
Athou
9c058cf6d6 disable xml entity expansion limits enabled in JDK24+ (#1961) 2025-11-17 06:43:55 +01:00
renovate[bot]
1ac9af23c5 chore(deps): lock file maintenance 2025-11-17 02:41:41 +00:00
renovate[bot]
f783bb660e fix(deps): update dependency style-to-object to ^1.0.14 2025-11-16 12:42:51 +00:00
Athou
e5c271ca1c add support for more emojis (#1955) 2025-11-16 09:37:36 +01:00
renovate[bot]
f927247955 fix(deps): update mantine monorepo to ^8.3.8 2025-11-15 13:36:11 +00:00
renovate[bot]
087e38bec8 chore(deps): update dependency @types/react to ^19.2.5 2025-11-14 23:57:15 +00:00
renovate[bot]
bab3c8e6b0 fix(deps): update quarkus.version to v3.29.3 2025-11-14 16:35:26 +00:00
renovate[bot]
54ac5d9e27 chore(deps): update dependency vitest to ^4.0.9 2025-11-14 10:05:26 +00:00
renovate[bot]
36519d9053 chore(deps): update ibm-semeru-runtimes:open-jdk-25.0.1_8-jre docker digest to 015afe2 2025-11-14 05:49:21 +00:00
renovate[bot]
ccce4c622d fix(deps): update dependency react-router-dom to ^7.9.6 2025-11-13 21:02:11 +00:00
renovate[bot]
4cbf677e55 chore(deps): update react monorepo 2025-11-12 22:05:12 +00:00
renovate[bot]
1dbac44a93 chore(deps): update dependency @vitejs/plugin-react to ^5.1.1 2025-11-12 13:33:44 +00:00
Jérémie Panzer
7e1cfb5cd2 Merge pull request #1965 from Athou/renovate/linguijs-monorepo
fix(deps): update linguijs monorepo to ^5.6.0 (minor)
2025-11-12 22:32:10 +09:00
renovate[bot]
df9fb956fa fix(deps): update linguijs monorepo to ^5.6.0 2025-11-12 11:35:12 +00:00
Jérémie Panzer
16dc383f2b Merge pull request #1964 from Athou/renovate/jsdom-27.x
chore(deps): update dependency jsdom to ^27.2.0
2025-11-12 20:33:53 +09:00
Jérémie Panzer
0dd7c4851b Merge pull request #1963 from Athou/renovate/com.puppycrawl.tools-checkstyle-12.1.x
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v12.1.2
2025-11-12 20:33:28 +09:00
renovate[bot]
fce4e75eef chore(deps): update dependency jsdom to ^27.2.0 2025-11-12 09:09:24 +00:00
renovate[bot]
16b578a76d chore(deps): update dependency com.puppycrawl.tools:checkstyle to v12.1.2 2025-11-12 09:09:16 +00:00
renovate[bot]
483db9881e chore(deps): update node.js to v24.11.1 2025-11-12 02:53:15 +00:00
renovate[bot]
a4053c6084 chore(deps): update dependency @types/react to ^19.2.3 2025-11-11 23:57:09 +00:00
renovate[bot]
e4f4b46047 chore(deps): update dependency @biomejs/biome to v2.3.5 2025-11-11 18:32:41 +00:00
Jérémie Panzer
36f77d5408 Merge pull request #1962 from Athou/renovate/org.sonarsource.scanner.maven-sonar-maven-plugin-5.x
chore(deps): update dependency org.sonarsource.scanner.maven:sonar-maven-plugin to v5.3.0.6276
2025-11-11 06:43:39 +09:00
renovate[bot]
b3533771dc chore(deps): update dependency org.sonarsource.scanner.maven:sonar-maven-plugin to v5.3.0.6276 2025-11-10 19:41:32 +00:00
renovate[bot]
45372cba92 chore(deps): lock file maintenance 2025-11-10 01:01:27 +00:00
renovate[bot]
dd7fb5bb0d fix(deps): update mantine monorepo to ^8.3.7 2025-11-09 13:53:33 +00:00
renovate[bot]
41bdc19a22 chore(deps): update dependency io.quarkus.platform:quarkus-maven-plugin to v3.29.2 2025-11-08 17:02:37 +00:00
renovate[bot]
8b7f22021a chore(deps): update dependency vitest to ^4.0.8 2025-11-07 16:13:57 +00:00
renovate[bot]
f0160e4d2b chore(deps): update dependency vite to ^7.2.2 2025-11-07 10:47:20 +00:00
renovate[bot]
39d727f98f fix(deps): update quarkus.version to v3.29.1 2025-11-06 22:57:39 +00:00
renovate[bot]
13cc8ac70d chore(deps): update dependency vite to ^7.2.1 (#1960)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 18:07:54 +00:00
renovate[bot]
eb2a219ec8 fix(deps): update dependency axios to ^1.13.2 2025-11-05 23:07:57 +00:00
renovate[bot]
4a59565b20 chore(deps): update docker/setup-qemu-action digest to c7c5346 2025-11-05 19:42:18 +00:00
renovate[bot]
4b7fa96308 chore(deps): update dependency @biomejs/biome to v2.3.4 2025-11-05 14:44:25 +00:00
Jérémie Panzer
1ebc8a1e7b Merge pull request #1959 from Athou/renovate/vite-7.x
chore(deps): update dependency vite to ^7.2.0
2025-11-05 23:43:30 +09:00
renovate[bot]
df2a9aae20 chore(deps): update dependency vite to ^7.2.0 2025-11-05 13:03:49 +00:00
renovate[bot]
dd8287c9d7 chore(deps): update ibm-semeru-runtimes docker tag to open-jdk-25.0.1_8-jre 2025-11-05 07:06:59 +00:00
renovate[bot]
22fcb08dad fix(deps): update dependency @reduxjs/toolkit to ^2.10.1 2025-11-05 03:04:14 +00:00
renovate[bot]
8c2cf181bd chore(deps): update dependency vitest to ^4.0.7 2025-11-04 21:31:09 +00:00
renovate[bot]
69adae36b6 chore(deps): update debian:13.1 docker digest to 01a723b 2025-11-04 16:45:27 +00:00
Jérémie Panzer
8ab700dfa9 Merge pull request #1957 from Athou/renovate/reduxjs-toolkit-2.x
fix(deps): update dependency @reduxjs/toolkit to ^2.10.0
2025-11-04 10:33:02 +01:00
renovate[bot]
0177529b45 fix(deps): update dependency @reduxjs/toolkit to ^2.10.0 2025-11-04 07:57:22 +00:00
renovate[bot]
4c6ae3364e chore(deps): update dependency @biomejs/biome to v2.3.3 2025-11-03 11:48:29 +00:00
renovate[bot]
6df8511a6d chore(deps): lock file maintenance 2025-11-03 00:39:22 +00:00
renovate[bot]
6fa39517f8 fix(deps): update dependency react-router-dom to ^7.9.5 2025-11-01 01:44:03 +00:00
renovate[bot]
c69ce39424 chore(deps): update dependency vitest to ^4.0.6 2025-10-31 20:23:24 +00:00
Jérémie Panzer
a47f6736ac Merge pull request #1956 from Athou/renovate/jsdom-27.x
chore(deps): update dependency jsdom to ^27.1.0
2025-10-31 21:22:17 +01:00
renovate[bot]
79bd7cfff3 chore(deps): update dependency jsdom to ^27.1.0 2025-10-31 11:51:25 +00:00
renovate[bot]
bc02f23f0f fix(deps): update dependency dayjs to ^1.11.19 2025-10-31 11:51:10 +00:00
renovate[bot]
715dffb6c8 chore(deps): update ibm-semeru-runtimes:open-jdk-25.0.0_36-jre docker digest to 7cee2dc 2025-10-31 04:41:07 +00:00
renovate[bot]
702b3eb971 chore(deps): update dependency vitest to ^4.0.5 2025-10-30 21:52:45 +00:00
Jérémie Panzer
17f62bf491 Merge pull request #1954 from Athou/renovate/com.ibm.icu-icu4j-78.x
fix(deps): update dependency com.ibm.icu:icu4j to v78
2025-10-30 22:51:40 +01:00
renovate[bot]
28471302ee fix(deps): update dependency com.ibm.icu:icu4j to v78 2025-10-30 20:32:35 +00:00
renovate[bot]
d8bfdd5d3b fix(deps): update linguijs monorepo to ^5.5.2 2025-10-30 15:58:30 +00:00
Jérémie Panzer
a36e68e9c3 Merge pull request #1953 from Athou/renovate/quarkus.version
fix(deps): update quarkus.version to v3.29.0 (minor)
2025-10-29 20:46:30 +01:00
renovate[bot]
343aed16fb fix(deps): update quarkus.version to v3.29.0 2025-10-29 17:06:51 +00:00
renovate[bot]
142d873c8b fix(deps): update mantine monorepo to ^8.3.6 2025-10-29 10:10:47 +00:00
renovate[bot]
a94b3e05d3 fix(deps): update dependency axios to ^1.13.1 2025-10-29 03:13:02 +00:00
Jérémie Panzer
26a79d58f0 Merge pull request #1952 from Athou/renovate/node-24.x
chore(deps): update node.js to v24
2025-10-29 04:12:10 +01:00
renovate[bot]
7c5e68e47d chore(deps): update node.js to v24 2025-10-28 20:36:14 +00:00
renovate[bot]
ba68627060 chore(deps): update dependency @biomejs/biome to v2.3.2 2025-10-28 20:36:01 +00:00
renovate[bot]
5bb6a7d4d4 chore(deps): update dependency vitest to ^4.0.4 2025-10-27 20:42:25 +00:00
Jérémie Panzer
76f7999046 Merge pull request #1951 from Athou/renovate/querydsl.version
fix(deps): update querydsl.version to v7.1 (minor)
2025-10-27 21:41:31 +01:00
renovate[bot]
547693df4f fix(deps): update querydsl.version to v7.1 2025-10-27 20:06:14 +00:00
Jérémie Panzer
0206f8211a Merge pull request #1950 from Athou/renovate/axios-1.x
fix(deps): update dependency axios to ^1.13.0
2025-10-27 21:05:06 +01:00
renovate[bot]
e061f2e259 fix(deps): update dependency axios to ^1.13.0 2025-10-27 18:12:02 +00:00
renovate[bot]
560ccff04a chore(deps): update graalvm/setup-graalvm digest to eec4810 2025-10-27 09:49:45 +00:00
renovate[bot]
2f0a84557b chore(deps): lock file maintenance 2025-10-27 00:39:04 +00:00
renovate[bot]
3ae7318ded chore(deps): update dependency @biomejs/biome to v2.3.1 2025-10-26 21:53:19 +00:00
renovate[bot]
6b7d66e833 chore(deps): update dependency com.puppycrawl.tools:checkstyle to v12.1.1 2025-10-26 18:28:31 +00:00
renovate[bot]
ec8e594a5c fix(deps): update dependency style-to-object to ^1.0.12 2025-10-25 02:16:11 +00:00
renovate[bot]
858041772e chore(deps): update dependency vitest to ^4.0.3 2025-10-24 22:17:14 +00:00
Jérémie Panzer
b355c04d87 Merge pull request #1948 from Athou/renovate/major-github-artifact-actions
chore(deps): update github artifact actions (major)
2025-10-25 00:15:30 +02:00
renovate[bot]
4918eaf752 chore(deps): update github artifact actions 2025-10-24 19:37:52 +00:00
Jérémie Panzer
80706f006d Merge pull request #1947 from Athou/renovate/vitejs-plugin-react-5.x
chore(deps): update dependency @vitejs/plugin-react to ^5.1.0
2025-10-24 21:37:11 +02:00
Jérémie Panzer
8a7fec1207 Merge pull request #1946 from Athou/renovate/biomejs-biome-2.x
chore(deps): update dependency @biomejs/biome to v2.3.0
2025-10-24 21:36:43 +02:00
renovate[bot]
22a5b6e85e chore(deps): update dependency @vitejs/plugin-react to ^5.1.0 2025-10-24 16:33:16 +00:00
renovate[bot]
a51c533712 chore(deps): update dependency @biomejs/biome to v2.3.0 2025-10-24 16:33:08 +00:00
renovate[bot]
1f74674a11 chore(deps): update dependency vitest to ^4.0.2 2025-10-24 00:56:41 +00:00
renovate[bot]
2eada58ce5 chore(deps): update dependency vite to ^7.1.12 2025-10-23 13:30:21 +00:00
Jérémie Panzer
31e74bd4a8 Merge pull request #1945 from Athou/renovate/patch-quarkus.version
fix(deps): update quarkus.version to v3.28.5 (patch)
2025-10-23 15:29:26 +02:00
renovate[bot]
903f73ee78 fix(deps): update quarkus.version to v3.28.5 2025-10-23 04:51:46 +00:00
renovate[bot]
b21198b239 fix(deps): update dependency io.github.hakky54:ayza-for-apache5 to v10.0.1 2025-10-23 00:49:27 +00:00
renovate[bot]
e20ff09457 fix(deps): update dependency @reduxjs/toolkit to ^2.9.2 2025-10-22 23:07:16 +00:00
Jérémie Panzer
674393eabc Merge pull request #1944 from Athou/renovate/major-vitest-monorepo
chore(deps): update dependency vitest to v4
2025-10-23 01:06:24 +02:00
renovate[bot]
d78a131713 chore(deps): update dependency vitest to v4 2025-10-22 22:10:33 +00:00
renovate[bot]
e3816bf05b chore(deps): update dependency @biomejs/biome to v2.2.7 2025-10-22 11:53:45 +00:00
renovate[bot]
37fe1c60cc chore(deps): update debian:13.1 docker digest to 72547dd 2025-10-21 05:50:15 +00:00
Jérémie Panzer
e705a0d32b Merge pull request #1942 from Athou/renovate/node-22.x
chore(deps): update node.js to v22.21.0
2025-10-21 04:01:46 +02:00
renovate[bot]
eb658a644b chore(deps): update node.js to v22.21.0 2025-10-21 00:52:29 +00:00
Jérémie Panzer
cb905bfc8c Merge pull request #1941 from Athou/renovate/io.quarkiverse.playwright-quarkus-playwright-2.x
chore(deps): update dependency io.quarkiverse.playwright:quarkus-playwright to v2.2.1
2025-10-21 01:49:14 +02:00
renovate[bot]
d0accf6a84 chore(deps): update dependency io.quarkiverse.playwright:quarkus-playwright to v2.2.1 2025-10-20 21:50:57 +00:00
renovate[bot]
55e6f89fc1 chore(deps): update dependency vite to ^7.1.11 2025-10-20 09:34:48 +00:00
renovate[bot]
60695a0ffc chore(deps): lock file maintenance 2025-10-20 02:37:10 +00:00
Jérémie Panzer
8a8e4655cd Merge pull request #1940 from Athou/renovate/com.puppycrawl.tools-checkstyle-12.x
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v12.1.0
2025-10-19 20:16:35 +02:00
renovate[bot]
2f4b390be1 chore(deps): update dependency com.puppycrawl.tools:checkstyle to v12.1.0 2025-10-19 17:55:02 +00:00
renovate[bot]
31146cc713 chore(deps): update dependency org.codehaus.mojo:exec-maven-plugin to v3.6.2 2025-10-19 08:44:03 +00:00
renovate[bot]
9e020ff268 chore(deps): update dependency jsdom to ^27.0.1 2025-10-18 09:56:15 +00:00
Athou
7e825192d0 enforce user password validation when created in the admin view (#1937) 2025-10-17 10:19:02 +02:00
Athou
8871ae894f handle invalid relative urls (#1939) 2025-10-17 09:04:38 +02:00
renovate[bot]
2808f4b1a2 fix(deps): update dependency @reduxjs/toolkit to ^2.9.1 2025-10-17 06:05:31 +00:00
renovate[bot]
0324c22061 fix(deps): update quarkus.version to v3.28.4 2025-10-16 18:47:22 +00:00
renovate[bot]
59c5131f1a chore(deps): update dependency rollup-plugin-visualizer to ^6.0.5 2025-10-16 13:58:50 +00:00
Athou
ccbc07d7d8 don't show "Star/Unstar" in the context menu if the entry is not markable (#1935) 2025-10-15 07:11:48 +02:00
renovate[bot]
a0247f0036 fix(deps): update mantine monorepo to ^8.3.5 2025-10-14 21:01:50 +00:00
renovate[bot]
0979c2767b chore(deps): update dependency vite to ^7.1.10 2025-10-14 17:07:41 +00:00
renovate[bot]
9a9613bba3 chore(deps): update dependency @types/react-dom to ^19.2.2 2025-10-13 16:44:29 +00:00
Jérémie Panzer
6451f5f3b7 Merge pull request #1931 from Athou/renovate/biomejs-biome-2.2.x
chore(deps): update dependency @biomejs/biome to v2.2.6
2025-10-13 15:34:10 +02:00
Athou
4a4430ce9b fix build 2025-10-13 13:45:03 +02:00
renovate[bot]
a38d3dcf72 chore(deps): update dependency @biomejs/biome to v2.2.6 2025-10-13 10:35:05 +00:00
renovate[bot]
60e1e0d037 chore(deps): lock file maintenance 2025-10-13 01:48:54 +00:00
Athou
8071b85b3d remove unused workflows 2025-10-12 14:53:25 +02:00
Jérémie Panzer
c867bfb846 Merge pull request #1929 from aniol/patch-3
Update messages.po
2025-10-12 13:51:59 +02:00
Aniol
24b32ab69b Update messages.po
The translation has been reviewed, and some tweaks have been applied.
2025-10-12 09:20:09 +02:00
renovate[bot]
b1fc65262f chore(deps): update dependency org.jacoco:jacoco-maven-plugin to v0.8.14 2025-10-12 01:05:50 +00:00
renovate[bot]
5af3fea74c chore(deps): update dependency com.puppycrawl.tools:checkstyle to v12.0.1 2025-10-11 21:37:13 +00:00
Athou
dde38985e4 use stable react compiler 2025-10-11 13:24:07 +02:00
Athou
3f0084fa1c make the app appear as "commafeed" when listing processes 2025-10-11 13:23:23 +02:00
renovate[bot]
8936d4fdce chore(deps): update github/codeql-action digest to f443b60 2025-10-10 18:40:20 +00:00
renovate[bot]
4c47b7d838 fix(deps): update linguijs monorepo to ^5.5.1 2025-10-10 12:34:48 +00:00
Jérémie Panzer
093a9cb8e4 Merge pull request #1927 from xmgz/master
Complete, Fix and Review galician (gl) translation
2025-10-10 14:33:46 +02:00
ghose
f27b3f8933 fixing line breaks not needed
there were many line breaks which did not were needed. Now  removed.
2025-10-10 12:13:41 +00:00
ghose
74a9e48e55 adding back header info 2025-10-10 05:36:38 +00:00
ghose
bafef26ffc Update messages.po
cleaning header
2025-10-10 05:33:53 +00:00
ghose
f8e66170bf consistency and fixes
first review
2025-10-10 05:33:01 +00:00
renovate[bot]
00bf99fe5a fix(deps): update mantine monorepo to ^8.3.4 2025-10-10 00:52:58 +00:00
renovate[bot]
05dd66177f chore(deps): update ibm-semeru-runtimes:open-jdk-25.0.0_36-jre docker digest to f31cc59 2025-10-09 22:58:37 +00:00
renovate[bot]
d5a9e6401e fix(deps): update quarkus.version to v3.28.3 2025-10-09 17:51:36 +00:00
Jérémie Panzer
660ba67433 Merge pull request #1925 from Athou/renovate/com.puppycrawl.tools-checkstyle-12.x
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v12
2025-10-09 19:50:55 +02:00
renovate[bot]
7ad948065b chore(deps): update dependency com.puppycrawl.tools:checkstyle to v12 2025-10-09 16:59:22 +00:00
ghose
40fcb85c93 missing text chains
there were about 80 text chains missing.
2025-10-09 12:52:45 +00:00
renovate[bot]
dcddb80f7b fix(deps): update dependency react-router-dom to ^7.9.4 2025-10-09 01:06:10 +00:00
renovate[bot]
8e349aea19 chore(deps): update dependency npm to v11.6.2 2025-10-08 21:03:39 +00:00
Jérémie Panzer
3d72725ae0 Merge pull request #1924 from Athou/renovate/github-codeql-action-4.x
chore(deps): update github/codeql-action action to v4
2025-10-08 06:36:34 +02:00
renovate[bot]
270cb340f5 chore(deps): update github/codeql-action action to v4 2025-10-07 19:00:38 +00:00
Jérémie Panzer
42b5462889 Merge pull request #1892 from Athou/renovate/monaco-editor-0.x
fix(deps): update dependency monaco-editor to ^0.54.0
2025-10-07 20:59:58 +02:00
renovate[bot]
b98ab8d011 fix(deps): update dependency monaco-editor to ^0.54.0 2025-10-07 12:51:51 +00:00
renovate[bot]
b4264a8ba3 chore(deps): update react monorepo 2025-10-07 10:14:49 +00:00
renovate[bot]
a395246d1e chore(deps): update dependency @types/react to ^19.2.1 2025-10-06 21:11:32 +00:00
renovate[bot]
4b7a2afd07 chore(deps): lock file maintenance 2025-10-06 03:45:30 +00:00
renovate[bot]
7f49ff20cf chore(deps): update dependency org.codehaus.mojo:exec-maven-plugin to v3.6.1 2025-10-06 02:13:46 +00:00
renovate[bot]
4e9995e610 fix(deps): update dependency style-to-object to ^1.0.11 (#1923)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 16:35:01 +00:00
renovate[bot]
9f61442cec chore(deps): update dependency vite to ^7.1.9 2025-10-03 05:30:16 +00:00
renovate[bot]
9339847d09 fix(deps): update mantine monorepo to ^8.3.3 2025-10-02 20:21:36 +00:00
Athou
39e57cb3ef fix warning 2025-10-02 22:06:24 +02:00
Athou
f3a574d05c reformat table 2025-10-02 22:02:28 +02:00
Athou
297c76006a use more records 2025-10-02 21:54:40 +02:00
renovate[bot]
62d025d827 chore(deps): update dependency vite to ^7.1.8 2025-10-02 16:37:35 +00:00
renovate[bot]
999799ea68 chore(deps): update github/codeql-action digest to 64d10c1 2025-10-02 15:25:23 +00:00
renovate[bot]
331f68253e chore(deps): update dependency rollup-plugin-visualizer to ^6.0.4 2025-10-02 13:54:43 +00:00
renovate[bot]
70d3c7a4be chore(deps): update dependency @biomejs/biome to v2.2.5 2025-10-02 13:00:18 +00:00
renovate[bot]
b3c75a0286 chore(deps): update ibm-semeru-runtimes:open-jdk-25.0.0_36-jre docker digest to 8ae0733 2025-10-02 11:54:35 +00:00
renovate[bot]
9946120304 chore(deps): update graalvm/setup-graalvm digest to 2a24120 2025-10-02 09:03:55 +00:00
Athou
7030a67389 Merge branch 'renovate/ibm-semeru-runtimes-25.x' 2025-10-02 06:24:06 +02:00
Jérémie Panzer
eda5ef6965 Merge pull request #1921 from Athou/renovate/patch-testing-library-monorepo
chore(deps): update dependency @testing-library/jest-dom to ^6.9.1
2025-10-02 06:18:06 +02:00
Jérémie Panzer
0324479fda Merge pull request #1922 from Athou/renovate/react-monorepo
fix(deps): update react monorepo to ^19.2.0 (minor)
2025-10-02 06:18:00 +02:00
Athou
aeafecb88d update semeru pattern 2025-10-02 06:17:52 +02:00
renovate[bot]
fde7fbe21a chore(deps): update ibm-semeru-runtimes docker tag to v25 2025-10-02 00:34:50 +00:00
renovate[bot]
7116efc490 fix(deps): update react monorepo to ^19.2.0 2025-10-02 00:34:44 +00:00
renovate[bot]
1ac6058200 chore(deps): update dependency @testing-library/jest-dom to ^6.9.1 2025-10-02 00:34:27 +00:00
renovate[bot]
32b80b64f4 chore(deps): update react monorepo 2025-10-01 19:24:44 +00:00
Jérémie Panzer
9e348767dc Merge pull request #1920 from Athou/renovate/peter-evans-dockerhub-description-5.x
chore(deps): update peter-evans/dockerhub-description action to v5
2025-10-01 21:23:52 +02:00
Jérémie Panzer
bce72e1152 Merge pull request #1904 from Athou/renovate/quarkus.version
fix(deps): update quarkus.version to v3.28.2 (minor)
2025-10-01 21:23:45 +02:00
renovate[bot]
64aba75be2 chore(deps): update peter-evans/dockerhub-description action to v5 2025-10-01 18:13:24 +00:00
renovate[bot]
ca65e13f9a fix(deps): update quarkus.version to v3.28.2 2025-10-01 18:13:15 +00:00
renovate[bot]
54797607c6 chore(deps): update dependency typescript to ^5.9.3 2025-10-01 03:33:28 +00:00
Jérémie Panzer
e174254a95 Merge pull request #1918 from Athou/renovate/testing-library-monorepo
chore(deps): update dependency @testing-library/jest-dom to ^6.9.0
2025-09-30 22:03:33 +02:00
renovate[bot]
4378e24b49 chore(deps): update dependency @testing-library/jest-dom to ^6.9.0 2025-09-30 19:28:02 +00:00
renovate[bot]
35d276ea98 chore(deps): update debian:13.1 docker digest to fd8f5a1 2025-09-30 11:44:47 +00:00
Jérémie Panzer
678c89d9c0 Merge pull request #1917 from Athou/renovate/org.codehaus.mojo-exec-maven-plugin-3.x
chore(deps): update dependency org.codehaus.mojo:exec-maven-plugin to v3.6.0
2025-09-30 13:44:11 +02:00
renovate[bot]
0a42223de0 chore(deps): update dependency org.codehaus.mojo:exec-maven-plugin to v3.6.0 2025-09-30 10:39:26 +00:00
renovate[bot]
54d3f3b007 chore(deps): update dependency @types/react to ^19.1.16 2025-09-30 06:14:38 +00:00
renovate[bot]
3ee58ee464 chore(deps): update debian:13.1 docker digest to 9dfe31a 2025-09-30 01:58:58 +00:00
renovate[bot]
3b5ff016fe chore(deps): update docker/login-action digest to 5e57cd1 2025-09-29 12:55:55 +00:00
Jérémie Panzer
8a8e786f5e Merge pull request #1913 from Athou/renovate/vite-plugin-checker-0.x
chore(deps): update dependency vite-plugin-checker to ^0.11.0
2025-09-29 12:30:24 +02:00
renovate[bot]
2a15f68ffb chore(deps): update dependency vite-plugin-checker to ^0.11.0 2025-09-29 10:10:00 +00:00
renovate[bot]
9387e014c1 chore(deps): lock file maintenance 2025-09-29 02:12:14 +00:00
renovate[bot]
1ef37fcaff chore(deps): update dependency @types/react to ^19.1.15 2025-09-28 15:23:14 +00:00
Jérémie Panzer
c5906a481f Merge pull request #1910 from Athou/renovate/com.puppycrawl.tools-checkstyle-11.x
chore(deps): update dependency com.puppycrawl.tools:checkstyle to v11.1.0
2025-09-28 17:21:38 +02:00
renovate[bot]
ac0bc916a1 chore(deps): update dependency com.puppycrawl.tools:checkstyle to v11.1.0 2025-09-28 13:57:20 +00:00
Athou
5bbe76d56e remove log.trace calls 2025-09-28 07:36:12 +02:00
Athou
1e6195d74c add coverage for in-page url fallback 2025-09-28 07:36:12 +02:00
Athou
85acea7e64 fix warning 2025-09-28 07:36:12 +02:00
renovate[bot]
0e4ff99602 fix(deps): update dependency org.apache.httpcomponents.client5:httpclient5 to v5.5.1 2025-09-28 01:39:52 +00:00
renovate[bot]
575d2a0940 chore(deps): update dependency @vitejs/plugin-react to ^5.0.4 2025-09-27 20:22:58 +00:00
Athou
c548462eef cleanup 2025-09-27 19:10:41 +02:00
Jérémie Panzer
3b4cc66b24 Merge pull request #1909 from RazyAnas/master
Fix off-by-one error in HttpGetter.toByteArray response size check
2025-09-27 19:07:19 +02:00
AnasRazy /
6d7273f822 Merge pull request #1 from RazyAnas/RazyAnas-patch-1
What this PR does:
Fixes an off-by-one error in HttpGetter.toByteArray where responses of exactly maxBytes were being rejected. Now allows responses up to maxBytes, and only throws if the response is larger.

Why this change is needed:
Some feeds return responses exactly equal to the limit. Current implementation incorrectly throws an exception, breaking parsing.

How it works:
Reads up to maxBytes + 1 bytes.
If actual size exceeds maxBytes, throws exception. Otherwise returns full content.

Other notes:
No breaking API changes.
Safe for existing usage.
2025-09-27 15:35:16 +05:30
AnasRazy /
65014d330a Fix off-by-one bug in HttpGetter.toByteArray()
What this PR does:
Fixes an off-by-one error in HttpGetter.toByteArray where responses of exactly maxBytes were being rejected.
Now allows responses up to maxBytes, and only throws if the response is larger.

Why this change is needed:
Some feeds return responses exactly equal to the limit.
Current implementation incorrectly throws an exception, breaking parsing.

How it works:
Reads up to maxBytes + 1 bytes.
If actual size exceeds maxBytes, throws exception.
Otherwise returns full content.

Other notes:
No breaking API changes.
Safe for existing usage.
2025-09-27 15:32:31 +05:30
renovate[bot]
d9e3cf0190 fix(deps): update dependency react-router-dom to ^7.9.3 2025-09-27 04:27:25 +00:00
renovate[bot]
2d8ee54d28 chore(deps): update dependency @types/react to ^19.1.14 2025-09-27 01:45:49 +00:00
renovate[bot]
98c3bb780d chore(deps): update github/codeql-action digest to 3599b3b 2025-09-26 21:56:48 +00:00
renovate[bot]
7247c10615 chore(deps): update dependency com.github.eirslett:frontend-maven-plugin to v1.15.4 2025-09-26 04:52:35 +00:00
Athou
0787284d80 mvn wrapper update 2025-09-26 06:51:52 +02:00
renovate[bot]
1c73bffc95 chore(deps): update github/codeql-action digest to 303c0ae 2025-09-25 12:31:52 +00:00
Jérémie Panzer
6f79815933 Merge pull request #1905 from Athou/renovate/patch-react-router-monorepo
fix(deps): update dependency react-router-dom to ^7.9.2
2025-09-25 08:37:03 +02:00
renovate[bot]
bb108d594a fix(deps): update dependency react-router-dom to ^7.9.2 2025-09-25 05:08:53 +00:00
Jérémie Panzer
f7716c8834 Merge pull request #1906 from Athou/renovate/com.diffplug.spotless-spotless-maven-plugin-3.x
chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v3
2025-09-25 07:07:51 +02:00
Jérémie Panzer
5ba076b1dd Merge pull request #1907 from Athou/renovate/npm-11.6.x
chore(deps): update dependency npm to v11.6.1
2025-09-25 07:04:47 +02:00
renovate[bot]
7861b5a414 chore(deps): update dependency npm to v11.6.1 2025-09-25 05:04:27 +00:00
Athou
f36a5988d8 quarkus build is not yet stable with java 25 2025-09-25 07:03:48 +02:00
renovate[bot]
8b57240db3 chore(deps): update dependency com.diffplug.spotless:spotless-maven-plugin to v3 2025-09-24 21:05:17 +00:00
Jérémie Panzer
7b52efd2d1 Merge pull request #1903 from Athou/renovate/node-22.x
chore(deps): update node.js to v22.20.0
2025-09-24 16:42:26 +02:00
renovate[bot]
4901b838e2 chore(deps): update node.js to v22.20.0 2025-09-24 14:10:30 +00:00
Jérémie Panzer
2313a60f32 Merge pull request #1902 from Athou/renovate/patch-mantine-monorepo
fix(deps): update mantine monorepo to ^8.3.2 (patch)
2025-09-24 07:25:17 +02:00
renovate[bot]
c38e958588 fix(deps): update mantine monorepo to ^8.3.2 2025-09-23 23:39:27 +00:00
Jérémie Panzer
43b1e14f41 Merge pull request #1901 from Athou/renovate/graalvm-setup-graalvm-digest
chore(deps): update graalvm/setup-graalvm digest to e140024
2025-09-23 16:18:56 +02:00
renovate[bot]
1e23b3c355 chore(deps): update graalvm/setup-graalvm digest to e140024 2025-09-23 10:35:05 +00:00
Jérémie Panzer
85e1556148 Merge pull request #1900 from Athou/renovate/vite-7.1.x
chore(deps): update dependency vite to ^7.1.7
2025-09-22 16:07:12 +02:00
renovate[bot]
b65f333a89 chore(deps): update dependency vite to ^7.1.7 2025-09-22 06:54:21 +00:00
renovate[bot]
3dbcbb8280 chore(deps): update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.14.1 2025-09-22 05:17:18 +00:00
renovate[bot]
06e464854a chore(deps): lock file maintenance 2025-09-22 01:55:24 +00:00
90 changed files with 1745 additions and 1480 deletions

View File

@@ -7,7 +7,7 @@ on:
pull_request:
env:
JAVA_VERSION: 25
JAVA_VERSION: 21
DOCKER_BUILD_SUMMARY: false
jobs:
@@ -23,13 +23,13 @@ jobs:
steps:
# Checkout
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
# Setup
- name: Set up GraalVM
uses: graalvm/setup-graalvm@aba6a077d71fbfc02138d7470c4ad6e7f85bd2a9 # v1
uses: graalvm/setup-graalvm@eec48106e0bf45f2976c2ff0c3e22395cced8243 # v1
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: "graalvm"
@@ -67,14 +67,14 @@ jobs:
# Upload artifacts
- name: Upload cross-platform app
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
if: matrix.os == 'ubuntu-latest' # we only need to upload the cross-platform artifact once per database
with:
name: commafeed-${{ matrix.database }}-jvm
path: commafeed-server/target/commafeed-*.zip
- name: Upload native executable
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: commafeed-${{ matrix.database }}-${{ runner.os }}-${{ runner.arch }}
path: commafeed-server/target/commafeed-*-runner*
@@ -98,13 +98,13 @@ jobs:
steps:
# Checkout
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
# Setup
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
@@ -114,7 +114,7 @@ jobs:
# Prepare artifacts
- name: Download artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
pattern: commafeed-${{ matrix.database }}-*
path: ./artifacts
@@ -135,7 +135,7 @@ jobs:
# Docker
- name: Login to Container Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
if: ${{ env.DOCKERHUB_USERNAME != '' }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -215,12 +215,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
pattern: commafeed-*
path: ./artifacts
@@ -249,12 +249,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Update Docker Hub Description
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@@ -1,78 +0,0 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '42 13 * * 4'
push:
branches: [ "master" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
# `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore
# file_mode: git
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3
with:
sarif_file: results.sarif

View File

@@ -1,41 +0,0 @@
name: SonarQube
permissions:
contents: read
on:
push:
branches:
- master
pull_request:
types: [ opened, synchronize, reopened ]
env:
JAVA_VERSION: 25
jobs:
build:
runs-on: ubuntu-latest
steps:
# Checkout
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
# Setup
- name: Set up GraalVM
uses: graalvm/setup-graalvm@aba6a077d71fbfc02138d7470c4ad6e7f85bd2a9 # v1
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: "graalvm"
cache: "maven"
- name: Install Playwright dependencies
run: sudo apt-get install -y libgbm1
# Run test coverage and SonarQube analysis
- name: Analyze with SonarQube
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn --batch-mode verify sonar:sonar -Dsonar.projectKey=Athou_commafeed

View File

@@ -1,18 +1,3 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip

View File

@@ -1,5 +1,15 @@
# Changelog
## [5.12.0]
- Added a setting to disable the "disable pull to refresh" feature because it messes with some browsers (#1168)
- Emojis in feeds are now correctly displayed (#1955)
- Don't show "Star/Unstar" in the context menu if the entry is too old to be starred (#1935)
- Invalid relative urls in feeds no longer prevent those feeds from being parsed (#1939)
- Fix an issue that could prevent large feeds from being parsed when using Java 24+ (#1961)
- Enforce user password validation when created in the admin view (#1937)
- The process in the docker native image is now called "commafeed" instead of "application"
## [5.11.1]
- The search limit of 3 characters has been removed (#1887)

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -17,67 +17,66 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@fontsource/open-sans": "^5.2.7",
"@lingui/core": "^5.5.0",
"@lingui/react": "^5.5.0",
"@mantine/core": "^8.3.1",
"@mantine/form": "^8.3.1",
"@mantine/hooks": "^8.3.1",
"@mantine/modals": "^8.3.1",
"@mantine/notifications": "^8.3.1",
"@mantine/spotlight": "^8.3.1",
"@lingui/core": "^5.6.0",
"@lingui/react": "^5.6.0",
"@mantine/core": "^8.3.8",
"@mantine/form": "^8.3.8",
"@mantine/hooks": "^8.3.8",
"@mantine/modals": "^8.3.8",
"@mantine/notifications": "^8.3.8",
"@mantine/spotlight": "^8.3.8",
"@monaco-editor/react": "^4.7.0",
"@reduxjs/toolkit": "^2.9.0",
"axios": "^1.12.2",
"dayjs": "^1.11.17",
"@reduxjs/toolkit": "^2.10.1",
"axios": "^1.13.2",
"dayjs": "^1.11.19",
"escape-string-regexp": "^5.0.0",
"interweave": "^13.1.1",
"monaco-editor": "^0.52.2",
"monaco-editor": "^0.54.0",
"mousetrap": "^1.6.5",
"react": "^19.1.1",
"react": "^19.2.0",
"react-async-hook": "^4.0.0",
"react-contexify": "^6.0.0",
"react-device-detect": "^2.2.3",
"react-dom": "^19.1.1",
"react-dom": "^19.2.0",
"react-draggable": "^4.5.0",
"react-icons": "^5.5.0",
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.1",
"react-router-dom": "^7.9.6",
"react-swipeable": "^7.0.2",
"style-to-object": "^1.0.9",
"style-to-object": "^1.0.14",
"throttle-debounce": "^5.0.2",
"tinycon": "^0.6.8",
"tss-react": "^4.9.19",
"websocket-heartbeat-js": "^1.1.3"
},
"devDependencies": {
"@biomejs/biome": "^2.2.4",
"@lingui/babel-plugin-lingui-macro": "^5.5.0",
"@lingui/cli": "^5.5.0",
"@lingui/vite-plugin": "^5.5.0",
"@testing-library/jest-dom": "^6.8.0",
"@biomejs/biome": "^2.3.6",
"@lingui/babel-plugin-lingui-macro": "^5.6.0",
"@lingui/cli": "^5.6.0",
"@lingui/vite-plugin": "^5.6.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/mousetrap": "^1.6.15",
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3",
"@types/react-infinite-scroller": "^1.2.5",
"@types/throttle-debounce": "^5.0.2",
"@types/tinycon": "^0.6.7",
"@vitejs/plugin-react": "^5.0.3",
"babel-plugin-react-compiler": "^19.1.0-rc.3",
"jsdom": "^27.0.0",
"rollup-plugin-visualizer": "^6.0.3",
"typescript": "^5.9.2",
"vite": "^7.1.6",
"vite-plugin-checker": "^0.10.3",
"@vitejs/plugin-react": "^5.1.1",
"babel-plugin-react-compiler": "1.0.0",
"jsdom": "^27.2.0",
"rollup-plugin-visualizer": "^6.0.5",
"typescript": "^5.9.3",
"vite": "^7.2.2",
"vite-plugin-checker": "^0.11.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4",
"vitest": "^4.0.10",
"yaml": "^2.8.1"
},
"overrides": {
"react-infinite-scroller": {
"react": "^19.1.1"
"react": "^19.2.0"
}
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>5.11.1</version>
<version>5.12.0</version>
</parent>
<artifactId>commafeed-client</artifactId>
<name>CommaFeed Client</name>
@@ -16,9 +16,9 @@
<sonar.coverage.exclusions>**/*</sonar.coverage.exclusions>
<!-- renovate: datasource=node-version depName=node -->
<node.version>v22.19.0</node.version>
<node.version>v24.11.1</node.version>
<!-- renovate: datasource=npm depName=npm -->
<npm.version>11.6.0</npm.version>
<npm.version>11.6.3</npm.version>
</properties>
<build>
@@ -26,7 +26,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.15.1</version>
<version>1.15.4</version>
<?m2e ignore?>
<executions>
<execution>

View File

@@ -5,7 +5,6 @@ import { ModalsProvider } from "@mantine/modals"
import { Notifications } from "@mantine/notifications"
import type React from "react"
import { useEffect, useState } from "react"
import { isSafari } from "react-device-detect"
import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom"
import Tinycon from "tinycon"
import { Constants } from "@/app/constants"
@@ -200,6 +199,7 @@ export function App() {
useI18n()
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
const dispatch = useAppDispatch()
useEffect(() => {
@@ -213,12 +213,7 @@ export function App() {
<BrowserExtensionBadgeUnreadCountHandler />
<CustomJsHandler />
<CustomCssHandler />
{/* disable pull-to-refresh as it messes with vertical scrolling
safari behaves weirdly when overscroll-behavior is set to none so we disable it only for other browsers
https://github.com/Athou/commafeed/issues/1168
*/}
{!isSafari && <DisablePullToRefresh />}
<DisablePullToRefresh enabled={disablePullToRefresh} />
<HashRouter>
<RedirectHandler />

View File

@@ -252,6 +252,7 @@ export interface Settings {
mobileFooter: boolean
unreadCountTitle: boolean
unreadCountFavicon: boolean
disablePullToRefresh: boolean
primaryColor?: string
sharingSettings: SharingSettings
}

View File

@@ -4,6 +4,7 @@ import { createSlice, isAnyOf, type PayloadAction } from "@reduxjs/toolkit"
import type { LocalSettings, Settings, UserModel, ViewMode } from "@/app/types"
import {
changeCustomContextMenu,
changeDisablePullToRefresh,
changeEntriesToKeepOnTopWhenScrolling,
changeExternalLinkIconDisplayMode,
changeLanguage,
@@ -135,6 +136,10 @@ export const userSlice = createSlice({
if (!state.settings) return
state.settings.unreadCountFavicon = action.meta.arg
})
builder.addCase(changeDisablePullToRefresh.pending, (state, action) => {
if (!state.settings) return
state.settings.disablePullToRefresh = action.meta.arg
})
builder.addCase(changePrimaryColor.pending, (state, action) => {
if (!state.settings) return
state.settings.primaryColor = action.meta.arg
@@ -143,6 +148,7 @@ export const userSlice = createSlice({
if (!state.settings) return
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
})
builder.addMatcher(
isAnyOf(
changeLanguage.fulfilled,
@@ -159,6 +165,7 @@ export const userSlice = createSlice({
changeMobileFooter.fulfilled,
changeUnreadCountTitle.fulfilled,
changeUnreadCountFavicon.fulfilled,
changeDisablePullToRefresh.fulfilled,
changePrimaryColor.fulfilled,
changeSharingSetting.fulfilled
),

View File

@@ -122,6 +122,15 @@ export const changeUnreadCountFavicon = createAppAsyncThunk("settings/unreadCoun
client.user.saveSettings({ ...settings, unreadCountFavicon })
})
export const changeDisablePullToRefresh = createAppAsyncThunk(
"settings/disablePullToRefresh",
(disablePullToRefresh: boolean, thunkApi) => {
const { settings } = thunkApi.getState().user
if (!settings) return
client.user.saveSettings({ ...settings, disablePullToRefresh })
}
)
export const changePrimaryColor = createAppAsyncThunk("settings/primaryColor", (primaryColor: string, thunkApi) => {
const { settings } = thunkApi.getState().user
if (!settings) return

View File

@@ -1,4 +0,0 @@
html,
body {
overscroll-behavior: none;
}

View File

@@ -1,4 +1,3 @@
export const DisablePullToRefresh = () => {
import("./DisablePullToRefresh.css")
return null
export const DisablePullToRefresh = ({ enabled }: { enabled: boolean | undefined }) => {
return enabled ? <style>{`html, body { overscroll-behavior: none; }`}</style> : null
}

View File

@@ -61,19 +61,21 @@ export function FeedEntryContextMenu(props: Readonly<FeedEntryContextMenuProps>)
<Separator />
<Item onClick={async () => await dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}>
<Group>
{props.entry.starred ? <TbStarOff size={iconSize} /> : <TbStar size={iconSize} />}
{props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
</Group>
</Item>
{props.entry.markable && (
<Item onClick={async () => await dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))}>
<Group>
{props.entry.read ? <TbMail size={iconSize} /> : <TbMailOpened size={iconSize} />}
{props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>}
</Group>
</Item>
<>
<Item onClick={async () => await dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}>
<Group>
{props.entry.starred ? <TbStarOff size={iconSize} /> : <TbStar size={iconSize} />}
{props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
</Group>
</Item>
<Item onClick={async () => await dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))}>
<Group>
{props.entry.read ? <TbMail size={iconSize} /> : <TbMailOpened size={iconSize} />}
{props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>}
</Group>
</Item>
</>
)}
<Item onClick={async () => await dispatch(markEntriesUpToEntry(props.entry))}>
<Group>

View File

@@ -9,6 +9,7 @@ import { useAppDispatch, useAppSelector } from "@/app/store"
import type { IconDisplayMode, ScrollMode, SharingSettings } from "@/app/types"
import {
changeCustomContextMenu,
changeDisablePullToRefresh,
changeEntriesToKeepOnTopWhenScrolling,
changeExternalLinkIconDisplayMode,
changeLanguage,
@@ -42,6 +43,7 @@ export function DisplaySettings() {
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const primaryColor = useAppSelector(state => state.user.settings?.primaryColor) || Constants.theme.defaultPrimaryColor
const { _ } = useLingui()
@@ -211,6 +213,12 @@ export function DisplaySettings() {
onChange={async e => await dispatch(changeScrollMarks(e.currentTarget.checked))}
/>
<Switch
label={<Trans>Disable "Pull to refresh" browser behavior</Trans>}
checked={disablePullToRefresh}
onChange={async e => await dispatch(changeDisablePullToRefresh(e.currentTarget.checked))}
/>
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
<SimpleGrid cols={2}>

View File

@@ -283,6 +283,10 @@ msgstr "تنازلي"
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -44,7 +44,7 @@ msgstr "Accions"
#: src/components/content/add/AddCategory.tsx
msgid "Add"
msgstr "Afegir"
msgstr "Afegeix"
#: src/pages/app/AddPage.tsx
msgid "Add category"
@@ -83,7 +83,7 @@ msgstr "Un fitxer opml és un fitxer XML que conté URL i categories de canals.
#: src/components/content/add/Subscribe.tsx
msgid "Analyze feed"
msgstr "Analitzar el feed"
msgstr "Analitza el canal"
#: src/components/AnnouncementDialog.tsx
msgid "Announcement"
@@ -91,11 +91,11 @@ msgstr "Anunci"
#: src/components/settings/ProfileSettings.tsx
msgid "API key"
msgstr "clau API"
msgstr "Clau API"
#: src/pages/app/CategoryDetailsPage.tsx
msgid "Are you sure you want to delete category <0>{categoryName}</0>?"
msgstr "Estàs segur que vols suprimir la categoria <0>{categoryName}</0>?"
msgstr "Esteu segur que voleu suprimir la categoria <0>{categoryName}</0>?"
#: src/pages/admin/AdminUsersPage.tsx
msgid "Are you sure you want to delete user <0>{userName}</0> ?"
@@ -115,7 +115,7 @@ msgstr "Esteu segur que voleu marcar les entrades més antigues de {threshold} d
#: src/pages/app/FeedDetailsPage.tsx
msgid "Are you sure you want to unsubscribe from <0>{feedName}</0>?"
msgstr "Estàs segur que vols cancel·lar la subscripció a <0>{feedName}</0>?"
msgstr "Esteu segur que voleu cancel·lar la subscripció a <0>{feedName}</0>?"
#: src/components/header/Header.tsx
msgid "Asc"
@@ -131,7 +131,7 @@ msgstr "Enrere"
#: src/pages/auth/PasswordRecoveryPage.tsx
msgid "Back to log in"
msgstr "Tornar a iniciar sessió"
msgstr "Torna a iniciar sessió"
#: src/components/settings/DisplaySettings.tsx
msgid "Blue"
@@ -283,6 +283,10 @@ msgstr "Desc"
msgid "Detailed"
msgstr "Detallat"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx
@@ -322,7 +326,7 @@ msgstr "Edita l'usuari"
#: src/components/admin/UserEdit.tsx
#: src/pages/admin/AdminUsersPage.tsx
msgid "Enabled"
msgstr "activat"
msgstr "Activat"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Enter"
@@ -650,7 +654,7 @@ msgstr "el més vell primer"
#: src/components/settings/DisplaySettings.tsx
msgid "On desktop"
msgstr "A l'scriptori"
msgstr "A l'escriptori"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Rhag"
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Beschr"
msgid "Detailed"
msgstr "Detailliert"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Desc"
msgid "Detailed"
msgstr "Detailed"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr "Disable \"Pull to refresh\" browser behavior"
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -284,6 +284,10 @@ msgstr "Desc"
msgid "Detailed"
msgstr "Detallado"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "توصیف"
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Descendant"
msgid "Detailed"
msgstr "Vue détaillée"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

File diff suppressed because it is too large Load Diff

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "説明"
msgid "Detailed"
msgstr "詳細"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "설명"
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Dec"
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Beschrijving"
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Opis"
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Desc"
msgid "Detailed"
msgstr "Detalhado"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "По убыванию"
msgid "Detailed"
msgstr "Подробно"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr ""
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "Açılış"
msgid "Detailed"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -283,6 +283,10 @@ msgstr "降序"
msgid "Detailed"
msgstr "详细"
#: src/components/settings/DisplaySettings.tsx
msgid "Disable \"Pull to refresh\" browser behavior"
msgstr ""
#: src/components/header/ProfileMenu.tsx
#: src/components/settings/DisplaySettings.tsx
#: src/pages/app/SettingsPage.tsx

View File

@@ -35,10 +35,11 @@ export function MetricsPage() {
setLoading: state => ({ ...state, loading: true }),
})
const { execute } = query
useEffect(() => {
const interval = setInterval(() => query.execute(), 2000)
const interval = setInterval(() => execute(), 2000)
return () => clearInterval(interval)
}, [query.execute])
}, [execute])
if (!query.result) return <Loader />
const { meters, gauges } = query.result.data

View File

@@ -50,7 +50,7 @@ function FilteringExpressionDescription() {
export function FeedDetailsPage() {
const { id } = useParams()
if (!id) throw Error("id required")
if (!id) throw new Error("id required")
const apiKey = useAppSelector(state => state.user.profile?.apiKey)
const dispatch = useAppDispatch()

View File

@@ -6,14 +6,14 @@
<parent>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>5.11.1</version>
<version>5.12.0</version>
</parent>
<artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name>
<properties>
<quarkus.version>3.26.4</quarkus.version>
<querydsl.version>7.0</querydsl.version>
<quarkus.version>3.29.4</quarkus.version>
<querydsl.version>7.1</querydsl.version>
<rome.version>2.1.0</rome.version>
<build.database>h2</build.database>
@@ -117,7 +117,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.1</version>
<version>3.6.2</version>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
@@ -220,7 +220,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.13</version>
<version>0.8.14</version>
<configuration>
<!-- excluding SACParserCSS21TokenManager because it causes a "Method too large" exception -->
<excludes>
@@ -299,7 +299,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>11.0.1</version>
<version>12.1.2</version>
</dependency>
</dependencies>
<executions>
@@ -328,7 +328,7 @@
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.46.1</version>
<version>3.1.0</version>
<?m2e ignore?>
<executions>
<execution>
@@ -357,7 +357,7 @@
<dependency>
<groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId>
<version>5.11.1</version>
<version>5.12.0</version>
</dependency>
<!-- compile-time processors -->
@@ -497,7 +497,7 @@
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>77.1</version>
<version>78.1</version>
</dependency>
<dependency>
<groupId>net.sourceforge.cssparser</groupId>
@@ -512,7 +512,7 @@
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.5</version>
<version>5.5.1</version>
</dependency>
<!-- add brotli support for httpclient5 -->
<dependency>
@@ -523,7 +523,7 @@
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>ayza-for-apache5</artifactId>
<version>10.0.0</version>
<version>10.0.1</version>
</dependency>
<!-- test dependencies -->
@@ -540,7 +540,7 @@
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<version>2.1.3</version>
<version>2.2.1</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -1,4 +1,4 @@
FROM ibm-semeru-runtimes:open-21.0.8_9-jre@sha256:0d2e27e83ccf97e8aa10ddbe3771811449b1ab5915428c3640cea4edc42d5c30
FROM ibm-semeru-runtimes:open-jdk-25.0.1_8-jre@sha256:015afe20b069a2e0a0e956117ad515f319a4a4e6a3dee5682f3428010fdfc151
EXPOSE 8082
RUN mkdir -p /commafeed/data

View File

@@ -1,4 +1,4 @@
FROM debian:13.1@sha256:833c135acfe9521d7a0035a296076f98c182c542a2b6b5a0fd7063d355d696be
FROM debian:13.2@sha256:8f6a88feef3ed01a300dafb87f208977f39dccda1fd120e878129463f7fa3b8f
ARG TARGETARCH
EXPOSE 8082
@@ -6,7 +6,7 @@ EXPOSE 8082
RUN mkdir -p /commafeed/data
VOLUME /commafeed/data
COPY artifacts/commafeed-*-${TARGETARCH}-runner /commafeed/application
COPY artifacts/commafeed-*-${TARGETARCH}-runner /commafeed/commafeed
WORKDIR /commafeed
CMD ["./application"]
CMD ["./commafeed"]

View File

@@ -58,7 +58,6 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Lombok;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
@@ -127,9 +126,9 @@ public class HttpGetter {
}
}
int code = response.getCode();
if (code == HttpStatus.SC_TOO_MANY_REQUESTS || code == HttpStatus.SC_SERVICE_UNAVAILABLE && response.getRetryAfter() != null) {
throw new TooManyRequestsException(response.getRetryAfter());
int code = response.code();
if (code == HttpStatus.SC_TOO_MANY_REQUESTS || code == HttpStatus.SC_SERVICE_UNAVAILABLE && response.retryAfter() != null) {
throw new TooManyRequestsException(response.retryAfter());
}
if (code == HttpStatus.SC_NOT_MODIFIED) {
@@ -140,16 +139,16 @@ public class HttpGetter {
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
}
String lastModifiedHeader = response.getLastModifiedHeader();
String eTagHeader = response.getETagHeader();
String lastModifiedHeader = response.lastModifiedHeader();
String eTagHeader = response.eTagHeader();
Duration validFor = Optional.ofNullable(response.getCacheControl())
Duration validFor = Optional.ofNullable(response.cacheControl())
.filter(cc -> cc.getMaxAge() >= 0)
.map(cc -> Duration.ofSeconds(cc.getMaxAge()))
.orElse(Duration.ZERO);
return new HttpResult(response.getContent(), response.getContentType(), lastModifiedHeader, eTagHeader,
response.getUrlAfterRedirect(), validFor);
return new HttpResult(response.content(), response.contentType(), lastModifiedHeader, eTagHeader, response.urlAfterRedirect(),
validFor);
}
private void ensureHttpScheme(String scheme) throws SchemeNotAllowedException {
@@ -254,8 +253,8 @@ public class HttpGetter {
return null;
}
byte[] bytes = ByteStreams.limit(input, maxBytes).readAllBytes();
if (bytes.length == maxBytes) {
byte[] bytes = ByteStreams.limit(input, maxBytes + 1).readAllBytes();
if (bytes.length > maxBytes) {
throw new IOException("Response size exceeds the maximum allowed size (%s bytes)".formatted(maxBytes));
}
return bytes;
@@ -307,7 +306,7 @@ public class HttpGetter {
}
return CacheBuilder.newBuilder()
.weigher((HttpRequest key, HttpResponse value) -> value.getContent() != null ? value.getContent().length : 0)
.weigher((HttpRequest key, HttpResponse value) -> value.content() != null ? value.content().length : 0)
.maximumWeight(cacheConfig.maximumMemorySize().asLongValue())
.expireAfterWrite(cacheConfig.expiration())
.build();
@@ -398,26 +397,12 @@ public class HttpGetter {
}
}
@Value
private static class HttpResponse {
int code;
String lastModifiedHeader;
String eTagHeader;
CacheControl cacheControl;
Instant retryAfter;
byte[] content;
String contentType;
String urlAfterRedirect;
private record HttpResponse(int code, String lastModifiedHeader, String eTagHeader, CacheControl cacheControl, Instant retryAfter,
byte[] content, String contentType, String urlAfterRedirect) {
}
@Value
public static class HttpResult {
byte[] content;
String contentType;
String lastModifiedSince;
String eTag;
String urlAfterRedirect;
Duration validFor;
public record HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, String urlAfterRedirect,
Duration validFor) {
}
}

View File

@@ -8,8 +8,10 @@ import org.netpreserve.urlcanon.Canonicalizer;
import org.netpreserve.urlcanon.ParsedUrl;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
@UtilityClass
@Slf4j
public class Urls {
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
@@ -42,7 +44,12 @@ public class Urls {
return null;
}
return URI.create(baseUrl).resolve(relativeUrl).toString();
try {
return URI.create(baseUrl).resolve(relativeUrl).toString();
} catch (IllegalArgumentException e) {
log.debug("Unable to create absolute url from relative url: {} base: {}", relativeUrl, baseUrl, e);
return null;
}
}
public static String removeTrailingSlash(String url) {

View File

@@ -12,9 +12,6 @@ import com.commafeed.backend.model.QFeedEntry;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.dsl.NumberExpression;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Singleton
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
@@ -64,10 +61,6 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
return delete(list);
}
@AllArgsConstructor
@Getter
public static class FeedCapacity {
private Long id;
private Long capacity;
public record FeedCapacity(Long id, Long capacity) {
}
}

View File

@@ -129,9 +129,9 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
if (CollectionUtils.isNotEmpty(keywords)) {
for (FeedEntryKeyword keyword : keywords) {
BooleanBuilder or = new BooleanBuilder();
or.or(CONTENT.content.containsIgnoreCase(keyword.getKeyword()));
or.or(CONTENT.title.containsIgnoreCase(keyword.getKeyword()));
if (keyword.getMode() == Mode.EXCLUDE) {
or.or(CONTENT.content.containsIgnoreCase(keyword.keyword()));
or.or(CONTENT.title.containsIgnoreCase(keyword.keyword()));
if (keyword.mode() == Mode.EXCLUDE) {
or.not();
}
query.where(or);

View File

@@ -71,11 +71,10 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
url = Urls.removeTrailingSlash(url) + "/favicon.ico";
log.debug("getting root icon at {}", url);
HttpResult result = getter.get(url);
bytes = result.getContent();
contentType = result.getContentType();
bytes = result.content();
contentType = result.contentType();
} catch (Exception e) {
log.debug("Failed to retrieve iconAtRoot for url {}: ", url);
log.trace("Failed to retrieve iconAtRoot for url {}: ", url, e);
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
}
if (!isValidIconResponse(bytes, contentType)) {
@@ -89,10 +88,9 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
Document doc;
try {
HttpResult result = getter.get(url);
doc = Jsoup.parse(new String(result.getContent()), url);
doc = Jsoup.parse(new String(result.content()), url);
} catch (Exception e) {
log.debug("Failed to retrieve page to find icon");
log.trace("Failed to retrieve page to find icon", e);
log.debug("Failed to retrieve page to find icon", e);
return null;
}
@@ -115,11 +113,10 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
String contentType;
try {
HttpResult result = getter.get(href);
bytes = result.getContent();
contentType = result.getContentType();
bytes = result.content();
contentType = result.contentType();
} catch (Exception e) {
log.debug("Failed to retrieve icon found in page {}", href);
log.trace("Failed to retrieve icon found in page {}", href, e);
log.debug("Failed to retrieve icon found in page {}", href, e);
return null;
}

View File

@@ -45,8 +45,8 @@ public class FacebookFaviconFetcher extends AbstractFaviconFetcher {
log.debug("Getting Facebook user's icon, {}", url);
HttpResult iconResult = getter.get(iconUrl);
bytes = iconResult.getContent();
contentType = iconResult.getContentType();
bytes = iconResult.content();
contentType = iconResult.contentType();
} catch (Exception e) {
log.debug("Failed to retrieve Facebook icon", e);
}

View File

@@ -2,20 +2,13 @@ package com.commafeed.backend.favicon;
import jakarta.ws.rs.core.MediaType;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
@Getter
@Slf4j
public class Favicon {
public record Favicon(byte[] icon, MediaType mediaType) {
private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.valueOf("image/x-icon");
private final byte[] icon;
private final MediaType mediaType;
public Favicon(byte[] icon, String contentType) {
this(icon, parseMediaType(contentType));
}

View File

@@ -85,8 +85,8 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
}
HttpResult iconResult = getter.get(thumbnailUrl.asText());
bytes = iconResult.getContent();
contentType = iconResult.getContentType();
bytes = iconResult.content();
contentType = iconResult.contentType();
} catch (Exception e) {
log.debug("Failed to retrieve YouTube icon", e);
}
@@ -104,7 +104,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
.queryParam("key", googleAuthKey)
.queryParam("forUsername", userId)
.build();
return getter.get(uri.toString()).getContent();
return getter.get(uri.toString()).content();
}
private byte[] fetchForChannel(String googleAuthKey, String channelId)
@@ -114,7 +114,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
.queryParam("key", googleAuthKey)
.queryParam("id", channelId)
.build();
return getter.get(uri.toString()).getContent();
return getter.get(uri.toString()).content();
}
private byte[] fetchForPlaylist(String googleAuthKey, String playlistId)
@@ -124,7 +124,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
.queryParam("key", googleAuthKey)
.queryParam("id", playlistId)
.build();
byte[] playlistBytes = getter.get(uri.toString()).getContent();
byte[] playlistBytes = getter.get(uri.toString()).content();
JsonNode channelId = objectMapper.readTree(playlistBytes).at(PLAYLIST_CHANNEL_ID);
if (channelId.isMissingNode()) {

View File

@@ -5,23 +5,15 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* A keyword used in a search query
*/
@Getter
@RequiredArgsConstructor
public class FeedEntryKeyword {
public record FeedEntryKeyword(String keyword, Mode mode) {
public enum Mode {
INCLUDE, EXCLUDE
}
private final String keyword;
private final Mode mode;
public static List<FeedEntryKeyword> fromQueryString(String keywords) {
List<FeedEntryKeyword> list = new ArrayList<>();
if (keywords != null) {

View File

@@ -50,20 +50,20 @@ public class FeedFetcher {
log.debug("Fetching feed {}", feedUrl);
HttpResult result = getter.get(HttpRequest.builder(feedUrl).lastModified(lastModified).eTag(eTag).build());
byte[] content = result.getContent();
byte[] content = result.content();
FeedParserResult parserResult;
try {
parserResult = parser.parse(result.getUrlAfterRedirect(), content);
parserResult = parser.parse(result.urlAfterRedirect(), content);
} catch (FeedParsingException e) {
if (extractFeedUrlFromHtml) {
String extractedUrl = extractFeedUrl(urlProviders, feedUrl, new String(result.getContent(), StandardCharsets.UTF_8));
String extractedUrl = extractFeedUrl(urlProviders, feedUrl, new String(result.content(), StandardCharsets.UTF_8));
if (StringUtils.isNotBlank(extractedUrl)) {
feedUrl = extractedUrl;
result = getter.get(HttpRequest.builder(extractedUrl).lastModified(lastModified).eTag(eTag).build());
content = result.getContent();
parserResult = parser.parse(result.getUrlAfterRedirect(), content);
content = result.content();
parserResult = parser.parse(result.urlAfterRedirect(), content);
} else {
throw new NoFeedFoundException(e);
}
@@ -76,26 +76,24 @@ public class FeedFetcher {
throw new IOException("Feed content is empty.");
}
boolean lastModifiedHeaderValueChanged = !Strings.CS.equals(lastModified, result.getLastModifiedSince());
boolean etagHeaderValueChanged = !Strings.CS.equals(eTag, result.getETag());
boolean lastModifiedHeaderValueChanged = !Strings.CS.equals(lastModified, result.lastModifiedSince());
boolean etagHeaderValueChanged = !Strings.CS.equals(eTag, result.eTag());
String hash = Digests.sha1Hex(content);
if (lastContentHash != null && lastContentHash.equals(hash)) {
log.debug("content hash not modified: {}", feedUrl);
throw new NotModifiedException("content hash not modified",
lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null,
etagHeaderValueChanged ? result.getETag() : null);
throw new NotModifiedException("content hash not modified", lastModifiedHeaderValueChanged ? result.lastModifiedSince() : null,
etagHeaderValueChanged ? result.eTag() : null);
}
if (lastPublishedDate != null && lastPublishedDate.equals(parserResult.lastPublishedDate())) {
log.debug("publishedDate not modified: {}", feedUrl);
throw new NotModifiedException("publishedDate not modified",
lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null,
etagHeaderValueChanged ? result.getETag() : null);
throw new NotModifiedException("publishedDate not modified", lastModifiedHeaderValueChanged ? result.lastModifiedSince() : null,
etagHeaderValueChanged ? result.eTag() : null);
}
return new FeedFetcherResult(parserResult, result.getUrlAfterRedirect(), result.getLastModifiedSince(), result.getETag(), hash,
result.getValidFor());
return new FeedFetcherResult(parserResult, result.urlAfterRedirect(), result.lastModifiedSince(), result.eTag(), hash,
result.validFor());
}
private static String extractFeedUrl(List<FeedURLProvider> urlProviders, String url, String urlContent) {

View File

@@ -31,7 +31,6 @@ import com.commafeed.frontend.ws.WebSocketMessageBuilder;
import com.commafeed.frontend.ws.WebSocketSessions;
import com.google.common.util.concurrent.Striped;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
@@ -171,11 +170,7 @@ public class FeedRefreshUpdater {
WebSocketMessageBuilder.newFeedEntries(sub, unreadCount)));
}
@AllArgsConstructor
private static class AddEntryResult {
private final boolean processed;
private final boolean inserted;
private final Set<FeedSubscription> subscriptionsForWhichEntryIsUnread;
private record AddEntryResult(boolean processed, boolean inserted, Set<FeedSubscription> subscriptionsForWhichEntryIsUnread) {
}
}

View File

@@ -11,7 +11,7 @@ import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch;
@Singleton
class EncodingDetector {
public class EncodingDetector {
/**
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the

View File

@@ -8,41 +8,47 @@ import jakarta.inject.Singleton;
import org.ahocorasick.trie.Emit;
import org.ahocorasick.trie.Trie;
import org.apache.commons.lang3.StringUtils;
import org.jdom2.Verifier;
@Singleton
class FeedCleaner {
public class FeedCleaner {
private static final Pattern DOCTYPE_PATTERN = Pattern.compile("<!DOCTYPE[^>]*>", Pattern.CASE_INSENSITIVE);
public String trimInvalidXmlCharacters(String xml) {
public String clean(String xml) {
xml = removeCharactersBeforeFirstXmlTag(xml);
xml = removeInvalidXmlCharacters(xml);
xml = replaceHtmlEntitiesWithNumericEntities(xml);
xml = removeDoctypeDeclarations(xml);
return xml;
}
String removeCharactersBeforeFirstXmlTag(String xml) {
if (StringUtils.isBlank(xml)) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean firstTagFound = false;
for (int i = 0; i < xml.length(); i++) {
char c = xml.charAt(i);
int pos = xml.indexOf('<');
return pos < 0 ? null : xml.substring(pos);
}
if (!firstTagFound) {
if (c == '<') {
firstTagFound = true;
} else {
continue;
}
}
if (c >= 32 || c == 9 || c == 10 || c == 13) {
if (!Character.isHighSurrogate(c) && !Character.isLowSurrogate(c)) {
sb.append(c);
}
}
String removeInvalidXmlCharacters(String xml) {
if (StringUtils.isBlank(xml)) {
return null;
}
return sb.toString();
return xml.codePoints()
.filter(Verifier::isXMLCharacter)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}
// https://stackoverflow.com/a/40836618
public String replaceHtmlEntitiesWithNumericEntities(String source) {
String replaceHtmlEntitiesWithNumericEntities(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
// Create a buffer sufficiently large that re-allocations are minimized.
StringBuilder sb = new StringBuilder(source.length() << 1);
@@ -63,7 +69,11 @@ class FeedCleaner {
return sb.toString();
}
public String removeDoctypeDeclarations(String xml) {
String removeDoctypeDeclarations(String xml) {
if (StringUtils.isBlank(xml)) {
return null;
}
return DOCTYPE_PATTERN.matcher(xml).replaceAll("");
}

View File

@@ -14,6 +14,7 @@ import jakarta.inject.Singleton;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemProperties;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.jdom2.Element;
import org.jdom2.Namespace;
@@ -38,12 +39,9 @@ import com.rometools.rome.feed.synd.SyndLink;
import com.rometools.rome.feed.synd.SyndLinkImpl;
import com.rometools.rome.io.SyndFeedInput;
import lombok.RequiredArgsConstructor;
/**
* Parses raw xml into a FeedParserResult object
*/
@RequiredArgsConstructor
@Singleton
public class FeedParser {
@@ -55,15 +53,25 @@ public class FeedParser {
private final EncodingDetector encodingDetector;
private final FeedCleaner feedCleaner;
public FeedParser(EncodingDetector encodingDetector, FeedCleaner feedCleaner) {
this.encodingDetector = encodingDetector;
this.feedCleaner = feedCleaner;
// disable entity expansion limits added in JDK24+ (#1961)
// we already strip doctype declarations in FeedCleaner to prevent xxe attacks
// we also already limit the size of feeds we download in HttpGetter
System.setProperty(SystemProperties.JDK_XML_MAX_GENERAL_ENTITY_SIZE_LIMIT, "0");
System.setProperty(SystemProperties.JDK_XML_TOTAL_ENTITY_SIZE_LIMIT, "0");
}
public FeedParserResult parse(String feedUrl, byte[] xml) throws FeedParsingException {
try {
Charset encoding = encodingDetector.getEncoding(xml);
String xmlString = feedCleaner.trimInvalidXmlCharacters(new String(xml, encoding));
String xmlString = feedCleaner.clean(new String(xml, encoding));
if (xmlString == null) {
throw new FeedParsingException("Input string is null for url " + feedUrl);
throw new FeedParsingException("Input string is empty for url " + feedUrl);
}
xmlString = feedCleaner.replaceHtmlEntitiesWithNumericEntities(xmlString);
xmlString = feedCleaner.removeDoctypeDeclarations(xmlString);
InputSource source = new InputSource(new StringReader(xmlString));
SyndFeed feed = new SyndFeedInput().build(source);

View File

@@ -131,6 +131,7 @@ public class UserSettings extends AbstractModel {
private boolean mobileFooter;
private boolean unreadCountTitle;
private boolean unreadCountFavicon;
private boolean disablePullToRefresh;
private boolean email;
private boolean gmail;

View File

@@ -92,10 +92,10 @@ public class DatabaseCleaningService {
}
for (final FeedCapacity feed : feeds) {
long remaining = feed.getCapacity() - maxFeedCapacity;
long remaining = feed.capacity() - maxFeedCapacity;
do {
final long rem = remaining;
int deleted = unitOfWork.call(() -> feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(batchSize, rem)));
int deleted = unitOfWork.call(() -> feedEntryDAO.deleteOldEntries(feed.id(), Math.min(batchSize, rem)));
entriesDeletedMeter.mark(deleted);
total += deleted;
remaining -= deleted;

View File

@@ -72,6 +72,9 @@ public class Settings implements Serializable {
@Schema(description = "show unread count in the favicon", required = true)
private boolean unreadCountFavicon;
@Schema(description = "disable pull to refresh", required = true)
private boolean disablePullToRefresh;
@Schema(description = "primary theme color to use in the UI")
private String primaryColor;

View File

@@ -4,6 +4,8 @@ import java.io.Serializable;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import com.commafeed.security.password.ValidPassword;
import lombok.Data;
@SuppressWarnings("serial")
@@ -21,6 +23,7 @@ public class AdminSaveUserRequest implements Serializable {
private String email;
@Schema(description = "user password")
@ValidPassword
private String password;
@Schema(description = "account status", required = true)

View File

@@ -22,7 +22,7 @@ public class RegistrationRequest implements Serializable {
@Size(min = 3, max = 32)
private String name;
@Schema(description = "password, minimum 6 characters", required = true)
@Schema(description = "password", required = true)
@NotEmpty
@ValidPassword
private String password;

View File

@@ -9,6 +9,7 @@ import java.util.Set;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Singleton;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
@@ -65,7 +66,7 @@ public class AdminREST {
@Operation(
summary = "Save or update a user",
description = "Save or update a user. If the id is not specified, a new user will be created")
public Response adminSaveUser(@Parameter(required = true) AdminSaveUserRequest req) {
public Response adminSaveUser(@Valid @Parameter(required = true) AdminSaveUserRequest req) {
Preconditions.checkNotNull(req);
Preconditions.checkNotNull(req.getName());

View File

@@ -342,7 +342,7 @@ public class FeedREST {
Feed feed = subscription.getFeed();
Favicon icon = feedService.fetchFavicon(feed);
return Response.ok(icon.getIcon(), icon.getMediaType()).build();
return Response.ok(icon.icon(), icon.mediaType()).build();
}
@POST

View File

@@ -74,7 +74,7 @@ public class ServerREST {
url = ImageProxyUrl.decode(url);
try {
HttpResult result = httpGetter.get(url);
return Response.ok(result.getContent()).build();
return Response.ok(result.content()).build();
} catch (Exception e) {
return Response.status(Status.SERVICE_UNAVAILABLE).entity(e.getMessage()).build();
}

View File

@@ -120,6 +120,7 @@ public class UserREST {
s.setMobileFooter(settings.isMobileFooter());
s.setUnreadCountTitle(settings.isUnreadCountTitle());
s.setUnreadCountFavicon(settings.isUnreadCountFavicon());
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
s.setPrimaryColor(settings.getPrimaryColor());
} else {
s.setReadingMode(ReadingMode.UNREAD);
@@ -148,6 +149,7 @@ public class UserREST {
s.setMobileFooter(false);
s.setUnreadCountTitle(false);
s.setUnreadCountFavicon(true);
s.setDisablePullToRefresh(true);
}
return s;
}
@@ -183,6 +185,7 @@ public class UserREST {
s.setMobileFooter(settings.isMobileFooter());
s.setUnreadCountTitle(settings.isUnreadCountTitle());
s.setUnreadCountFavicon(settings.isUnreadCountFavicon());
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
s.setPrimaryColor(settings.getPrimaryColor());
s.setEmail(settings.getSharingSettings().isEmail());

View File

@@ -306,7 +306,7 @@ public class FeverREST {
FeverFavicon f = new FeverFavicon();
f.setId(s.getFeed().getId());
f.setData(String.format("data:%s;base64,%s", favicon.getMediaType(), Base64.getEncoder().encodeToString(favicon.getIcon())));
f.setData(String.format("data:%s;base64,%s", favicon.mediaType(), Base64.getEncoder().encodeToString(favicon.icon())));
return f;
}).toList();
}

View File

@@ -0,0 +1,20 @@
<?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="add-disablePullToRefresh-setting" author="athou">
<addColumn tableName="USERSETTINGS">
<column name="disablePullToRefresh" type="BOOLEAN" valueBoolean="false">
<constraints nullable="false" />
</column>
</addColumn>
</changeSet>
<changeSet id="enable-disablePullToRefresh-setting" author="athou">
<update tableName="USERSETTINGS">
<column name="disablePullToRefresh" valueBoolean="true" />
</update>
</changeSet>
</databaseChangeLog>

View File

@@ -36,5 +36,6 @@
<include file="changelogs/db.changelog-5.3.xml" />
<include file="changelogs/db.changelog-5.8.xml" />
<include file="changelogs/db.changelog-5.11.xml" />
<include file="changelogs/db.changelog-5.12.xml" />
</databaseChangeLog>

View File

@@ -104,12 +104,12 @@ class HttpGetterTest {
.withHeader(HttpHeaders.RETRY_AFTER, "120"));
HttpResult result = getter.get(this.feedUrl);
Assertions.assertArrayEquals(feedContent, result.getContent());
Assertions.assertEquals(MediaType.APPLICATION_ATOM_XML.toString(), result.getContentType());
Assertions.assertEquals("123456", result.getLastModifiedSince());
Assertions.assertEquals("78910", result.getETag());
Assertions.assertEquals(Duration.ofSeconds(60), result.getValidFor());
Assertions.assertEquals(this.feedUrl, result.getUrlAfterRedirect());
Assertions.assertArrayEquals(feedContent, result.content());
Assertions.assertEquals(MediaType.APPLICATION_ATOM_XML.toString(), result.contentType());
Assertions.assertEquals("123456", result.lastModifiedSince());
Assertions.assertEquals("78910", result.eTag());
Assertions.assertEquals(Duration.ofSeconds(60), result.validFor());
Assertions.assertEquals(this.feedUrl, result.urlAfterRedirect());
}
@Test
@@ -121,7 +121,7 @@ class HttpGetterTest {
.withHeader(HttpHeaders.CACHE_CONTROL, "max-age=60; must-revalidate"));
HttpResult result = getter.get(this.feedUrl);
Assertions.assertEquals(Duration.ZERO, result.getValidFor());
Assertions.assertEquals(Duration.ZERO, result.validFor());
}
@Test
@@ -167,7 +167,7 @@ class HttpGetterTest {
.respond(HttpResponse.response().withBody(feedContent).withContentType(MediaType.APPLICATION_ATOM_XML));
HttpResult result = getter.get(this.feedUrl);
Assertions.assertEquals("http://localhost:" + this.mockServerClient.getPort() + "/redirected-2", result.getUrlAfterRedirect());
Assertions.assertEquals("http://localhost:" + this.mockServerClient.getPort() + "/redirected-2", result.urlAfterRedirect());
}
@Test
@@ -202,7 +202,7 @@ class HttpGetterTest {
.respond(HttpResponse.response().withBody("ok"));
HttpResult result = getter.get(this.feedUrl);
Assertions.assertEquals("ok", new String(result.getContent()));
Assertions.assertEquals("ok", new String(result.content()));
}
@Test
@@ -284,7 +284,7 @@ class HttpGetterTest {
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withBody("ok"));
HttpResult result = getter.get("https://localhost:" + this.mockServerClient.getPort());
Assertions.assertEquals("ok", new String(result.getContent()));
Assertions.assertEquals("ok", new String(result.content()));
}
@Test
@@ -336,7 +336,7 @@ class HttpGetterTest {
});
HttpResult result = getter.get(HttpGetterTest.this.feedUrl);
Assertions.assertEquals(body, new String(result.getContent()));
Assertions.assertEquals(body, new String(result.content()));
}
@FunctionalInterface

View File

@@ -62,6 +62,10 @@ class UrlsTest {
Assertions.assertEquals("http://ergoemacs.org/emacs/elisp_all_about_lines.html",
Urls.toAbsolute("elisp_all_about_lines.html", "blog.xml", "http://ergoemacs.org/emacs/blog.xml"));
// invalid relative urls
Assertions.assertEquals("title:10001280",
Urls.toAbsolute("title:10001280", "https://www.berliner-zeitung.de", "https://www.berliner-zeitung.de/feed.xml"));
}
@Test

View File

@@ -43,8 +43,8 @@ class FacebookFaviconFetcherTest {
Favicon result = faviconFetcher.fetch(feed);
Assertions.assertNotNull(result);
Assertions.assertEquals(iconBytes, result.getIcon());
Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType)));
Assertions.assertEquals(iconBytes, result.icon());
Assertions.assertTrue(result.mediaType().isCompatible(MediaType.valueOf(contentType)));
}
@Test

View File

@@ -86,8 +86,8 @@ class YoutubeFaviconFetcherTest {
Favicon result = faviconFetcher.fetch(feed);
Assertions.assertNotNull(result);
Assertions.assertEquals(iconBytes, result.getIcon());
Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType)));
Assertions.assertEquals(iconBytes, result.icon());
Assertions.assertTrue(result.mediaType().isCompatible(MediaType.valueOf(contentType)));
}
@Test
@@ -114,8 +114,8 @@ class YoutubeFaviconFetcherTest {
Favicon result = faviconFetcher.fetch(feed);
Assertions.assertNotNull(result);
Assertions.assertEquals(iconBytes, result.getIcon());
Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType)));
Assertions.assertEquals(iconBytes, result.icon());
Assertions.assertTrue(result.mediaType().isCompatible(MediaType.valueOf(contentType)));
}
@Test
@@ -151,8 +151,8 @@ class YoutubeFaviconFetcherTest {
Favicon result = faviconFetcher.fetch(feed);
Assertions.assertNotNull(result);
Assertions.assertEquals(iconBytes, result.getIcon());
Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType)));
Assertions.assertEquals(iconBytes, result.icon());
Assertions.assertTrue(result.mediaType().isCompatible(MediaType.valueOf(contentType)));
}
@Test

View File

@@ -16,7 +16,10 @@ import com.commafeed.backend.Digests;
import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.HttpGetter.HttpResult;
import com.commafeed.backend.HttpGetter.NotModifiedException;
import com.commafeed.backend.feed.FeedFetcher.FeedFetcherResult;
import com.commafeed.backend.feed.parser.FeedParser;
import com.commafeed.backend.feed.parser.FeedParser.FeedParsingException;
import com.commafeed.backend.feed.parser.FeedParserResult;
import com.commafeed.backend.urlprovider.FeedURLProvider;
@ExtendWith(MockitoExtension.class)
@@ -29,13 +32,33 @@ class FeedFetcherTest {
private HttpGetter getter;
@Mock
private List<FeedURLProvider> urlProviders;
private FeedURLProvider urlProvider;
private FeedFetcher fetcher;
@BeforeEach
void init() {
fetcher = new FeedFetcher(parser, getter, urlProviders);
fetcher = new FeedFetcher(parser, getter, List.of(urlProvider));
}
@Test
void findsUrlInPage() throws Exception {
String htmlUrl = "https://aaa.com";
byte[] html = "html".getBytes();
Mockito.when(getter.get(HttpGetter.HttpRequest.builder(htmlUrl).build()))
.thenReturn(new HttpResult(html, "text/html", null, null, htmlUrl, Duration.ZERO));
Mockito.when(parser.parse(htmlUrl, html)).thenThrow(new FeedParsingException("invalid feed"));
String feedUrl = "https://bbb.com/feed";
byte[] feed = "feed".getBytes();
Mockito.when(getter.get(HttpGetter.HttpRequest.builder(feedUrl).build()))
.thenReturn(new HttpResult(feed, "application/atom+xml", null, null, feedUrl, Duration.ZERO));
Mockito.when(parser.parse(feedUrl, feed)).thenReturn(new FeedParserResult("title", "link", null, null, null, null));
Mockito.when(urlProvider.get(htmlUrl, new String(html))).thenReturn(List.of(feedUrl));
FeedFetcherResult result = fetcher.fetch(htmlUrl, true, null, null, null, null);
Assertions.assertEquals("title", result.feed().title());
}
@Test

View File

@@ -1,34 +1,271 @@
package com.commafeed.backend.feed.parser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
class FeedCleanerTest {
FeedCleaner feedCleaner = new FeedCleaner();
@Test
void testReplaceHtmlEntitiesWithNumericEntities() {
String source = "<source>T&acute;l&acute;phone &prime;</source>";
Assertions.assertEquals("<source>T&#180;l&#180;phone &#8242;</source>", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
@Nested
class RemoveCharactersBeforeFirstXmlTag {
@Test
void removesWhitespaceBeforeXmlTag() {
String xml = " \n\t<feed>content</feed>";
Assertions.assertEquals("<feed>content</feed>", feedCleaner.removeCharactersBeforeFirstXmlTag(xml));
}
@Test
void removesTextBeforeXmlTag() {
String xml = "some text here<feed>content</feed>";
Assertions.assertEquals("<feed>content</feed>", feedCleaner.removeCharactersBeforeFirstXmlTag(xml));
}
@Test
void returnsUnchangedWhenStartsWithXmlTag() {
String xml = "<feed>content</feed>";
Assertions.assertEquals("<feed>content</feed>", feedCleaner.removeCharactersBeforeFirstXmlTag(xml));
}
@Test
void returnsNullWhenNoXmlTagFound() {
String xml = "no xml tags here";
Assertions.assertNull(feedCleaner.removeCharactersBeforeFirstXmlTag(xml));
}
@Test
void returnsNullWhenInputIsNull() {
Assertions.assertNull(feedCleaner.removeCharactersBeforeFirstXmlTag(null));
}
@Test
void returnsNullWhenInputIsEmpty() {
Assertions.assertNull(feedCleaner.removeCharactersBeforeFirstXmlTag(""));
}
@Test
void returnsNullWhenInputIsBlank() {
Assertions.assertNull(feedCleaner.removeCharactersBeforeFirstXmlTag(" \n\t "));
}
@Test
void preservesMultipleXmlTags() {
String xml = "garbage<feed><item>content</item></feed>";
Assertions.assertEquals("<feed><item>content</item></feed>", feedCleaner.removeCharactersBeforeFirstXmlTag(xml));
}
}
@Test
void testRemoveDoctype() {
String source = "<!DOCTYPE html><html><head></head><body></body></html>";
Assertions.assertEquals("<html><head></head><body></body></html>", feedCleaner.removeDoctypeDeclarations(source));
@Nested
class RemoveInvalidXmlCharacters {
@Test
void removesNullCharacter() {
String xml = "<feed>content\u0000here</feed>";
Assertions.assertEquals("<feed>contenthere</feed>", feedCleaner.removeInvalidXmlCharacters(xml));
}
@Test
void removesInvalidControlCharacters() {
String xml = "<feed>content\u0001\u0002\u0003here</feed>";
Assertions.assertEquals("<feed>contenthere</feed>", feedCleaner.removeInvalidXmlCharacters(xml));
}
@Test
void preservesValidXmlCharacters() {
String xml = "<feed>content with\ttab\nand newline</feed>";
Assertions.assertEquals("<feed>content with\ttab\nand newline</feed>", feedCleaner.removeInvalidXmlCharacters(xml));
}
@Test
void preservesUnicodeCharacters() {
String xml = "<feed>café résumé 中文 العربية</feed>";
Assertions.assertEquals("<feed>café résumé 中文 العربية</feed>", feedCleaner.removeInvalidXmlCharacters(xml));
}
@Test
void preservesEmojiCharacters() {
String xml = "<feed>🎮💪✅</feed>";
Assertions.assertEquals("<feed>🎮💪✅</feed>", feedCleaner.removeInvalidXmlCharacters(xml));
}
@Test
void removesMultipleInvalidCharacters() {
String xml = "test\u0000test\u0001test\u0002test";
Assertions.assertEquals("testtesttesttest", feedCleaner.removeInvalidXmlCharacters(xml));
}
@Test
void returnsNullWhenInputIsNull() {
Assertions.assertNull(feedCleaner.removeInvalidXmlCharacters(null));
}
@Test
void returnsNullWhenInputIsEmpty() {
Assertions.assertNull(feedCleaner.removeInvalidXmlCharacters(""));
}
@Test
void returnsNullWhenInputIsBlank() {
Assertions.assertNull(feedCleaner.removeInvalidXmlCharacters(" "));
}
@Test
void handlesStringWithOnlyInvalidCharacters() {
String xml = "\u0000\u0001\u0002";
Assertions.assertEquals("", feedCleaner.removeInvalidXmlCharacters(xml));
}
}
@Test
void testRemoveMultilineDoctype() {
String source = """
<!DOCTYPE
html
>
<html><head></head><body></body></html>""";
Assertions.assertEquals("""
@Nested
class Entities {
@Test
void testReplaceHtmlEntitiesWithNumericEntities() {
String source = "<source>T&acute;l&acute;phone &prime;</source>";
Assertions.assertEquals("<source>T&#180;l&#180;phone &#8242;</source>",
feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
}
<html><head></head><body></body></html>""", feedCleaner.removeDoctypeDeclarations(source));
@Test
void replacesMultipleOccurrencesOfSameEntity() {
String source = "&nbsp;&nbsp;&nbsp;";
Assertions.assertEquals("&#160;&#160;&#160;", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
}
@Test
void preservesTextWithoutEntities() {
String source = "<feed>regular content</feed>";
Assertions.assertEquals("<feed>regular content</feed>", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
}
@Test
void preservesNumericEntities() {
String source = "&#180;&#8242;";
Assertions.assertEquals("&#180;&#8242;", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
}
@Test
void replacesCommonHtmlEntities() {
String source = "&amp;&quot;";
Assertions.assertEquals("&#38;&#34;", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
}
@Test
void handlesPartialEntityMatches() {
String source = "&amplifier";
String result = feedCleaner.replaceHtmlEntitiesWithNumericEntities(source);
Assertions.assertTrue(result.startsWith("&#38;") || result.equals("&amplifier"));
}
@Test
void returnsNullWhenInputIsNull() {
Assertions.assertNull(feedCleaner.replaceHtmlEntitiesWithNumericEntities(null));
}
@Test
void returnsNullWhenInputIsEmpty() {
Assertions.assertNull(feedCleaner.replaceHtmlEntitiesWithNumericEntities(""));
}
@Test
void returnsNullWhenInputIsBlank() {
Assertions.assertNull(feedCleaner.replaceHtmlEntitiesWithNumericEntities(" "));
}
@Test
void handlesEntityAtStartOfString() {
String source = "&amp;test";
Assertions.assertEquals("&#38;test", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
}
@Test
void handlesEntityAtEndOfString() {
String source = "test&amp;";
Assertions.assertEquals("test&#38;", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
}
@Test
void handlesMixedEntitiesAndText() {
String source = "Hello&nbsp;World&excl;&nbsp;Test&period;";
String result = feedCleaner.replaceHtmlEntitiesWithNumericEntities(source);
Assertions.assertTrue(result.contains("&#"));
}
}
}
@Nested
class Doctype {
@Test
void testRemoveDoctype() {
String source = "<!DOCTYPE html><html><head></head><body></body></html>";
Assertions.assertEquals("<html><head></head><body></body></html>", feedCleaner.removeDoctypeDeclarations(source));
}
@Test
void testRemoveMultilineDoctype() {
String source = """
<!DOCTYPE
html
>
<html><head></head><body></body></html>""";
Assertions.assertEquals("""
<html><head></head><body></body></html>""", feedCleaner.removeDoctypeDeclarations(source));
}
@Test
void removesComplexDoctypeWithSystemId() {
String source = "<!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><body></body></html>";
Assertions.assertEquals("<html><body></body></html>", feedCleaner.removeDoctypeDeclarations(source));
}
@Test
void removesComplexDoctypeWithPublicId() {
String source = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html></html>";
Assertions.assertEquals("<html></html>", feedCleaner.removeDoctypeDeclarations(source));
}
@Test
void removesCaseInsensitiveDoctype() {
String source = "<!doctype html><html></html>";
Assertions.assertEquals("<html></html>", feedCleaner.removeDoctypeDeclarations(source));
}
@Test
void removesMixedCaseDoctype() {
String source = "<!DoCtYpE html><html></html>";
Assertions.assertEquals("<html></html>", feedCleaner.removeDoctypeDeclarations(source));
}
@Test
void removesMultipleDoctypeDeclarations() {
String source = "<!DOCTYPE html><!DOCTYPE html><html></html>";
Assertions.assertEquals("<html></html>", feedCleaner.removeDoctypeDeclarations(source));
}
@Test
void preservesContentWithoutDoctype() {
String source = "<html><body>No doctype here</body></html>";
Assertions.assertEquals("<html><body>No doctype here</body></html>", feedCleaner.removeDoctypeDeclarations(source));
}
@Test
void returnsNullWhenInputIsNull() {
Assertions.assertNull(feedCleaner.removeDoctypeDeclarations(null));
}
@Test
void returnsNullWhenInputIsEmpty() {
Assertions.assertNull(feedCleaner.removeDoctypeDeclarations(""));
}
@Test
void returnsNullWhenInputIsBlank() {
Assertions.assertNull(feedCleaner.removeDoctypeDeclarations(" "));
}
@Test
void handlesDoctypeWithExtraWhitespace() {
String source = "<!DOCTYPE html ><html></html>";
Assertions.assertEquals("<html></html>", feedCleaner.removeDoctypeDeclarations(source));
}
}
}

View File

@@ -108,12 +108,12 @@ class DatabaseCleaningServiceTest {
@Test
void cleanEntriesForFeedsExceedingCapacityDeletesOldEntries() {
FeedCapacity feed1 = Mockito.mock(FeedCapacity.class);
Mockito.when(feed1.getId()).thenReturn(1L);
Mockito.when(feed1.getCapacity()).thenReturn(180L);
Mockito.when(feed1.id()).thenReturn(1L);
Mockito.when(feed1.capacity()).thenReturn(180L);
FeedCapacity feed2 = Mockito.mock(FeedCapacity.class);
Mockito.when(feed2.getId()).thenReturn(2L);
Mockito.when(feed2.getCapacity()).thenReturn(120L);
Mockito.when(feed2.id()).thenReturn(2L);
Mockito.when(feed2.capacity()).thenReturn(120L);
Mockito.when(feedEntryDAO.findFeedsExceedingCapacity(50, BATCH_SIZE))
.thenReturn(Arrays.asList(feed1, feed2))

View File

@@ -9,8 +9,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import com.commafeed.backend.model.User;
import com.commafeed.frontend.model.UserModel;
import com.commafeed.frontend.model.request.AdminSaveUserRequest;
import com.commafeed.frontend.model.request.IDRequest;
import com.commafeed.integration.BaseIT;
@@ -51,10 +51,11 @@ class AdminIT extends BaseIT {
}
private long createUser() {
User user = new User();
AdminSaveUserRequest user = new AdminSaveUserRequest();
user.setName("test");
user.setPassword("test".getBytes());
user.setPassword("Test1234!");
user.setEmail("test@test.com");
user.setEnabled(true);
String response = RestAssured.given()
.body(user)
.contentType(ContentType.JSON)

View File

@@ -29,7 +29,6 @@ import com.rometools.rome.io.SyndFeedInput;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import io.restassured.common.mapper.TypeRef;
import io.restassured.http.ContentType;
@QuarkusTest
@@ -109,17 +108,16 @@ class CategoryIT extends BaseIT {
Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl(), categoryId);
Assertions.assertEquals(2, getCategoryEntries(categoryId).getEntries().size());
List<UnreadCount> counts = RestAssured.given()
UnreadCount[] counts = RestAssured.given()
.get("rest/category/unreadCount")
.then()
.statusCode(200)
.extract()
.as(new TypeRef<List<UnreadCount>>() {
});
.as(UnreadCount[].class);
Assertions.assertEquals(1, counts.size());
Assertions.assertEquals(subscriptionId, counts.get(0).getFeedId());
Assertions.assertEquals(2, counts.get(0).getUnreadCount());
Assertions.assertEquals(1, counts.length);
Assertions.assertEquals(subscriptionId, counts[0].getFeedId());
Assertions.assertEquals(2, counts[0].getUnreadCount());
}
@Nested

View File

@@ -77,15 +77,14 @@ The table below shows some elements of the CommaFeed main page that are useful f
article {background-color: lightblue;}
```
|Element Name|Element Description|
|---|---|
|main|The entire web page|
|header|The header area (logo and toolbar)|
|nav|The entire sidebar|
|footer|The footer area at the bottom of the page|
|article|Entire feed entry|
|h3, h2, h1|HTML headers|
| Element Name | Element Description |
|--------------|-------------------------------------------|
| main | The entire web page |
| header | The header area (logo and toolbar) |
| nav | The entire sidebar |
| footer | The footer area at the bottom of the page |
| article | Entire feed entry |
| h3, h2, h1 | HTML headers |
## CommaFeed Class Names
The table below shows the CommaFeed specific class names. To reference a class name in a CSS rule, use a leading period. For example:
@@ -94,28 +93,28 @@ The table below shows the CommaFeed specific class names. To reference a class
.cf-header {background-color: lightblue;}
```
|Class Name|Element Description|
|---|---|
|cf-logo-title|The CommaFeed logo and title in upper left of page|
|cf-logo|The CommaFeed logo|
|cf-title|The CommaFeed title|
|cf-toolbar|The entire toolbar of action buttons at the top of the page|
|cf-action-button|Each button within the toolbar. (Note: also used in feed entry footer.)|
|cf-treesearch|The search box at the top of the sidebar|
|cf-tree|The entire feed tree in the sidebar|
|cf-treenode|All nodes in the feed tree|
|cf-treenode-category|Category nodes in the feed tree|
|cf-treenode-feed|Feed nodes in the feed tree|
|cf-treenode-icon|Icon within feed nodes|
|cf-treenode-unread-count|Unread count within feed nodes|
|cf-badge|The badge for the unread count|
|cf-entries-title|Title of feed currently displayed in the content area|
|cf-entries|All of the feed entries being displayed in the content area|
|cf-header|The header of a feed entry|
|cf-header-title|The first line in the header of a feed entry (the entry title)|
|cf-header-subtitle|The second line in the header of a feed entry (feed name and time of entry)|
|cf-header-details|The third line in the header of a feed entry (typically author, subject, etc.)|
|cf-content|The content (body) of a feed entry|
|cf-footer-divider|The divider between the feed entry content and the feed entry footer|
|cf-footer|The feed entry footer (buttons to share, star, etc.)|
|cf-action-button|Each button within the feed entry footer. (note: also used in toolbar.)|
| Class Name | Element Description |
|--------------------------|--------------------------------------------------------------------------------|
| cf-logo-title | The CommaFeed logo and title in upper left of page |
| cf-logo | The CommaFeed logo |
| cf-title | The CommaFeed title |
| cf-toolbar | The entire toolbar of action buttons at the top of the page |
| cf-action-button | Each button within the toolbar. (Note: also used in feed entry footer.) |
| cf-treesearch | The search box at the top of the sidebar |
| cf-tree | The entire feed tree in the sidebar |
| cf-treenode | All nodes in the feed tree |
| cf-treenode-category | Category nodes in the feed tree |
| cf-treenode-feed | Feed nodes in the feed tree |
| cf-treenode-icon | Icon within feed nodes |
| cf-treenode-unread-count | Unread count within feed nodes |
| cf-badge | The badge for the unread count |
| cf-entries-title | Title of feed currently displayed in the content area |
| cf-entries | All of the feed entries being displayed in the content area |
| cf-header | The header of a feed entry |
| cf-header-title | The first line in the header of a feed entry (the entry title) |
| cf-header-subtitle | The second line in the header of a feed entry (feed name and time of entry) |
| cf-header-details | The third line in the header of a feed entry (typically author, subject, etc.) |
| cf-content | The content (body) of a feed entry |
| cf-footer-divider | The divider between the feed entry content and the feed entry footer |
| cf-footer | The feed entry footer (buttons to share, star, etc.) |
| cf-action-button | Each button within the feed entry footer. (note: also used in toolbar.) |

50
mvnw vendored
View File

@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
@@ -105,14 +105,17 @@ trim() {
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
@@ -130,7 +133,7 @@ maven-mvnd-*bin.*)
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
@@ -227,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
@@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

56
mvnw.cmd vendored
View File

@@ -19,7 +19,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@@ -40,7 +40,7 @@
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
@@ -73,16 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
@@ -134,7 +148,33 @@ if ($distributionSha256Sum) {
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {

View File

@@ -5,7 +5,7 @@
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>5.11.1</version>
<version>5.12.0</version>
<name>CommaFeed</name>
<packaging>pom</packaging>
@@ -22,7 +22,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<version>3.14.1</version>
<configuration>
<parameters>true</parameters>
@@ -44,7 +44,7 @@
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>5.2.0.4988</version>
<version>5.3.0.6276</version>
</plugin>
</plugins>
</build>

View File

@@ -5,7 +5,7 @@
"customManagers:mavenPropertyVersions",
"customManagers:biomeVersions",
":automergePatch",
":automergeDigest",
":automergeDigest",
":automergeBranch",
":automergeRequireAllStatusChecks",
":maintainLockFilesWeekly"
@@ -33,8 +33,8 @@
"description": "IBM Semeru Runtimes uses a custom versioning scheme",
"matchDatasources": "docker",
"matchPackageNames": "ibm-semeru-runtimes",
"versioning": "regex:^open-(?<major>\\d+)?(\\.(?<minor>\\d+))?(\\.(?<patch>\\d+))?([\\._+](?<build>(\\d\\.?)+))?(-(?<compatibility>.*))?$",
"allowedVersions": "/^open-(?:8|11|17|21|25)(?:\\.|-|$)/"
"versioning": "regex:^open-jdk-(?<major>\\d+)?(\\.(?<minor>\\d+))?(\\.(?<patch>\\d+))?([\\._+](?<build>(\\d\\.?)+))?(-(?<compatibility>.*))?$",
"allowedVersions": "/^open-jdk-(?:8|11|17|21|25)(?:\\.|-|$)/"
}
]
}