forked from Archives/Athou_commafeed
Compare commits
348 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d702b3992 | ||
|
|
3bf4a004d4 | ||
|
|
7ac5876d2d | ||
|
|
0f18c612af | ||
|
|
03f4a3c478 | ||
|
|
7069343cf4 | ||
|
|
7fae79f2c5 | ||
|
|
66b714ed39 | ||
|
|
d371ebe354 | ||
|
|
9093d0d5e5 | ||
|
|
1139df0637 | ||
|
|
c1810de316 | ||
|
|
863ced57f8 | ||
|
|
2147aeb4ae | ||
|
|
a810b4fc9a | ||
|
|
abcbb61b4c | ||
|
|
83332223ef | ||
|
|
fd8d981ea0 | ||
|
|
03e3ade09d | ||
|
|
68305f2e00 | ||
|
|
b7d6b06242 | ||
|
|
9098050c5a | ||
|
|
0147ec0a6a | ||
|
|
c6b71605d0 | ||
|
|
64009c82e9 | ||
|
|
5b24cb370f | ||
|
|
2d261cd97b | ||
|
|
9455d91b3d | ||
|
|
cb645c56b4 | ||
|
|
1a6b91dee5 | ||
|
|
8d2edad488 | ||
|
|
522e26b1fa | ||
|
|
259b22c255 | ||
|
|
b61cf82b46 | ||
|
|
4f06f7424c | ||
|
|
d2d65437f8 | ||
|
|
3ae0f7558e | ||
|
|
604801686d | ||
|
|
554d4190ff | ||
|
|
1d71390349 | ||
|
|
fe24c6d682 | ||
|
|
4359d91a23 | ||
|
|
ae42eac7fd | ||
|
|
37a8888a32 | ||
|
|
2d7e065d39 | ||
|
|
35cf640691 | ||
|
|
b308fbe0ad | ||
|
|
d5e2b51b6d | ||
|
|
9b7844542d | ||
|
|
9f6fac0d58 | ||
|
|
f6011dc3f2 | ||
|
|
fdb7fa21f6 | ||
|
|
29bbe41e51 | ||
|
|
004ada8204 | ||
|
|
9a2894944c | ||
|
|
dfcff5029b | ||
|
|
853fc600dd | ||
|
|
a546b21755 | ||
|
|
e40c4e3779 | ||
|
|
60cbf6cff3 | ||
|
|
6d3f4b98d7 | ||
|
|
4812a2b401 | ||
|
|
5f99376d58 | ||
|
|
3e76c142c3 | ||
|
|
28f23a73af | ||
|
|
68b94fed8e | ||
|
|
657b02727c | ||
|
|
7d7a10073c | ||
|
|
9d5f0c791c | ||
|
|
212493e4ff | ||
|
|
9fc8e9c6d7 | ||
|
|
f69ddb71a0 | ||
|
|
290beec0c5 | ||
|
|
dcb2f6f8cd | ||
|
|
d200845906 | ||
|
|
a52e02695d | ||
|
|
febd7c3063 | ||
|
|
d5ae0b99f0 | ||
|
|
8b10c608fc | ||
|
|
0d49b91cc6 | ||
|
|
2af4b83e09 | ||
|
|
cde3ca3d9e | ||
|
|
429798190a | ||
|
|
583db4c70f | ||
|
|
3ec35eec91 | ||
|
|
65bfbfc7fd | ||
|
|
1b93701df2 | ||
|
|
d6debc55f5 | ||
|
|
87fd9ae686 | ||
|
|
3225a3b337 | ||
|
|
4eb98a6c31 | ||
|
|
38a6e2fc98 | ||
|
|
c171cf1487 | ||
|
|
b64a0f1d55 | ||
|
|
3ab124b2db | ||
|
|
d710f3995f | ||
|
|
f53c209082 | ||
|
|
9997be3462 | ||
|
|
c3b06e375c | ||
|
|
1476c5a932 | ||
|
|
8e1c9b9703 | ||
|
|
27c89f7cc7 | ||
|
|
9210198766 | ||
|
|
ce6fa0bf8f | ||
|
|
cd6629b424 | ||
|
|
f25a62ad71 | ||
|
|
cec3c872b6 | ||
|
|
e666e71281 | ||
|
|
3d0c303d41 | ||
|
|
d70a97cf77 | ||
|
|
c67c433258 | ||
|
|
0da6bd5ab6 | ||
|
|
e5cdb1580e | ||
|
|
2c10292073 | ||
|
|
30036a456e | ||
|
|
6349ae9e2b | ||
|
|
8d746669c3 | ||
|
|
0081abc9a7 | ||
|
|
a2f9ac05fe | ||
|
|
6c1f24bad7 | ||
|
|
77cd01e91f | ||
|
|
5487aac81d | ||
|
|
8a6257dc63 | ||
|
|
8146c69ebf | ||
|
|
78ece1abf2 | ||
|
|
baab35c4c5 | ||
|
|
357f9d46f9 | ||
|
|
4eb26302a7 | ||
|
|
a2071d9527 | ||
|
|
65c32c52ff | ||
|
|
fa4353f47d | ||
|
|
46fea1a3e5 | ||
|
|
497cf111d1 | ||
|
|
b1f2fd26e3 | ||
|
|
ae60d4a60f | ||
|
|
ae78e4691d | ||
|
|
9c058cf6d6 | ||
|
|
1ac9af23c5 | ||
|
|
f783bb660e | ||
|
|
e5c271ca1c | ||
|
|
f927247955 | ||
|
|
087e38bec8 | ||
|
|
bab3c8e6b0 | ||
|
|
54ac5d9e27 | ||
|
|
36519d9053 | ||
|
|
ccce4c622d | ||
|
|
4cbf677e55 | ||
|
|
1dbac44a93 | ||
|
|
7e1cfb5cd2 | ||
|
|
df9fb956fa | ||
|
|
16dc383f2b | ||
|
|
0dd7c4851b | ||
|
|
fce4e75eef | ||
|
|
16b578a76d | ||
|
|
483db9881e | ||
|
|
a4053c6084 | ||
|
|
e4f4b46047 | ||
|
|
36f77d5408 | ||
|
|
b3533771dc | ||
|
|
45372cba92 | ||
|
|
dd7fb5bb0d | ||
|
|
41bdc19a22 | ||
|
|
8b7f22021a | ||
|
|
f0160e4d2b | ||
|
|
39d727f98f | ||
|
|
13cc8ac70d | ||
|
|
eb2a219ec8 | ||
|
|
4a59565b20 | ||
|
|
4b7fa96308 | ||
|
|
1ebc8a1e7b | ||
|
|
df2a9aae20 | ||
|
|
dd8287c9d7 | ||
|
|
22fcb08dad | ||
|
|
8c2cf181bd | ||
|
|
69adae36b6 | ||
|
|
8ab700dfa9 | ||
|
|
0177529b45 | ||
|
|
4c6ae3364e | ||
|
|
6df8511a6d | ||
|
|
6fa39517f8 | ||
|
|
c69ce39424 | ||
|
|
a47f6736ac | ||
|
|
79bd7cfff3 | ||
|
|
bc02f23f0f | ||
|
|
715dffb6c8 | ||
|
|
702b3eb971 | ||
|
|
17f62bf491 | ||
|
|
28471302ee | ||
|
|
d8bfdd5d3b | ||
|
|
a36e68e9c3 | ||
|
|
343aed16fb | ||
|
|
142d873c8b | ||
|
|
a94b3e05d3 | ||
|
|
26a79d58f0 | ||
|
|
7c5e68e47d | ||
|
|
ba68627060 | ||
|
|
5bb6a7d4d4 | ||
|
|
76f7999046 | ||
|
|
547693df4f | ||
|
|
0206f8211a | ||
|
|
e061f2e259 | ||
|
|
560ccff04a | ||
|
|
2f0a84557b | ||
|
|
3ae7318ded | ||
|
|
6b7d66e833 | ||
|
|
ec8e594a5c | ||
|
|
858041772e | ||
|
|
b355c04d87 | ||
|
|
4918eaf752 | ||
|
|
80706f006d | ||
|
|
8a7fec1207 | ||
|
|
22a5b6e85e | ||
|
|
a51c533712 | ||
|
|
1f74674a11 | ||
|
|
2eada58ce5 | ||
|
|
31e74bd4a8 | ||
|
|
903f73ee78 | ||
|
|
b21198b239 | ||
|
|
e20ff09457 | ||
|
|
674393eabc | ||
|
|
d78a131713 | ||
|
|
e3816bf05b | ||
|
|
37fe1c60cc | ||
|
|
e705a0d32b | ||
|
|
eb658a644b | ||
|
|
cb905bfc8c | ||
|
|
d0accf6a84 | ||
|
|
55e6f89fc1 | ||
|
|
60695a0ffc | ||
|
|
8a8e4655cd | ||
|
|
2f4b390be1 | ||
|
|
31146cc713 | ||
|
|
9e020ff268 | ||
|
|
7e825192d0 | ||
|
|
8871ae894f | ||
|
|
2808f4b1a2 | ||
|
|
0324c22061 | ||
|
|
57227f9544 | ||
|
|
59c5131f1a | ||
|
|
ccbc07d7d8 | ||
|
|
a0247f0036 | ||
|
|
0979c2767b | ||
|
|
9a9613bba3 | ||
|
|
6451f5f3b7 | ||
|
|
4a4430ce9b | ||
|
|
a38d3dcf72 | ||
|
|
60e1e0d037 | ||
|
|
8071b85b3d | ||
|
|
c867bfb846 | ||
|
|
24b32ab69b | ||
|
|
b1fc65262f | ||
|
|
5af3fea74c | ||
|
|
dde38985e4 | ||
|
|
3f0084fa1c | ||
|
|
8936d4fdce | ||
|
|
4c47b7d838 | ||
|
|
093a9cb8e4 | ||
|
|
f27b3f8933 | ||
|
|
74a9e48e55 | ||
|
|
bafef26ffc | ||
|
|
f8e66170bf | ||
|
|
00bf99fe5a | ||
|
|
05dd66177f | ||
|
|
d5a9e6401e | ||
|
|
660ba67433 | ||
|
|
7ad948065b | ||
|
|
40fcb85c93 | ||
|
|
dcddb80f7b | ||
|
|
8e349aea19 | ||
|
|
3d72725ae0 | ||
|
|
270cb340f5 | ||
|
|
42b5462889 | ||
|
|
b98ab8d011 | ||
|
|
b4264a8ba3 | ||
|
|
a395246d1e | ||
|
|
4b7a2afd07 | ||
|
|
7f49ff20cf | ||
|
|
4e9995e610 | ||
|
|
9f61442cec | ||
|
|
9339847d09 | ||
|
|
39e57cb3ef | ||
|
|
f3a574d05c | ||
|
|
297c76006a | ||
|
|
62d025d827 | ||
|
|
999799ea68 | ||
|
|
331f68253e | ||
|
|
70d3c7a4be | ||
|
|
b3c75a0286 | ||
|
|
9946120304 | ||
|
|
7030a67389 | ||
|
|
eda5ef6965 | ||
|
|
0324479fda | ||
|
|
aeafecb88d | ||
|
|
fde7fbe21a | ||
|
|
7116efc490 | ||
|
|
1ac6058200 | ||
|
|
32b80b64f4 | ||
|
|
9e348767dc | ||
|
|
bce72e1152 | ||
|
|
64aba75be2 | ||
|
|
ca65e13f9a | ||
|
|
54797607c6 | ||
|
|
e174254a95 | ||
|
|
4378e24b49 | ||
|
|
35d276ea98 | ||
|
|
678c89d9c0 | ||
|
|
0a42223de0 | ||
|
|
54d3f3b007 | ||
|
|
3ee58ee464 | ||
|
|
3b5ff016fe | ||
|
|
8a8e786f5e | ||
|
|
2a15f68ffb | ||
|
|
9387e014c1 | ||
|
|
1ef37fcaff | ||
|
|
c5906a481f | ||
|
|
ac0bc916a1 | ||
|
|
5bbe76d56e | ||
|
|
1e6195d74c | ||
|
|
85acea7e64 | ||
|
|
0e4ff99602 | ||
|
|
575d2a0940 | ||
|
|
c548462eef | ||
|
|
3b4cc66b24 | ||
|
|
6d7273f822 | ||
|
|
65014d330a | ||
|
|
d9e3cf0190 | ||
|
|
2d8ee54d28 | ||
|
|
98c3bb780d | ||
|
|
7247c10615 | ||
|
|
0787284d80 | ||
|
|
1c73bffc95 | ||
|
|
6f79815933 | ||
|
|
bb108d594a | ||
|
|
f7716c8834 | ||
|
|
5ba076b1dd | ||
|
|
7861b5a414 | ||
|
|
f36a5988d8 | ||
|
|
8b57240db3 | ||
|
|
7b52efd2d1 | ||
|
|
4901b838e2 | ||
|
|
2313a60f32 | ||
|
|
c38e958588 | ||
|
|
43b1e14f41 | ||
|
|
1e23b3c355 | ||
|
|
85e1556148 | ||
|
|
b65f333a89 | ||
|
|
3dbcbb8280 | ||
|
|
06e464854a |
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -23,13 +23,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout
|
# Checkout
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
- name: Set up GraalVM
|
- name: Set up GraalVM
|
||||||
uses: graalvm/setup-graalvm@aba6a077d71fbfc02138d7470c4ad6e7f85bd2a9 # v1
|
uses: graalvm/setup-graalvm@790e28947b79a9c09c3391c0f18bf8d0f102ed69 # v1
|
||||||
with:
|
with:
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
distribution: "graalvm"
|
distribution: "graalvm"
|
||||||
@@ -67,14 +67,14 @@ jobs:
|
|||||||
|
|
||||||
# Upload artifacts
|
# Upload artifacts
|
||||||
- name: Upload cross-platform app
|
- name: Upload cross-platform app
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||||
if: matrix.os == 'ubuntu-latest' # we only need to upload the cross-platform artifact once per database
|
if: matrix.os == 'ubuntu-latest' # we only need to upload the cross-platform artifact once per database
|
||||||
with:
|
with:
|
||||||
name: commafeed-${{ matrix.database }}-jvm
|
name: commafeed-${{ matrix.database }}-jvm
|
||||||
path: commafeed-server/target/commafeed-*.zip
|
path: commafeed-server/target/commafeed-*.zip
|
||||||
|
|
||||||
- name: Upload native executable
|
- name: Upload native executable
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||||
with:
|
with:
|
||||||
name: commafeed-${{ matrix.database }}-${{ runner.os }}-${{ runner.arch }}
|
name: commafeed-${{ matrix.database }}-${{ runner.os }}-${{ runner.arch }}
|
||||||
path: commafeed-server/target/commafeed-*-runner*
|
path: commafeed-server/target/commafeed-*-runner*
|
||||||
@@ -98,23 +98,23 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout
|
# Checkout
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
|
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||||
|
|
||||||
- name: Install required packages
|
- name: Install required packages
|
||||||
run: sudo apt-get install -y rename unzip
|
run: sudo apt-get install -y rename unzip
|
||||||
|
|
||||||
# Prepare artifacts
|
# Prepare artifacts
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||||
with:
|
with:
|
||||||
pattern: commafeed-${{ matrix.database }}-*
|
pattern: commafeed-${{ matrix.database }}-*
|
||||||
path: ./artifacts
|
path: ./artifacts
|
||||||
@@ -135,7 +135,7 @@ jobs:
|
|||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
- name: Login to Container Registry
|
- name: Login to Container Registry
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
||||||
if: ${{ env.DOCKERHUB_USERNAME != '' }}
|
if: ${{ env.DOCKERHUB_USERNAME != '' }}
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
@@ -215,12 +215,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||||
with:
|
with:
|
||||||
pattern: commafeed-*
|
pattern: commafeed-*
|
||||||
path: ./artifacts
|
path: ./artifacts
|
||||||
@@ -249,12 +249,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Update Docker Hub Description
|
- name: Update Docker Hub Description
|
||||||
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4
|
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|||||||
78
.github/workflows/scorecard.yml
vendored
78
.github/workflows/scorecard.yml
vendored
@@ -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
|
|
||||||
41
.github/workflows/sonar.yml
vendored
41
.github/workflows/sonar.yml
vendored
@@ -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
|
|
||||||
19
.mvn/wrapper/maven-wrapper.properties
vendored
19
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1,18 +1,3 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
wrapperVersion=3.3.4
|
||||||
# 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.
|
|
||||||
distributionType=only-script
|
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
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,5 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [5.12.1]
|
||||||
|
|
||||||
|
- The favicon is now crispier (#1978)
|
||||||
|
- The ReadKit iOS app now works via the Fever API (#1602)
|
||||||
|
|
||||||
|
## [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]
|
## [5.11.1]
|
||||||
|
|
||||||
- The search limit of 3 characters has been removed (#1887)
|
- The search limit of 3 characters has been removed (#1887)
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -26,11 +26,18 @@ Google Reader inspired self-hosted RSS reader, based on Quarkus and React/TypeSc
|
|||||||
- MySQL
|
- MySQL
|
||||||
- MariaDB
|
- MariaDB
|
||||||
|
|
||||||
## Deployment
|
## Usage
|
||||||
|
|
||||||
|
### Public instance
|
||||||
|
|
||||||
|
A free public instance is available at https://www.commafeed.com.
|
||||||
|
|
||||||
|
It has no ads, no tracking, and your data is never exploited or sold to third parties. The service is funded entirely through donations.
|
||||||
|
However, this public instance does have a few limitations compared to self-hosted setups, outlined [here](https://github.com/Athou/commafeed/discussions/1567).
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
Docker is the easiest way to get started with CommaFeed.
|
Docker is the easiest way to get started with self-hosted CommaFeed.
|
||||||
|
|
||||||
Docker images are built automatically and are available at https://hub.docker.com/r/athou/commafeed
|
Docker images are built automatically and are available at https://hub.docker.com/r/athou/commafeed
|
||||||
|
|
||||||
@@ -103,7 +110,7 @@ There are multiple ways to configure CommaFeed:
|
|||||||
- Environment variables (keys in UPPER_CASE)
|
- Environment variables (keys in UPPER_CASE)
|
||||||
- a `.env` file in the working directory (keys in UPPER_CASE)
|
- a `.env` file in the working directory (keys in UPPER_CASE)
|
||||||
|
|
||||||
The properties file is recommended because CommaFeed will be able to warn about invalid properties and typos.
|
When in doubt, the properties file is recommended because CommaFeed will be able to warn about invalid properties and typos.
|
||||||
|
|
||||||
All [CommaFeed settings](https://athou.github.io/commafeed/documentation) are optional and have sensible default values.
|
All [CommaFeed settings](https://athou.github.io/commafeed/documentation) are optional and have sensible default values.
|
||||||
|
|
||||||
|
|||||||
3
commafeed-client/.gitignore
vendored
3
commafeed-client/.gitignore
vendored
@@ -23,9 +23,6 @@ dist-ssr
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
# rollup-plugin-visualizer
|
|
||||||
/stats.html
|
|
||||||
|
|
||||||
# vite
|
# vite
|
||||||
vite.config.ts.timestamp-*.mjs
|
vite.config.ts.timestamp-*.mjs
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
|
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"indentStyle": "space",
|
"indentStyle": "space",
|
||||||
"indentWidth": 4,
|
"indentWidth": 4,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
|||||||
2566
commafeed-client/package-lock.json
generated
2566
commafeed-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,67 +17,65 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@fontsource/open-sans": "^5.2.7",
|
"@fontsource/open-sans": "^5.2.7",
|
||||||
"@lingui/core": "^5.5.0",
|
"@lingui/core": "^5.7.0",
|
||||||
"@lingui/react": "^5.5.0",
|
"@lingui/react": "^5.7.0",
|
||||||
"@mantine/core": "^8.3.1",
|
"@mantine/core": "^8.3.10",
|
||||||
"@mantine/form": "^8.3.1",
|
"@mantine/form": "^8.3.10",
|
||||||
"@mantine/hooks": "^8.3.1",
|
"@mantine/hooks": "^8.3.10",
|
||||||
"@mantine/modals": "^8.3.1",
|
"@mantine/modals": "^8.3.10",
|
||||||
"@mantine/notifications": "^8.3.1",
|
"@mantine/notifications": "^8.3.10",
|
||||||
"@mantine/spotlight": "^8.3.1",
|
"@mantine/spotlight": "^8.3.10",
|
||||||
"@monaco-editor/react": "^4.7.0",
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"@reduxjs/toolkit": "^2.9.0",
|
"@reduxjs/toolkit": "^2.11.2",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.13.2",
|
||||||
"dayjs": "^1.11.17",
|
"dayjs": "^1.11.19",
|
||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
"interweave": "^13.1.1",
|
"interweave": "^13.1.1",
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.55.1",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"react": "^19.1.1",
|
"react": "^19.2.3",
|
||||||
"react-async-hook": "^4.0.0",
|
"react-async-hook": "^4.0.0",
|
||||||
"react-contexify": "^6.0.0",
|
"react-contexify": "^6.0.0",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-dom": "^19.2.3",
|
||||||
"react-dom": "^19.1.1",
|
|
||||||
"react-draggable": "^4.5.0",
|
"react-draggable": "^4.5.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-infinite-scroller": "^1.2.6",
|
"react-infinite-scroller": "^1.2.6",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.9.1",
|
"react-router-dom": "^7.11.0",
|
||||||
"react-swipeable": "^7.0.2",
|
"react-swipeable": "^7.0.2",
|
||||||
"style-to-object": "^1.0.9",
|
"style-to-object": "^1.0.14",
|
||||||
"throttle-debounce": "^5.0.2",
|
"throttle-debounce": "^5.0.2",
|
||||||
"tinycon": "^0.6.8",
|
"tinycon": "^0.6.8",
|
||||||
"tss-react": "^4.9.19",
|
"tss-react": "^4.9.20",
|
||||||
"websocket-heartbeat-js": "^1.1.3"
|
"websocket-heartbeat-js": "^1.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.2.4",
|
"@biomejs/biome": "^2.3.10",
|
||||||
"@lingui/babel-plugin-lingui-macro": "^5.5.0",
|
"@lingui/babel-plugin-lingui-macro": "^5.7.0",
|
||||||
"@lingui/cli": "^5.5.0",
|
"@lingui/cli": "^5.7.0",
|
||||||
"@lingui/vite-plugin": "^5.5.0",
|
"@lingui/vite-plugin": "^5.7.0",
|
||||||
"@testing-library/jest-dom": "^6.8.0",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.1",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/mousetrap": "^1.6.15",
|
"@types/mousetrap": "^1.6.15",
|
||||||
"@types/react": "^19.1.13",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-infinite-scroller": "^1.2.5",
|
"@types/react-infinite-scroller": "^1.2.5",
|
||||||
"@types/throttle-debounce": "^5.0.2",
|
"@types/throttle-debounce": "^5.0.2",
|
||||||
"@types/tinycon": "^0.6.7",
|
"@types/tinycon": "^0.6.7",
|
||||||
"@vitejs/plugin-react": "^5.0.3",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"babel-plugin-react-compiler": "^19.1.0-rc.3",
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.4.0",
|
||||||
"rollup-plugin-visualizer": "^6.0.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript": "^5.9.2",
|
"vite": "^7.3.0",
|
||||||
"vite": "^7.1.6",
|
"vite-plugin-checker": "^0.12.0",
|
||||||
"vite-plugin-checker": "^0.10.3",
|
"vite-tsconfig-paths": "^6.0.3",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vitest": "^4.0.16",
|
||||||
"vitest": "^3.2.4",
|
"yaml": "^2.8.2"
|
||||||
"yaml": "^2.8.1"
|
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"react-infinite-scroller": {
|
"react-infinite-scroller": {
|
||||||
"react": "^19.1.1"
|
"react": "^19.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,16 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>5.11.1</version>
|
<version>5.12.1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commafeed-client</artifactId>
|
<artifactId>commafeed-client</artifactId>
|
||||||
<name>CommaFeed Client</name>
|
<name>CommaFeed Client</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<sonar.sources>package.json,src</sonar.sources>
|
|
||||||
<sonar.coverage.exclusions>**/*</sonar.coverage.exclusions>
|
|
||||||
|
|
||||||
<!-- renovate: datasource=node-version depName=node -->
|
<!-- renovate: datasource=node-version depName=node -->
|
||||||
<node.version>v22.19.0</node.version>
|
<node.version>v24.12.0</node.version>
|
||||||
<!-- renovate: datasource=npm depName=npm -->
|
<!-- renovate: datasource=npm depName=npm -->
|
||||||
<npm.version>11.6.0</npm.version>
|
<npm.version>11.7.0</npm.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -26,7 +23,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.github.eirslett</groupId>
|
<groupId>com.github.eirslett</groupId>
|
||||||
<artifactId>frontend-maven-plugin</artifactId>
|
<artifactId>frontend-maven-plugin</artifactId>
|
||||||
<version>1.15.1</version>
|
<version>2.0.0</version>
|
||||||
<?m2e ignore?>
|
<?m2e ignore?>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
@@ -75,7 +72,7 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
<version>3.3.1</version>
|
<version>3.4.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy web interface to resources</id>
|
<id>copy web interface to resources</id>
|
||||||
|
|||||||
62
commafeed-client/public/favicon.svg
Normal file
62
commafeed-client/public/favicon.svg
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="393.84613"
|
||||||
|
width="393.84613"
|
||||||
|
viewBox="0 0 5.0480766 5.0480766"
|
||||||
|
version="1.1"
|
||||||
|
id="svg3"
|
||||||
|
sodipodi:docname="favicon.svg"
|
||||||
|
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs3" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview3"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="1.21875"
|
||||||
|
inkscape:cx="207.17949"
|
||||||
|
inkscape:cy="187.07692"
|
||||||
|
inkscape:window-width="1440"
|
||||||
|
inkscape:window-height="855"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg3" />
|
||||||
|
<rect
|
||||||
|
fill="#f88a14"
|
||||||
|
rx="0.53846151"
|
||||||
|
ry="0.53846151"
|
||||||
|
height="5.0480766"
|
||||||
|
width="5.0480766"
|
||||||
|
id="rect1"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
style="stroke-width:0.769231" />
|
||||||
|
<path
|
||||||
|
d="m 1.3450904,0.64548657 c 2.9002,0 2.9002,2.91010003 2.9002,2.91010003"
|
||||||
|
fill="none"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="0.78125"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
d="m 1.3377904,1.9915866 c 1.5705,-0.00908 1.5705,1.5639 1.5705,1.5639"
|
||||||
|
fill="none"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="0.78125"
|
||||||
|
id="path2" />
|
||||||
|
<path
|
||||||
|
d="m 2.0192904,3.5227866 c 0,0.23366 -0.10712,0.47418 -0.24663,0.6537 -0.1814,0.2333 -0.5705,0.5618 -0.6913,0.5653 0.0402,-0.0662 0.263,-0.5654 0.2563,-0.5654 -0.36423004,0 -0.65950004,-0.29265 -0.65950004,-0.65365 0,-0.361 0.29527,-0.65365 0.65950004,-0.65365 0.36423,0 0.68159,0.29265 0.68159,0.65365 z"
|
||||||
|
fill="#ffffff"
|
||||||
|
id="path3" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -5,7 +5,6 @@ import { ModalsProvider } from "@mantine/modals"
|
|||||||
import { Notifications } from "@mantine/notifications"
|
import { Notifications } from "@mantine/notifications"
|
||||||
import type React from "react"
|
import type React from "react"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { isSafari } from "react-device-detect"
|
|
||||||
import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom"
|
import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom"
|
||||||
import Tinycon from "tinycon"
|
import Tinycon from "tinycon"
|
||||||
import { Constants } from "@/app/constants"
|
import { Constants } from "@/app/constants"
|
||||||
@@ -200,6 +199,7 @@ export function App() {
|
|||||||
useI18n()
|
useI18n()
|
||||||
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
||||||
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
||||||
|
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -213,12 +213,7 @@ export function App() {
|
|||||||
<BrowserExtensionBadgeUnreadCountHandler />
|
<BrowserExtensionBadgeUnreadCountHandler />
|
||||||
<CustomJsHandler />
|
<CustomJsHandler />
|
||||||
<CustomCssHandler />
|
<CustomCssHandler />
|
||||||
|
<DisablePullToRefresh enabled={disablePullToRefresh} />
|
||||||
{/* 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 />}
|
|
||||||
|
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<RedirectHandler />
|
<RedirectHandler />
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ export interface Settings {
|
|||||||
mobileFooter: boolean
|
mobileFooter: boolean
|
||||||
unreadCountTitle: boolean
|
unreadCountTitle: boolean
|
||||||
unreadCountFavicon: boolean
|
unreadCountFavicon: boolean
|
||||||
|
disablePullToRefresh: boolean
|
||||||
primaryColor?: string
|
primaryColor?: string
|
||||||
sharingSettings: SharingSettings
|
sharingSettings: SharingSettings
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { createSlice, isAnyOf, type PayloadAction } from "@reduxjs/toolkit"
|
|||||||
import type { LocalSettings, Settings, UserModel, ViewMode } from "@/app/types"
|
import type { LocalSettings, Settings, UserModel, ViewMode } from "@/app/types"
|
||||||
import {
|
import {
|
||||||
changeCustomContextMenu,
|
changeCustomContextMenu,
|
||||||
|
changeDisablePullToRefresh,
|
||||||
changeEntriesToKeepOnTopWhenScrolling,
|
changeEntriesToKeepOnTopWhenScrolling,
|
||||||
changeExternalLinkIconDisplayMode,
|
changeExternalLinkIconDisplayMode,
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
@@ -135,6 +136,10 @@ export const userSlice = createSlice({
|
|||||||
if (!state.settings) return
|
if (!state.settings) return
|
||||||
state.settings.unreadCountFavicon = action.meta.arg
|
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) => {
|
builder.addCase(changePrimaryColor.pending, (state, action) => {
|
||||||
if (!state.settings) return
|
if (!state.settings) return
|
||||||
state.settings.primaryColor = action.meta.arg
|
state.settings.primaryColor = action.meta.arg
|
||||||
@@ -143,6 +148,7 @@ export const userSlice = createSlice({
|
|||||||
if (!state.settings) return
|
if (!state.settings) return
|
||||||
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
|
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.addMatcher(
|
builder.addMatcher(
|
||||||
isAnyOf(
|
isAnyOf(
|
||||||
changeLanguage.fulfilled,
|
changeLanguage.fulfilled,
|
||||||
@@ -159,6 +165,7 @@ export const userSlice = createSlice({
|
|||||||
changeMobileFooter.fulfilled,
|
changeMobileFooter.fulfilled,
|
||||||
changeUnreadCountTitle.fulfilled,
|
changeUnreadCountTitle.fulfilled,
|
||||||
changeUnreadCountFavicon.fulfilled,
|
changeUnreadCountFavicon.fulfilled,
|
||||||
|
changeDisablePullToRefresh.fulfilled,
|
||||||
changePrimaryColor.fulfilled,
|
changePrimaryColor.fulfilled,
|
||||||
changeSharingSetting.fulfilled
|
changeSharingSetting.fulfilled
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -122,6 +122,15 @@ export const changeUnreadCountFavicon = createAppAsyncThunk("settings/unreadCoun
|
|||||||
client.user.saveSettings({ ...settings, unreadCountFavicon })
|
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) => {
|
export const changePrimaryColor = createAppAsyncThunk("settings/primaryColor", (primaryColor: string, thunkApi) => {
|
||||||
const { settings } = thunkApi.getState().user
|
const { settings } = thunkApi.getState().user
|
||||||
if (!settings) return
|
if (!settings) return
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
html,
|
|
||||||
body {
|
|
||||||
overscroll-behavior: none;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
export const DisablePullToRefresh = () => {
|
export const DisablePullToRefresh = ({ enabled }: { enabled: boolean | undefined }) => {
|
||||||
import("./DisablePullToRefresh.css")
|
return enabled ? <style>{`html, body { overscroll-behavior: none; }`}</style> : null
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export function FeedEntry(props: Readonly<FeedEntryProps>) {
|
|||||||
component="article"
|
component="article"
|
||||||
id={Constants.dom.entryId(props.entry)}
|
id={Constants.dom.entryId(props.entry)}
|
||||||
data-id={props.entry.id}
|
data-id={props.entry.id}
|
||||||
|
data-feed-id={props.entry.feedId}
|
||||||
withBorder
|
withBorder
|
||||||
radius={borderRadius}
|
radius={borderRadius}
|
||||||
className={cx(classes.paper, {
|
className={cx(classes.paper, {
|
||||||
|
|||||||
@@ -61,19 +61,21 @@ export function FeedEntryContextMenu(props: Readonly<FeedEntryContextMenuProps>)
|
|||||||
|
|
||||||
<Separator />
|
<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 && (
|
{props.entry.markable && (
|
||||||
<Item onClick={async () => await dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))}>
|
<>
|
||||||
<Group>
|
<Item onClick={async () => await dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}>
|
||||||
{props.entry.read ? <TbMail size={iconSize} /> : <TbMailOpened size={iconSize} />}
|
<Group>
|
||||||
{props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>}
|
{props.entry.starred ? <TbStarOff size={iconSize} /> : <TbStar size={iconSize} />}
|
||||||
</Group>
|
{props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
|
||||||
</Item>
|
</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))}>
|
<Item onClick={async () => await dispatch(markEntriesUpToEntry(props.entry))}>
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useAppDispatch, useAppSelector } from "@/app/store"
|
|||||||
import type { IconDisplayMode, ScrollMode, SharingSettings } from "@/app/types"
|
import type { IconDisplayMode, ScrollMode, SharingSettings } from "@/app/types"
|
||||||
import {
|
import {
|
||||||
changeCustomContextMenu,
|
changeCustomContextMenu,
|
||||||
|
changeDisablePullToRefresh,
|
||||||
changeEntriesToKeepOnTopWhenScrolling,
|
changeEntriesToKeepOnTopWhenScrolling,
|
||||||
changeExternalLinkIconDisplayMode,
|
changeExternalLinkIconDisplayMode,
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
@@ -42,6 +43,7 @@ export function DisplaySettings() {
|
|||||||
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
|
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
|
||||||
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
||||||
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
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 sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
|
||||||
const primaryColor = useAppSelector(state => state.user.settings?.primaryColor) || Constants.theme.defaultPrimaryColor
|
const primaryColor = useAppSelector(state => state.user.settings?.primaryColor) || Constants.theme.defaultPrimaryColor
|
||||||
const { _ } = useLingui()
|
const { _ } = useLingui()
|
||||||
@@ -211,6 +213,13 @@ export function DisplaySettings() {
|
|||||||
onChange={async e => await dispatch(changeScrollMarks(e.currentTarget.checked))}
|
onChange={async e => await dispatch(changeScrollMarks(e.currentTarget.checked))}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
label={<Trans>Disable "Pull to refresh" browser behavior</Trans>}
|
||||||
|
description={<Trans>This setting can cause scrolling issues on some browsers (e.g. Safari)</Trans>}
|
||||||
|
checked={disablePullToRefresh}
|
||||||
|
onChange={async e => await dispatch(changeDisablePullToRefresh(e.currentTarget.checked))}
|
||||||
|
/>
|
||||||
|
|
||||||
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
|
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
|
||||||
|
|
||||||
<SimpleGrid cols={2}>
|
<SimpleGrid cols={2}>
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "تنازلي"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "الموضوع"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "تبديل قراءة حالة الإدخال الحالي"
|
msgstr "تبديل قراءة حالة الإدخال الحالي"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ msgstr "Accions"
|
|||||||
|
|
||||||
#: src/components/content/add/AddCategory.tsx
|
#: src/components/content/add/AddCategory.tsx
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr "Afegir"
|
msgstr "Afegeix"
|
||||||
|
|
||||||
#: src/pages/app/AddPage.tsx
|
#: src/pages/app/AddPage.tsx
|
||||||
msgid "Add category"
|
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
|
#: src/components/content/add/Subscribe.tsx
|
||||||
msgid "Analyze feed"
|
msgid "Analyze feed"
|
||||||
msgstr "Analitzar el feed"
|
msgstr "Analitza el canal"
|
||||||
|
|
||||||
#: src/components/AnnouncementDialog.tsx
|
#: src/components/AnnouncementDialog.tsx
|
||||||
msgid "Announcement"
|
msgid "Announcement"
|
||||||
@@ -91,11 +91,11 @@ msgstr "Anunci"
|
|||||||
|
|
||||||
#: src/components/settings/ProfileSettings.tsx
|
#: src/components/settings/ProfileSettings.tsx
|
||||||
msgid "API key"
|
msgid "API key"
|
||||||
msgstr "clau API"
|
msgstr "Clau API"
|
||||||
|
|
||||||
#: src/pages/app/CategoryDetailsPage.tsx
|
#: src/pages/app/CategoryDetailsPage.tsx
|
||||||
msgid "Are you sure you want to delete category <0>{categoryName}</0>?"
|
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
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
msgid "Are you sure you want to delete user <0>{userName}</0> ?"
|
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
|
#: src/pages/app/FeedDetailsPage.tsx
|
||||||
msgid "Are you sure you want to unsubscribe from <0>{feedName}</0>?"
|
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
|
#: src/components/header/Header.tsx
|
||||||
msgid "Asc"
|
msgid "Asc"
|
||||||
@@ -131,7 +131,7 @@ msgstr "Enrere"
|
|||||||
|
|
||||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||||
msgid "Back to log in"
|
msgid "Back to log in"
|
||||||
msgstr "Tornar a iniciar sessió"
|
msgstr "Torna a iniciar sessió"
|
||||||
|
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
msgid "Blue"
|
msgid "Blue"
|
||||||
@@ -283,6 +283,10 @@ msgstr "Desc"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "Detallat"
|
msgstr "Detallat"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr "Desactiva el comportament \"Arrossega per actualitzar"\ del navegador"
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -322,7 +326,7 @@ msgstr "Edita l'usuari"
|
|||||||
#: src/components/admin/UserEdit.tsx
|
#: src/components/admin/UserEdit.tsx
|
||||||
#: src/pages/admin/AdminUsersPage.tsx
|
#: src/pages/admin/AdminUsersPage.tsx
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr "activat"
|
msgstr "Activat"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Enter"
|
msgid "Enter"
|
||||||
@@ -430,7 +434,7 @@ msgstr "Vés a la documentació de l'API."
|
|||||||
|
|
||||||
#: src/pages/app/AboutPage.tsx
|
#: src/pages/app/AboutPage.tsx
|
||||||
msgid "Goodies"
|
msgid "Goodies"
|
||||||
msgstr "Bones"
|
msgstr "Extres"
|
||||||
|
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
msgid "Grape"
|
msgid "Grape"
|
||||||
@@ -650,7 +654,7 @@ msgstr "el més vell primer"
|
|||||||
|
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
msgid "On desktop"
|
msgid "On desktop"
|
||||||
msgstr "A l'scriptori"
|
msgstr "A l'escriptori"
|
||||||
|
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
msgid "On mobile"
|
msgid "On mobile"
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "Aquesta és la vostra clau de l'API. Es pot utilitzar per a algunes operacions de l'API de només lectura i permet accedir a l'API Fever. Utilitzeu el formulari de la part inferior de la pàgina per generar una nova clau d'API."
|
msgstr "Aquesta és la vostra clau de l'API. Es pot utilitzar per a algunes operacions de l'API de només lectura i permet accedir a l'API Fever. Utilitzeu el formulari de la part inferior de la pàgina per generar una nova clau d'API."
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr "Aquesta configuració pot causar problemes de desplaçament en alguns navegadors (per exemple, Safari)"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Canvia l'estat de lectura de l'entrada actual"
|
msgstr "Canvia l'estat de lectura de l'entrada actual"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Téma"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Přepne stav čtení aktuálního záznamu"
|
msgstr "Přepne stav čtení aktuálního záznamu"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Rhag"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Thema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Toglo statws darllen y cofnod cyfredol"
|
msgstr "Toglo statws darllen y cofnod cyfredol"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Skift læsestatus for den aktuelle post"
|
msgstr "Skift læsestatus for den aktuelle post"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Beschr"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "Detailliert"
|
msgstr "Detailliert"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Thema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "Dies ist Ihr API-Schlüssel. Er kann für einige schreibgeschützte API-Vorgänge verwendet werden und ermöglicht den Zugriff auf die Fever-API. Verwenden Sie das Formular unten auf der Seite, um einen neuen API-Schlüssel zu generieren"
|
msgstr "Dies ist Ihr API-Schlüssel. Er kann für einige schreibgeschützte API-Vorgänge verwendet werden und ermöglicht den Zugriff auf die Fever-API. Verwenden Sie das Formular unten auf der Seite, um einen neuen API-Schlüssel zu generieren"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Lesestatus des aktuellen Eintrags umschalten"
|
msgstr "Lesestatus des aktuellen Eintrags umschalten"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Desc"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "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/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Theme"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgstr "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Toggle read status of current entry"
|
msgstr "Toggle read status of current entry"
|
||||||
|
|||||||
@@ -284,6 +284,10 @@ msgstr "Desc"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "Detallado"
|
msgstr "Detallado"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -997,6 +1001,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "Esta es su clave API. Se puede utilizar para algunas operaciones API de solo lectura y otorga acceso a Fever API. Utilice el formulario en la parte inferior de la página para generar una nueva clave API"
|
msgstr "Esta es su clave API. Se puede utilizar para algunas operaciones API de solo lectura y otorga acceso a Fever API. Utilice el formulario en la parte inferior de la página para generar una nueva clave API"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Alternar estado de lectura de la entrada actual"
|
msgstr "Alternar estado de lectura de la entrada actual"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "توصیف"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "تم"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید"
|
msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Teema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Vaihda nykyisen merkinnän lukutila"
|
msgstr "Vaihda nykyisen merkinnän lukutila"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Descendant"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "Vue détaillée"
|
msgstr "Vue détaillée"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr "Désactiver la fonction \"tirer pour rafraîchir\" du navigateur"
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Thème"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "Ceci est votre clef API. Elle peut être utilisée pour certaines opérations en lecture seule et donne accès à l'API Fever. Utilisez le formulaire en bas de la page pour générer une nouvelle clef API"
|
msgstr "Ceci est votre clef API. Elle peut être utilisée pour certaines opérations en lecture seule et donne accès à l'API Fever. Utilisez le formulaire en bas de la page pour générer une nouvelle clef API"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr "Cette fonctinnalité peut causer des problèmes de défilement sur certains navigateurs (Safari, par exemple)"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Marquer l'entrée actuelle comme lue/non lue"
|
msgstr "Marquer l'entrée actuelle comme lue/non lue"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Téma"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Az aktuális bejegyzés olvasási állapotának váltása"
|
msgstr "Az aktuális bejegyzés olvasási állapotának váltása"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Beralih status baca entri saat ini"
|
msgstr "Beralih status baca entri saat ini"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Commuta lo stato di lettura della voce corrente"
|
msgstr "Commuta lo stato di lettura della voce corrente"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "説明"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "詳細"
|
msgstr "詳細"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "テーマ"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "これはあなたのAPIキーです。いくつかの読み取り専用API操作に使用できます。これにより、Fever APIへのアクセスが可能になります。ページの下部のフォームを使用して新しいAPIキーを生成します。"
|
msgstr "これはあなたのAPIキーです。いくつかの読み取り専用API操作に使用できます。これにより、Fever APIへのアクセスが可能になります。ページの下部のフォームを使用して新しいAPIキーを生成します。"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "現在のエントリーの読み取りステータスを切り替えます"
|
msgstr "現在のエントリーの読み取りステータスを切り替えます"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "설명"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "테마"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "현재 항목의 읽기 상태 전환"
|
msgstr "현재 항목의 읽기 상태 전환"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Dec"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Togol status bacaan entri semasa"
|
msgstr "Togol status bacaan entri semasa"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Veksle lesestatus for gjeldende oppføring"
|
msgstr "Veksle lesestatus for gjeldende oppføring"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Beschrijving"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Thema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Toggle leesstatus van huidige invoer"
|
msgstr "Toggle leesstatus van huidige invoer"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Veksle lesestatus for gjeldende oppføring"
|
msgstr "Veksle lesestatus for gjeldende oppføring"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Opis"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Motyw"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Przełącz stan odczytu bieżącego wpisu"
|
msgstr "Przełącz stan odczytu bieżącego wpisu"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Desc"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "Detalhado"
|
msgstr "Detalhado"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "Esta é sua chave de API. Ela pode ser usada para algumas operações somente leitura da API e concede acesso à API do Fever. Use o formulário abaixo para gerar uma nova chave de API"
|
msgstr "Esta é sua chave de API. Ela pode ser usada para algumas operações somente leitura da API e concede acesso à API do Fever. Use o formulário abaixo para gerar uma nova chave de API"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Alternar o status de leitura da entrada atual"
|
msgstr "Alternar o status de leitura da entrada atual"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "По убыванию"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "Подробно"
|
msgstr "Подробно"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Тема"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "Это ваш ключ API. Он может использоваться для некоторых операций API только для чтения и предоставляет доступ к API Fever. Чтобы сгенерировать новый ключ API, воспользуйтесь формой в нижней части страницы"
|
msgstr "Это ваш ключ API. Он может использоваться для некоторых операций API только для чтения и предоставляет доступ к API Fever. Чтобы сгенерировать новый ключ API, воспользуйтесь формой в нижней части страницы"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Переключить статус чтения текущей записи"
|
msgstr "Переключить статус чтения текущей записи"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Téma"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Prepne stav čítania aktuálneho záznamu"
|
msgstr "Prepne stav čítania aktuálneho záznamu"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr ""
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Växla lässtatus för aktuell post"
|
msgstr "Växla lässtatus för aktuell post"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "Açılış"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "Tema"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "Geçerli girişin okuma durumunu değiştir"
|
msgstr "Geçerli girişin okuma durumunu değiştir"
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ msgstr "降序"
|
|||||||
msgid "Detailed"
|
msgid "Detailed"
|
||||||
msgstr "详细"
|
msgstr "详细"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "Disable \"Pull to refresh\" browser behavior"
|
||||||
|
msgstr "禁用浏览器的“下拉刷新”功能"
|
||||||
|
|
||||||
#: src/components/header/ProfileMenu.tsx
|
#: src/components/header/ProfileMenu.tsx
|
||||||
#: src/components/settings/DisplaySettings.tsx
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
#: src/pages/app/SettingsPage.tsx
|
#: src/pages/app/SettingsPage.tsx
|
||||||
@@ -996,6 +1000,10 @@ msgstr "主题"
|
|||||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||||
msgstr "这是您的API 密钥,它可以被用于Fever API的只读操作及访问授权。使用页面底部的表单生成一个新的API密钥。"
|
msgstr "这是您的API 密钥,它可以被用于Fever API的只读操作及访问授权。使用页面底部的表单生成一个新的API密钥。"
|
||||||
|
|
||||||
|
#: src/components/settings/DisplaySettings.tsx
|
||||||
|
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||||
|
msgstr "此设置在部分浏览器(例如 Safari)中可能导致滚动问题"
|
||||||
|
|
||||||
#: src/components/KeyboardShortcutsHelp.tsx
|
#: src/components/KeyboardShortcutsHelp.tsx
|
||||||
msgid "Toggle read status of current entry"
|
msgid "Toggle read status of current entry"
|
||||||
msgstr "切换当前条目的阅读状态"
|
msgstr "切换当前条目的阅读状态"
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ export function MetricsPage() {
|
|||||||
setLoading: state => ({ ...state, loading: true }),
|
setLoading: state => ({ ...state, loading: true }),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { execute } = query
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => query.execute(), 2000)
|
const interval = setInterval(() => execute(), 2000)
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [query.execute])
|
}, [execute])
|
||||||
|
|
||||||
if (!query.result) return <Loader />
|
if (!query.result) return <Loader />
|
||||||
const { meters, gauges } = query.result.data
|
const { meters, gauges } = query.result.data
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function FilteringExpressionDescription() {
|
|||||||
|
|
||||||
export function FeedDetailsPage() {
|
export function FeedDetailsPage() {
|
||||||
const { id } = useParams()
|
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 apiKey = useAppSelector(state => state.user.profile?.apiKey)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { lingui } from "@lingui/vite-plugin"
|
import { lingui } from "@lingui/vite-plugin"
|
||||||
import react from "@vitejs/plugin-react"
|
import react from "@vitejs/plugin-react"
|
||||||
import { visualizer } from "rollup-plugin-visualizer"
|
|
||||||
import { defineConfig } from "vite"
|
import { defineConfig } from "vite"
|
||||||
import checker from "vite-plugin-checker"
|
import checker from "vite-plugin-checker"
|
||||||
import tsconfigPaths from "vite-tsconfig-paths"
|
import tsconfigPaths from "vite-tsconfig-paths"
|
||||||
@@ -21,7 +20,6 @@ export default defineConfig(() => ({
|
|||||||
}),
|
}),
|
||||||
lingui(),
|
lingui(),
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
visualizer(),
|
|
||||||
checker({
|
checker({
|
||||||
typescript: true,
|
typescript: true,
|
||||||
biome: {
|
biome: {
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>5.11.1</version>
|
<version>5.12.1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commafeed-server</artifactId>
|
<artifactId>commafeed-server</artifactId>
|
||||||
<name>CommaFeed Server</name>
|
<name>CommaFeed Server</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<quarkus.version>3.26.4</quarkus.version>
|
<quarkus.version>3.30.5</quarkus.version>
|
||||||
<querydsl.version>7.0</querydsl.version>
|
<querydsl.version>7.1</querydsl.version>
|
||||||
<rome.version>2.1.0</rome.version>
|
<rome.version>2.1.0</rome.version>
|
||||||
|
|
||||||
<build.database>h2</build.database>
|
<build.database>h2</build.database>
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
<version>3.5.1</version>
|
<version>3.6.3</version>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
<version>3.7.1</version>
|
<version>3.8.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
@@ -192,6 +192,8 @@
|
|||||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||||
<quarkus.datasource.db-kind>${build.database}</quarkus.datasource.db-kind>
|
<quarkus.datasource.db-kind>${build.database}</quarkus.datasource.db-kind>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
|
<!-- fix for java.lang.NoClassDefFoundError: Could not initialize class org.jboss.threads.JDKSpecific$ThreadAccess (#1938) -->
|
||||||
|
<argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine>
|
||||||
</configuration>
|
</configuration>
|
||||||
<!-- failsafe plugin does not seem to be able to pick up dependencies declared in profiles -->
|
<!-- failsafe plugin does not seem to be able to pick up dependencies declared in profiles -->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -220,7 +222,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
<version>0.8.13</version>
|
<version>0.8.14</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<!-- excluding SACParserCSS21TokenManager because it causes a "Method too large" exception -->
|
<!-- excluding SACParserCSS21TokenManager because it causes a "Method too large" exception -->
|
||||||
<excludes>
|
<excludes>
|
||||||
@@ -299,7 +301,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.puppycrawl.tools</groupId>
|
<groupId>com.puppycrawl.tools</groupId>
|
||||||
<artifactId>checkstyle</artifactId>
|
<artifactId>checkstyle</artifactId>
|
||||||
<version>11.0.1</version>
|
<version>12.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<executions>
|
<executions>
|
||||||
@@ -328,7 +330,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.diffplug.spotless</groupId>
|
<groupId>com.diffplug.spotless</groupId>
|
||||||
<artifactId>spotless-maven-plugin</artifactId>
|
<artifactId>spotless-maven-plugin</artifactId>
|
||||||
<version>2.46.1</version>
|
<version>3.1.0</version>
|
||||||
<?m2e ignore?>
|
<?m2e ignore?>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
@@ -357,7 +359,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed-client</artifactId>
|
<artifactId>commafeed-client</artifactId>
|
||||||
<version>5.11.1</version>
|
<version>5.12.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- compile-time processors -->
|
<!-- compile-time processors -->
|
||||||
@@ -497,7 +499,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ibm.icu</groupId>
|
<groupId>com.ibm.icu</groupId>
|
||||||
<artifactId>icu4j</artifactId>
|
<artifactId>icu4j</artifactId>
|
||||||
<version>77.1</version>
|
<version>78.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.sourceforge.cssparser</groupId>
|
<groupId>net.sourceforge.cssparser</groupId>
|
||||||
@@ -512,18 +514,17 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||||
<artifactId>httpclient5</artifactId>
|
<artifactId>httpclient5</artifactId>
|
||||||
<version>5.5</version>
|
<version>5.6</version>
|
||||||
</dependency>
|
|
||||||
<!-- add brotli support for httpclient5 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.brotli</groupId>
|
|
||||||
<artifactId>dec</artifactId>
|
|
||||||
<version>0.1.2</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.github.hakky54</groupId>
|
<groupId>io.github.hakky54</groupId>
|
||||||
<artifactId>ayza-for-apache5</artifactId>
|
<artifactId>ayza-for-apache5</artifactId>
|
||||||
<version>10.0.0</version>
|
<version>10.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.brotli</groupId>
|
||||||
|
<artifactId>dec</artifactId>
|
||||||
|
<version>0.1.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
@@ -540,7 +541,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkiverse.playwright</groupId>
|
<groupId>io.quarkiverse.playwright</groupId>
|
||||||
<artifactId>quarkus-playwright</artifactId>
|
<artifactId>quarkus-playwright</artifactId>
|
||||||
<version>2.1.3</version>
|
<version>2.3.1</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -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:d88c854ca5506a04dd2cdaedb98dcf23d6b1b077aebaf738d5c3c5d8c94fff20
|
||||||
EXPOSE 8082
|
EXPOSE 8082
|
||||||
|
|
||||||
RUN mkdir -p /commafeed/data
|
RUN mkdir -p /commafeed/data
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM debian:13.1@sha256:833c135acfe9521d7a0035a296076f98c182c542a2b6b5a0fd7063d355d696be
|
FROM debian:13.2@sha256:c71b05eac0b20adb4cdcc9f7b052227efd7da381ad10bb92f972e8eae7c6cdc9
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
EXPOSE 8082
|
EXPOSE 8082
|
||||||
@@ -6,7 +6,7 @@ EXPOSE 8082
|
|||||||
RUN mkdir -p /commafeed/data
|
RUN mkdir -p /commafeed/data
|
||||||
VOLUME /commafeed/data
|
VOLUME /commafeed/data
|
||||||
|
|
||||||
COPY artifacts/commafeed-*-${TARGETARCH}-runner /commafeed/application
|
COPY artifacts/commafeed-*-${TARGETARCH}-runner /commafeed/commafeed
|
||||||
WORKDIR /commafeed
|
WORKDIR /commafeed
|
||||||
|
|
||||||
CMD ["./application"]
|
CMD ["./commafeed"]
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ import java.time.Duration;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.InstantSource;
|
import java.time.InstantSource;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
import jakarta.inject.Singleton;
|
import jakarta.inject.Singleton;
|
||||||
import jakarta.ws.rs.core.CacheControl;
|
import jakarta.ws.rs.core.CacheControl;
|
||||||
@@ -24,6 +27,9 @@ import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
|||||||
import org.apache.hc.client5.http.config.ConnectionConfig;
|
import org.apache.hc.client5.http.config.ConnectionConfig;
|
||||||
import org.apache.hc.client5.http.config.RequestConfig;
|
import org.apache.hc.client5.http.config.RequestConfig;
|
||||||
import org.apache.hc.client5.http.config.TlsConfig;
|
import org.apache.hc.client5.http.config.TlsConfig;
|
||||||
|
import org.apache.hc.client5.http.entity.DeflateInputStream;
|
||||||
|
import org.apache.hc.client5.http.entity.InputStreamFactory;
|
||||||
|
import org.apache.hc.client5.http.entity.compress.ContentCoding;
|
||||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||||
@@ -41,6 +47,7 @@ import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
|||||||
import org.apache.hc.core5.http.message.BasicHeader;
|
import org.apache.hc.core5.http.message.BasicHeader;
|
||||||
import org.apache.hc.core5.util.TimeValue;
|
import org.apache.hc.core5.util.TimeValue;
|
||||||
import org.apache.hc.core5.util.Timeout;
|
import org.apache.hc.core5.util.Timeout;
|
||||||
|
import org.brotli.dec.BrotliInputStream;
|
||||||
import org.jboss.resteasy.reactive.common.headers.CacheControlDelegate;
|
import org.jboss.resteasy.reactive.common.headers.CacheControlDelegate;
|
||||||
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
import com.codahale.metrics.MetricRegistry;
|
||||||
@@ -58,7 +65,6 @@ import lombok.EqualsAndHashCode;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Lombok;
|
import lombok.Lombok;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Value;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import nl.altindag.ssl.SSLFactory;
|
import nl.altindag.ssl.SSLFactory;
|
||||||
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
|
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
|
||||||
@@ -127,9 +133,9 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int code = response.getCode();
|
int code = response.code();
|
||||||
if (code == HttpStatus.SC_TOO_MANY_REQUESTS || code == HttpStatus.SC_SERVICE_UNAVAILABLE && response.getRetryAfter() != null) {
|
if (code == HttpStatus.SC_TOO_MANY_REQUESTS || code == HttpStatus.SC_SERVICE_UNAVAILABLE && response.retryAfter() != null) {
|
||||||
throw new TooManyRequestsException(response.getRetryAfter());
|
throw new TooManyRequestsException(response.retryAfter());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
||||||
@@ -140,16 +146,16 @@ public class HttpGetter {
|
|||||||
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
|
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
|
||||||
}
|
}
|
||||||
|
|
||||||
String lastModifiedHeader = response.getLastModifiedHeader();
|
String lastModifiedHeader = response.lastModifiedHeader();
|
||||||
String eTagHeader = response.getETagHeader();
|
String eTagHeader = response.eTagHeader();
|
||||||
|
|
||||||
Duration validFor = Optional.ofNullable(response.getCacheControl())
|
Duration validFor = Optional.ofNullable(response.cacheControl())
|
||||||
.filter(cc -> cc.getMaxAge() >= 0)
|
.filter(cc -> cc.getMaxAge() >= 0)
|
||||||
.map(cc -> Duration.ofSeconds(cc.getMaxAge()))
|
.map(cc -> Duration.ofSeconds(cc.getMaxAge()))
|
||||||
.orElse(Duration.ZERO);
|
.orElse(Duration.ZERO);
|
||||||
|
|
||||||
return new HttpResult(response.getContent(), response.getContentType(), lastModifiedHeader, eTagHeader,
|
return new HttpResult(response.content(), response.contentType(), lastModifiedHeader, eTagHeader, response.urlAfterRedirect(),
|
||||||
response.getUrlAfterRedirect(), validFor);
|
validFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureHttpScheme(String scheme) throws SchemeNotAllowedException {
|
private void ensureHttpScheme(String scheme) throws SchemeNotAllowedException {
|
||||||
@@ -254,8 +260,8 @@ public class HttpGetter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes = ByteStreams.limit(input, maxBytes).readAllBytes();
|
byte[] bytes = ByteStreams.limit(input, maxBytes + 1).readAllBytes();
|
||||||
if (bytes.length == maxBytes) {
|
if (bytes.length > maxBytes) {
|
||||||
throw new IOException("Response size exceeds the maximum allowed size (%s bytes)".formatted(maxBytes));
|
throw new IOException("Response size exceeds the maximum allowed size (%s bytes)".formatted(maxBytes));
|
||||||
}
|
}
|
||||||
return bytes;
|
return bytes;
|
||||||
@@ -288,6 +294,11 @@ public class HttpGetter {
|
|||||||
headers.add(new BasicHeader(HttpHeaders.PRAGMA, "No-cache"));
|
headers.add(new BasicHeader(HttpHeaders.PRAGMA, "No-cache"));
|
||||||
headers.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"));
|
headers.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"));
|
||||||
|
|
||||||
|
Map<String, InputStreamFactory> contentDecoderMap = new LinkedHashMap<>();
|
||||||
|
contentDecoderMap.put(ContentCoding.GZIP.token(), GZIPInputStream::new);
|
||||||
|
contentDecoderMap.put(ContentCoding.DEFLATE.token(), DeflateInputStream::new);
|
||||||
|
contentDecoderMap.put(ContentCoding.BROTLI.token(), BrotliInputStream::new);
|
||||||
|
|
||||||
return HttpClientBuilder.create()
|
return HttpClientBuilder.create()
|
||||||
.useSystemProperties()
|
.useSystemProperties()
|
||||||
.disableAutomaticRetries()
|
.disableAutomaticRetries()
|
||||||
@@ -297,6 +308,7 @@ public class HttpGetter {
|
|||||||
.setConnectionManager(connectionManager)
|
.setConnectionManager(connectionManager)
|
||||||
.evictExpiredConnections()
|
.evictExpiredConnections()
|
||||||
.evictIdleConnections(TimeValue.of(idleConnectionsEvictionInterval))
|
.evictIdleConnections(TimeValue.of(idleConnectionsEvictionInterval))
|
||||||
|
.setContentDecoderRegistry(new LinkedHashMap<>(contentDecoderMap))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +319,7 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return CacheBuilder.newBuilder()
|
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())
|
.maximumWeight(cacheConfig.maximumMemorySize().asLongValue())
|
||||||
.expireAfterWrite(cacheConfig.expiration())
|
.expireAfterWrite(cacheConfig.expiration())
|
||||||
.build();
|
.build();
|
||||||
@@ -398,26 +410,12 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
private record HttpResponse(int code, String lastModifiedHeader, String eTagHeader, CacheControl cacheControl, Instant retryAfter,
|
||||||
private static class HttpResponse {
|
byte[] content, String contentType, String urlAfterRedirect) {
|
||||||
int code;
|
|
||||||
String lastModifiedHeader;
|
|
||||||
String eTagHeader;
|
|
||||||
CacheControl cacheControl;
|
|
||||||
Instant retryAfter;
|
|
||||||
byte[] content;
|
|
||||||
String contentType;
|
|
||||||
String urlAfterRedirect;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
public record HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, String urlAfterRedirect,
|
||||||
public static class HttpResult {
|
Duration validFor) {
|
||||||
byte[] content;
|
|
||||||
String contentType;
|
|
||||||
String lastModifiedSince;
|
|
||||||
String eTag;
|
|
||||||
String urlAfterRedirect;
|
|
||||||
Duration validFor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import org.netpreserve.urlcanon.Canonicalizer;
|
|||||||
import org.netpreserve.urlcanon.ParsedUrl;
|
import org.netpreserve.urlcanon.ParsedUrl;
|
||||||
|
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
|
@Slf4j
|
||||||
public class Urls {
|
public class Urls {
|
||||||
|
|
||||||
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
|
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
|
||||||
@@ -42,7 +44,12 @@ public class Urls {
|
|||||||
return null;
|
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) {
|
public static String removeTrailingSlash(String url) {
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ import com.commafeed.backend.model.QFeedEntry;
|
|||||||
import com.querydsl.core.Tuple;
|
import com.querydsl.core.Tuple;
|
||||||
import com.querydsl.core.types.dsl.NumberExpression;
|
import com.querydsl.core.types.dsl.NumberExpression;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
||||||
|
|
||||||
@@ -64,10 +61,6 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
|||||||
return delete(list);
|
return delete(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
public record FeedCapacity(Long id, Long capacity) {
|
||||||
@Getter
|
|
||||||
public static class FeedCapacity {
|
|
||||||
private Long id;
|
|
||||||
private Long capacity;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,9 +129,9 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||||
for (FeedEntryKeyword keyword : keywords) {
|
for (FeedEntryKeyword keyword : keywords) {
|
||||||
BooleanBuilder or = new BooleanBuilder();
|
BooleanBuilder or = new BooleanBuilder();
|
||||||
or.or(CONTENT.content.containsIgnoreCase(keyword.getKeyword()));
|
or.or(CONTENT.content.containsIgnoreCase(keyword.keyword()));
|
||||||
or.or(CONTENT.title.containsIgnoreCase(keyword.getKeyword()));
|
or.or(CONTENT.title.containsIgnoreCase(keyword.keyword()));
|
||||||
if (keyword.getMode() == Mode.EXCLUDE) {
|
if (keyword.mode() == Mode.EXCLUDE) {
|
||||||
or.not();
|
or.not();
|
||||||
}
|
}
|
||||||
query.where(or);
|
query.where(or);
|
||||||
|
|||||||
@@ -71,11 +71,10 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
url = Urls.removeTrailingSlash(url) + "/favicon.ico";
|
url = Urls.removeTrailingSlash(url) + "/favicon.ico";
|
||||||
log.debug("getting root icon at {}", url);
|
log.debug("getting root icon at {}", url);
|
||||||
HttpResult result = getter.get(url);
|
HttpResult result = getter.get(url);
|
||||||
bytes = result.getContent();
|
bytes = result.content();
|
||||||
contentType = result.getContentType();
|
contentType = result.contentType();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url);
|
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||||
log.trace("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidIconResponse(bytes, contentType)) {
|
if (!isValidIconResponse(bytes, contentType)) {
|
||||||
@@ -89,10 +88,9 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
Document doc;
|
Document doc;
|
||||||
try {
|
try {
|
||||||
HttpResult result = getter.get(url);
|
HttpResult result = getter.get(url);
|
||||||
doc = Jsoup.parse(new String(result.getContent()), url);
|
doc = Jsoup.parse(new String(result.content()), url);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("Failed to retrieve page to find icon");
|
log.debug("Failed to retrieve page to find icon", e);
|
||||||
log.trace("Failed to retrieve page to find icon", e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +113,10 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
String contentType;
|
String contentType;
|
||||||
try {
|
try {
|
||||||
HttpResult result = getter.get(href);
|
HttpResult result = getter.get(href);
|
||||||
bytes = result.getContent();
|
bytes = result.content();
|
||||||
contentType = result.getContentType();
|
contentType = result.contentType();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("Failed to retrieve icon found in page {}", href);
|
log.debug("Failed to retrieve icon found in page {}", href, e);
|
||||||
log.trace("Failed to retrieve icon found in page {}", href, e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ public class FacebookFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
log.debug("Getting Facebook user's icon, {}", url);
|
log.debug("Getting Facebook user's icon, {}", url);
|
||||||
|
|
||||||
HttpResult iconResult = getter.get(iconUrl);
|
HttpResult iconResult = getter.get(iconUrl);
|
||||||
bytes = iconResult.getContent();
|
bytes = iconResult.content();
|
||||||
contentType = iconResult.getContentType();
|
contentType = iconResult.contentType();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("Failed to retrieve Facebook icon", e);
|
log.debug("Failed to retrieve Facebook icon", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,13 @@ package com.commafeed.backend.favicon;
|
|||||||
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Favicon {
|
public record Favicon(byte[] icon, MediaType mediaType) {
|
||||||
|
|
||||||
private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.valueOf("image/x-icon");
|
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) {
|
public Favicon(byte[] icon, String contentType) {
|
||||||
this(icon, parseMediaType(contentType));
|
this(icon, parseMediaType(contentType));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HttpResult iconResult = getter.get(thumbnailUrl.asText());
|
HttpResult iconResult = getter.get(thumbnailUrl.asText());
|
||||||
bytes = iconResult.getContent();
|
bytes = iconResult.content();
|
||||||
contentType = iconResult.getContentType();
|
contentType = iconResult.contentType();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("Failed to retrieve YouTube icon", e);
|
log.debug("Failed to retrieve YouTube icon", e);
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
.queryParam("key", googleAuthKey)
|
.queryParam("key", googleAuthKey)
|
||||||
.queryParam("forUsername", userId)
|
.queryParam("forUsername", userId)
|
||||||
.build();
|
.build();
|
||||||
return getter.get(uri.toString()).getContent();
|
return getter.get(uri.toString()).content();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] fetchForChannel(String googleAuthKey, String channelId)
|
private byte[] fetchForChannel(String googleAuthKey, String channelId)
|
||||||
@@ -114,7 +114,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
.queryParam("key", googleAuthKey)
|
.queryParam("key", googleAuthKey)
|
||||||
.queryParam("id", channelId)
|
.queryParam("id", channelId)
|
||||||
.build();
|
.build();
|
||||||
return getter.get(uri.toString()).getContent();
|
return getter.get(uri.toString()).content();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] fetchForPlaylist(String googleAuthKey, String playlistId)
|
private byte[] fetchForPlaylist(String googleAuthKey, String playlistId)
|
||||||
@@ -124,7 +124,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
|||||||
.queryParam("key", googleAuthKey)
|
.queryParam("key", googleAuthKey)
|
||||||
.queryParam("id", playlistId)
|
.queryParam("id", playlistId)
|
||||||
.build();
|
.build();
|
||||||
byte[] playlistBytes = getter.get(uri.toString()).getContent();
|
byte[] playlistBytes = getter.get(uri.toString()).content();
|
||||||
|
|
||||||
JsonNode channelId = objectMapper.readTree(playlistBytes).at(PLAYLIST_CHANNEL_ID);
|
JsonNode channelId = objectMapper.readTree(playlistBytes).at(PLAYLIST_CHANNEL_ID);
|
||||||
if (channelId.isMissingNode()) {
|
if (channelId.isMissingNode()) {
|
||||||
|
|||||||
@@ -5,23 +5,15 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A keyword used in a search query
|
* A keyword used in a search query
|
||||||
*/
|
*/
|
||||||
@Getter
|
public record FeedEntryKeyword(String keyword, Mode mode) {
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class FeedEntryKeyword {
|
|
||||||
|
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
INCLUDE, EXCLUDE
|
INCLUDE, EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String keyword;
|
|
||||||
private final Mode mode;
|
|
||||||
|
|
||||||
public static List<FeedEntryKeyword> fromQueryString(String keywords) {
|
public static List<FeedEntryKeyword> fromQueryString(String keywords) {
|
||||||
List<FeedEntryKeyword> list = new ArrayList<>();
|
List<FeedEntryKeyword> list = new ArrayList<>();
|
||||||
if (keywords != null) {
|
if (keywords != null) {
|
||||||
|
|||||||
@@ -50,20 +50,20 @@ public class FeedFetcher {
|
|||||||
log.debug("Fetching feed {}", feedUrl);
|
log.debug("Fetching feed {}", feedUrl);
|
||||||
|
|
||||||
HttpResult result = getter.get(HttpRequest.builder(feedUrl).lastModified(lastModified).eTag(eTag).build());
|
HttpResult result = getter.get(HttpRequest.builder(feedUrl).lastModified(lastModified).eTag(eTag).build());
|
||||||
byte[] content = result.getContent();
|
byte[] content = result.content();
|
||||||
|
|
||||||
FeedParserResult parserResult;
|
FeedParserResult parserResult;
|
||||||
try {
|
try {
|
||||||
parserResult = parser.parse(result.getUrlAfterRedirect(), content);
|
parserResult = parser.parse(result.urlAfterRedirect(), content);
|
||||||
} catch (FeedParsingException e) {
|
} catch (FeedParsingException e) {
|
||||||
if (extractFeedUrlFromHtml) {
|
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)) {
|
if (StringUtils.isNotBlank(extractedUrl)) {
|
||||||
feedUrl = extractedUrl;
|
feedUrl = extractedUrl;
|
||||||
|
|
||||||
result = getter.get(HttpRequest.builder(extractedUrl).lastModified(lastModified).eTag(eTag).build());
|
result = getter.get(HttpRequest.builder(extractedUrl).lastModified(lastModified).eTag(eTag).build());
|
||||||
content = result.getContent();
|
content = result.content();
|
||||||
parserResult = parser.parse(result.getUrlAfterRedirect(), content);
|
parserResult = parser.parse(result.urlAfterRedirect(), content);
|
||||||
} else {
|
} else {
|
||||||
throw new NoFeedFoundException(e);
|
throw new NoFeedFoundException(e);
|
||||||
}
|
}
|
||||||
@@ -76,26 +76,24 @@ public class FeedFetcher {
|
|||||||
throw new IOException("Feed content is empty.");
|
throw new IOException("Feed content is empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean lastModifiedHeaderValueChanged = !Strings.CS.equals(lastModified, result.getLastModifiedSince());
|
boolean lastModifiedHeaderValueChanged = !Strings.CS.equals(lastModified, result.lastModifiedSince());
|
||||||
boolean etagHeaderValueChanged = !Strings.CS.equals(eTag, result.getETag());
|
boolean etagHeaderValueChanged = !Strings.CS.equals(eTag, result.eTag());
|
||||||
|
|
||||||
String hash = Digests.sha1Hex(content);
|
String hash = Digests.sha1Hex(content);
|
||||||
if (lastContentHash != null && lastContentHash.equals(hash)) {
|
if (lastContentHash != null && lastContentHash.equals(hash)) {
|
||||||
log.debug("content hash not modified: {}", feedUrl);
|
log.debug("content hash not modified: {}", feedUrl);
|
||||||
throw new NotModifiedException("content hash not modified",
|
throw new NotModifiedException("content hash not modified", lastModifiedHeaderValueChanged ? result.lastModifiedSince() : null,
|
||||||
lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null,
|
etagHeaderValueChanged ? result.eTag() : null);
|
||||||
etagHeaderValueChanged ? result.getETag() : null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastPublishedDate != null && lastPublishedDate.equals(parserResult.lastPublishedDate())) {
|
if (lastPublishedDate != null && lastPublishedDate.equals(parserResult.lastPublishedDate())) {
|
||||||
log.debug("publishedDate not modified: {}", feedUrl);
|
log.debug("publishedDate not modified: {}", feedUrl);
|
||||||
throw new NotModifiedException("publishedDate not modified",
|
throw new NotModifiedException("publishedDate not modified", lastModifiedHeaderValueChanged ? result.lastModifiedSince() : null,
|
||||||
lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null,
|
etagHeaderValueChanged ? result.eTag() : null);
|
||||||
etagHeaderValueChanged ? result.getETag() : null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FeedFetcherResult(parserResult, result.getUrlAfterRedirect(), result.getLastModifiedSince(), result.getETag(), hash,
|
return new FeedFetcherResult(parserResult, result.urlAfterRedirect(), result.lastModifiedSince(), result.eTag(), hash,
|
||||||
result.getValidFor());
|
result.validFor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractFeedUrl(List<FeedURLProvider> urlProviders, String url, String urlContent) {
|
private static String extractFeedUrl(List<FeedURLProvider> urlProviders, String url, String urlContent) {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import com.commafeed.frontend.ws.WebSocketMessageBuilder;
|
|||||||
import com.commafeed.frontend.ws.WebSocketSessions;
|
import com.commafeed.frontend.ws.WebSocketSessions;
|
||||||
import com.google.common.util.concurrent.Striped;
|
import com.google.common.util.concurrent.Striped;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,11 +170,7 @@ public class FeedRefreshUpdater {
|
|||||||
WebSocketMessageBuilder.newFeedEntries(sub, unreadCount)));
|
WebSocketMessageBuilder.newFeedEntries(sub, unreadCount)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
private record AddEntryResult(boolean processed, boolean inserted, Set<FeedSubscription> subscriptionsForWhichEntryIsUnread) {
|
||||||
private static class AddEntryResult {
|
|
||||||
private final boolean processed;
|
|
||||||
private final boolean inserted;
|
|
||||||
private final Set<FeedSubscription> subscriptionsForWhichEntryIsUnread;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.ibm.icu.text.CharsetDetector;
|
|||||||
import com.ibm.icu.text.CharsetMatch;
|
import com.ibm.icu.text.CharsetMatch;
|
||||||
|
|
||||||
@Singleton
|
@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
|
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
|
||||||
|
|||||||
@@ -8,41 +8,47 @@ import jakarta.inject.Singleton;
|
|||||||
import org.ahocorasick.trie.Emit;
|
import org.ahocorasick.trie.Emit;
|
||||||
import org.ahocorasick.trie.Trie;
|
import org.ahocorasick.trie.Trie;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jdom2.Verifier;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class FeedCleaner {
|
public class FeedCleaner {
|
||||||
|
|
||||||
private static final Pattern DOCTYPE_PATTERN = Pattern.compile("<!DOCTYPE[^>]*>", Pattern.CASE_INSENSITIVE);
|
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)) {
|
if (StringUtils.isBlank(xml)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
boolean firstTagFound = false;
|
int pos = xml.indexOf('<');
|
||||||
for (int i = 0; i < xml.length(); i++) {
|
return pos < 0 ? null : xml.substring(pos);
|
||||||
char c = xml.charAt(i);
|
}
|
||||||
|
|
||||||
if (!firstTagFound) {
|
String removeInvalidXmlCharacters(String xml) {
|
||||||
if (c == '<') {
|
if (StringUtils.isBlank(xml)) {
|
||||||
firstTagFound = true;
|
return null;
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c >= 32 || c == 9 || c == 10 || c == 13) {
|
|
||||||
if (!Character.isHighSurrogate(c) && !Character.isLowSurrogate(c)) {
|
|
||||||
sb.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return sb.toString();
|
|
||||||
|
return xml.codePoints()
|
||||||
|
.filter(Verifier::isXMLCharacter)
|
||||||
|
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
|
||||||
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/40836618
|
// 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.
|
// Create a buffer sufficiently large that re-allocations are minimized.
|
||||||
StringBuilder sb = new StringBuilder(source.length() << 1);
|
StringBuilder sb = new StringBuilder(source.length() << 1);
|
||||||
|
|
||||||
@@ -63,7 +69,11 @@ class FeedCleaner {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String removeDoctypeDeclarations(String xml) {
|
String removeDoctypeDeclarations(String xml) {
|
||||||
|
if (StringUtils.isBlank(xml)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return DOCTYPE_PATTERN.matcher(xml).replaceAll("");
|
return DOCTYPE_PATTERN.matcher(xml).replaceAll("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import jakarta.inject.Singleton;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.SystemProperties;
|
||||||
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
|
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
|
||||||
import org.jdom2.Element;
|
import org.jdom2.Element;
|
||||||
import org.jdom2.Namespace;
|
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.feed.synd.SyndLinkImpl;
|
||||||
import com.rometools.rome.io.SyndFeedInput;
|
import com.rometools.rome.io.SyndFeedInput;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses raw xml into a FeedParserResult object
|
* Parses raw xml into a FeedParserResult object
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FeedParser {
|
public class FeedParser {
|
||||||
|
|
||||||
@@ -55,15 +53,25 @@ public class FeedParser {
|
|||||||
private final EncodingDetector encodingDetector;
|
private final EncodingDetector encodingDetector;
|
||||||
private final FeedCleaner feedCleaner;
|
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 {
|
public FeedParserResult parse(String feedUrl, byte[] xml) throws FeedParsingException {
|
||||||
try {
|
try {
|
||||||
Charset encoding = encodingDetector.getEncoding(xml);
|
Charset encoding = encodingDetector.getEncoding(xml);
|
||||||
String xmlString = feedCleaner.trimInvalidXmlCharacters(new String(xml, encoding));
|
|
||||||
|
String xmlString = feedCleaner.clean(new String(xml, encoding));
|
||||||
if (xmlString == null) {
|
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));
|
InputSource source = new InputSource(new StringReader(xmlString));
|
||||||
SyndFeed feed = new SyndFeedInput().build(source);
|
SyndFeed feed = new SyndFeedInput().build(source);
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ public class UserSettings extends AbstractModel {
|
|||||||
private boolean mobileFooter;
|
private boolean mobileFooter;
|
||||||
private boolean unreadCountTitle;
|
private boolean unreadCountTitle;
|
||||||
private boolean unreadCountFavicon;
|
private boolean unreadCountFavicon;
|
||||||
|
private boolean disablePullToRefresh;
|
||||||
|
|
||||||
private boolean email;
|
private boolean email;
|
||||||
private boolean gmail;
|
private boolean gmail;
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String computedFeverApiKey = Digests.md5Hex(user.getName() + ":" + user.getApiKey());
|
String computedFeverApiKey = Digests.md5Hex(user.getName() + ":" + user.getApiKey());
|
||||||
if (!computedFeverApiKey.equals(feverApiKey)) {
|
if (!computedFeverApiKey.equalsIgnoreCase(feverApiKey)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ public class DatabaseCleaningService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final FeedCapacity feed : feeds) {
|
for (final FeedCapacity feed : feeds) {
|
||||||
long remaining = feed.getCapacity() - maxFeedCapacity;
|
long remaining = feed.capacity() - maxFeedCapacity;
|
||||||
do {
|
do {
|
||||||
final long rem = remaining;
|
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);
|
entriesDeletedMeter.mark(deleted);
|
||||||
total += deleted;
|
total += deleted;
|
||||||
remaining -= deleted;
|
remaining -= deleted;
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ public class Settings implements Serializable {
|
|||||||
@Schema(description = "show unread count in the favicon", required = true)
|
@Schema(description = "show unread count in the favicon", required = true)
|
||||||
private boolean unreadCountFavicon;
|
private boolean unreadCountFavicon;
|
||||||
|
|
||||||
|
@Schema(description = "disable pull to refresh", required = true)
|
||||||
|
private boolean disablePullToRefresh;
|
||||||
|
|
||||||
@Schema(description = "primary theme color to use in the UI")
|
@Schema(description = "primary theme color to use in the UI")
|
||||||
private String primaryColor;
|
private String primaryColor;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import java.io.Serializable;
|
|||||||
|
|
||||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||||
|
|
||||||
|
import com.commafeed.security.password.ValidPassword;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@@ -21,6 +23,7 @@ public class AdminSaveUserRequest implements Serializable {
|
|||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@Schema(description = "user password")
|
@Schema(description = "user password")
|
||||||
|
@ValidPassword
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Schema(description = "account status", required = true)
|
@Schema(description = "account status", required = true)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class RegistrationRequest implements Serializable {
|
|||||||
@Size(min = 3, max = 32)
|
@Size(min = 3, max = 32)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Schema(description = "password, minimum 6 characters", required = true)
|
@Schema(description = "password", required = true)
|
||||||
@NotEmpty
|
@NotEmpty
|
||||||
@ValidPassword
|
@ValidPassword
|
||||||
private String password;
|
private String password;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.util.Set;
|
|||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
import jakarta.inject.Singleton;
|
import jakarta.inject.Singleton;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.POST;
|
import jakarta.ws.rs.POST;
|
||||||
@@ -65,7 +66,7 @@ public class AdminREST {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Save or update a user",
|
summary = "Save or update a user",
|
||||||
description = "Save or update a user. If the id is not specified, a new user will be created")
|
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);
|
||||||
Preconditions.checkNotNull(req.getName());
|
Preconditions.checkNotNull(req.getName());
|
||||||
|
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ public class FeedREST {
|
|||||||
|
|
||||||
Feed feed = subscription.getFeed();
|
Feed feed = subscription.getFeed();
|
||||||
Favicon icon = feedService.fetchFavicon(feed);
|
Favicon icon = feedService.fetchFavicon(feed);
|
||||||
return Response.ok(icon.getIcon(), icon.getMediaType()).build();
|
return Response.ok(icon.icon(), icon.mediaType()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class ServerREST {
|
|||||||
url = ImageProxyUrl.decode(url);
|
url = ImageProxyUrl.decode(url);
|
||||||
try {
|
try {
|
||||||
HttpResult result = httpGetter.get(url);
|
HttpResult result = httpGetter.get(url);
|
||||||
return Response.ok(result.getContent()).build();
|
return Response.ok(result.content()).build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Response.status(Status.SERVICE_UNAVAILABLE).entity(e.getMessage()).build();
|
return Response.status(Status.SERVICE_UNAVAILABLE).entity(e.getMessage()).build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ public class UserREST {
|
|||||||
s.setMobileFooter(settings.isMobileFooter());
|
s.setMobileFooter(settings.isMobileFooter());
|
||||||
s.setUnreadCountTitle(settings.isUnreadCountTitle());
|
s.setUnreadCountTitle(settings.isUnreadCountTitle());
|
||||||
s.setUnreadCountFavicon(settings.isUnreadCountFavicon());
|
s.setUnreadCountFavicon(settings.isUnreadCountFavicon());
|
||||||
|
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
|
||||||
s.setPrimaryColor(settings.getPrimaryColor());
|
s.setPrimaryColor(settings.getPrimaryColor());
|
||||||
} else {
|
} else {
|
||||||
s.setReadingMode(ReadingMode.UNREAD);
|
s.setReadingMode(ReadingMode.UNREAD);
|
||||||
@@ -148,6 +149,7 @@ public class UserREST {
|
|||||||
s.setMobileFooter(false);
|
s.setMobileFooter(false);
|
||||||
s.setUnreadCountTitle(false);
|
s.setUnreadCountTitle(false);
|
||||||
s.setUnreadCountFavicon(true);
|
s.setUnreadCountFavicon(true);
|
||||||
|
s.setDisablePullToRefresh(true);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@@ -183,6 +185,7 @@ public class UserREST {
|
|||||||
s.setMobileFooter(settings.isMobileFooter());
|
s.setMobileFooter(settings.isMobileFooter());
|
||||||
s.setUnreadCountTitle(settings.isUnreadCountTitle());
|
s.setUnreadCountTitle(settings.isUnreadCountTitle());
|
||||||
s.setUnreadCountFavicon(settings.isUnreadCountFavicon());
|
s.setUnreadCountFavicon(settings.isUnreadCountFavicon());
|
||||||
|
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
|
||||||
s.setPrimaryColor(settings.getPrimaryColor());
|
s.setPrimaryColor(settings.getPrimaryColor());
|
||||||
|
|
||||||
s.setEmail(settings.getSharingSettings().isEmail());
|
s.setEmail(settings.getSharingSettings().isEmail());
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import jakarta.ws.rs.core.MediaType;
|
|||||||
import jakarta.ws.rs.core.MultivaluedMap;
|
import jakarta.ws.rs.core.MultivaluedMap;
|
||||||
import jakarta.ws.rs.core.UriInfo;
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
import org.jboss.resteasy.reactive.server.multipart.FormValue;
|
import org.jboss.resteasy.reactive.server.multipart.FormValue;
|
||||||
import org.jboss.resteasy.reactive.server.multipart.MultipartFormDataInput;
|
import org.jboss.resteasy.reactive.server.multipart.MultipartFormDataInput;
|
||||||
@@ -177,8 +178,8 @@ public class FeverREST {
|
|||||||
List<String> entryIds = Stream.of(withIds.split(",")).map(String::trim).toList();
|
List<String> entryIds = Stream.of(withIds.split(",")).map(String::trim).toList();
|
||||||
resp.setItems(buildItems(user, subscriptions, entryIds));
|
resp.setItems(buildItems(user, subscriptions, entryIds));
|
||||||
} else {
|
} else {
|
||||||
Long sinceId = params.containsKey("since_id") ? Long.valueOf(params.get("since_id")) : null;
|
Long sinceId = Optional.ofNullable(params.get("since_id")).filter(StringUtils::isNotBlank).map(Long::valueOf).orElse(null);
|
||||||
Long maxId = params.containsKey("max_id") ? Long.valueOf(params.get("max_id")) : null;
|
Long maxId = Optional.ofNullable(params.get("max_id")).filter(StringUtils::isNotBlank).map(Long::valueOf).orElse(null);
|
||||||
resp.setItems(buildItems(user, subscriptions, sinceId, maxId));
|
resp.setItems(buildItems(user, subscriptions, sinceId, maxId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,7 +307,7 @@ public class FeverREST {
|
|||||||
|
|
||||||
FeverFavicon f = new FeverFavicon();
|
FeverFavicon f = new FeverFavicon();
|
||||||
f.setId(s.getFeed().getId());
|
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;
|
return f;
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
quarkus.http.port=8082
|
quarkus.http.port=8082
|
||||||
quarkus.http.test-port=8085
|
quarkus.http.test-port=8085
|
||||||
quarkus.http.enable-compression=true
|
quarkus.http.enable-compression=true
|
||||||
|
quarkus.http.enable-decompression=true
|
||||||
|
|
||||||
# http cache
|
# http cache
|
||||||
quarkus.http.static-resources.max-age=P365d
|
quarkus.http.static-resources.max-age=P365d
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -36,5 +36,6 @@
|
|||||||
<include file="changelogs/db.changelog-5.3.xml" />
|
<include file="changelogs/db.changelog-5.3.xml" />
|
||||||
<include file="changelogs/db.changelog-5.8.xml" />
|
<include file="changelogs/db.changelog-5.8.xml" />
|
||||||
<include file="changelogs/db.changelog-5.11.xml" />
|
<include file="changelogs/db.changelog-5.11.xml" />
|
||||||
|
<include file="changelogs/db.changelog-5.12.xml" />
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -11,7 +11,6 @@ import java.time.Instant;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.zip.DeflaterOutputStream;
|
import java.util.zip.DeflaterOutputStream;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
@@ -104,12 +103,12 @@ class HttpGetterTest {
|
|||||||
.withHeader(HttpHeaders.RETRY_AFTER, "120"));
|
.withHeader(HttpHeaders.RETRY_AFTER, "120"));
|
||||||
|
|
||||||
HttpResult result = getter.get(this.feedUrl);
|
HttpResult result = getter.get(this.feedUrl);
|
||||||
Assertions.assertArrayEquals(feedContent, result.getContent());
|
Assertions.assertArrayEquals(feedContent, result.content());
|
||||||
Assertions.assertEquals(MediaType.APPLICATION_ATOM_XML.toString(), result.getContentType());
|
Assertions.assertEquals(MediaType.APPLICATION_ATOM_XML.toString(), result.contentType());
|
||||||
Assertions.assertEquals("123456", result.getLastModifiedSince());
|
Assertions.assertEquals("123456", result.lastModifiedSince());
|
||||||
Assertions.assertEquals("78910", result.getETag());
|
Assertions.assertEquals("78910", result.eTag());
|
||||||
Assertions.assertEquals(Duration.ofSeconds(60), result.getValidFor());
|
Assertions.assertEquals(Duration.ofSeconds(60), result.validFor());
|
||||||
Assertions.assertEquals(this.feedUrl, result.getUrlAfterRedirect());
|
Assertions.assertEquals(this.feedUrl, result.urlAfterRedirect());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -121,7 +120,7 @@ class HttpGetterTest {
|
|||||||
.withHeader(HttpHeaders.CACHE_CONTROL, "max-age=60; must-revalidate"));
|
.withHeader(HttpHeaders.CACHE_CONTROL, "max-age=60; must-revalidate"));
|
||||||
|
|
||||||
HttpResult result = getter.get(this.feedUrl);
|
HttpResult result = getter.get(this.feedUrl);
|
||||||
Assertions.assertEquals(Duration.ZERO, result.getValidFor());
|
Assertions.assertEquals(Duration.ZERO, result.validFor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -167,7 +166,7 @@ class HttpGetterTest {
|
|||||||
.respond(HttpResponse.response().withBody(feedContent).withContentType(MediaType.APPLICATION_ATOM_XML));
|
.respond(HttpResponse.response().withBody(feedContent).withContentType(MediaType.APPLICATION_ATOM_XML));
|
||||||
|
|
||||||
HttpResult result = getter.get(this.feedUrl);
|
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
|
@Test
|
||||||
@@ -202,7 +201,7 @@ class HttpGetterTest {
|
|||||||
.respond(HttpResponse.response().withBody("ok"));
|
.respond(HttpResponse.response().withBody("ok"));
|
||||||
|
|
||||||
HttpResult result = getter.get(this.feedUrl);
|
HttpResult result = getter.get(this.feedUrl);
|
||||||
Assertions.assertEquals("ok", new String(result.getContent()));
|
Assertions.assertEquals("ok", new String(result.content()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -284,7 +283,7 @@ class HttpGetterTest {
|
|||||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withBody("ok"));
|
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withBody("ok"));
|
||||||
|
|
||||||
HttpResult result = getter.get("https://localhost:" + this.mockServerClient.getPort());
|
HttpResult result = getter.get("https://localhost:" + this.mockServerClient.getPort());
|
||||||
Assertions.assertEquals("ok", new String(result.getContent()));
|
Assertions.assertEquals("ok", new String(result.content()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -308,23 +307,26 @@ class HttpGetterTest {
|
|||||||
@Nested
|
@Nested
|
||||||
class Compression {
|
class Compression {
|
||||||
|
|
||||||
@Test
|
private static final String ACCEPT_ENCODING = "gzip, deflate, br";
|
||||||
void deflate() throws Exception {
|
|
||||||
supportsCompression("deflate", DeflaterOutputStream::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void gzip() throws Exception {
|
void gzip() throws Exception {
|
||||||
supportsCompression("gzip", GZIPOutputStream::new);
|
supportsCompression("gzip", GZIPOutputStream::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deflate() throws Exception {
|
||||||
|
supportsCompression("deflate", DeflaterOutputStream::new);
|
||||||
|
}
|
||||||
|
|
||||||
void supportsCompression(String encoding, CompressionOutputStreamFunction compressionOutputStreamFunction) throws Exception {
|
void supportsCompression(String encoding, CompressionOutputStreamFunction compressionOutputStreamFunction) throws Exception {
|
||||||
String body = "my body";
|
String body = "my body";
|
||||||
|
|
||||||
HttpGetterTest.this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
HttpGetterTest.this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||||
String acceptEncodingHeader = req.getFirstHeader(HttpHeaders.ACCEPT_ENCODING);
|
String acceptEncodingHeader = req.getFirstHeader(HttpHeaders.ACCEPT_ENCODING);
|
||||||
if (!Set.of(acceptEncodingHeader.split(", ")).contains(encoding)) {
|
if (!ACCEPT_ENCODING.equals(acceptEncodingHeader)) {
|
||||||
throw new Exception(encoding + " should be in the Accept-Encoding header");
|
throw new Exception("Wrong value in the Accept-Encoding header, should be '%s' but was '%s'".formatted(ACCEPT_ENCODING,
|
||||||
|
acceptEncodingHeader));
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
@@ -336,7 +338,7 @@ class HttpGetterTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
HttpResult result = getter.get(HttpGetterTest.this.feedUrl);
|
HttpResult result = getter.get(HttpGetterTest.this.feedUrl);
|
||||||
Assertions.assertEquals(body, new String(result.getContent()));
|
Assertions.assertEquals(body, new String(result.content()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ class UrlsTest {
|
|||||||
Assertions.assertEquals("http://ergoemacs.org/emacs/elisp_all_about_lines.html",
|
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"));
|
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
|
@Test
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ class FacebookFaviconFetcherTest {
|
|||||||
Favicon result = faviconFetcher.fetch(feed);
|
Favicon result = faviconFetcher.fetch(feed);
|
||||||
|
|
||||||
Assertions.assertNotNull(result);
|
Assertions.assertNotNull(result);
|
||||||
Assertions.assertEquals(iconBytes, result.getIcon());
|
Assertions.assertEquals(iconBytes, result.icon());
|
||||||
Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType)));
|
Assertions.assertTrue(result.mediaType().isCompatible(MediaType.valueOf(contentType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ class YoutubeFaviconFetcherTest {
|
|||||||
Favicon result = faviconFetcher.fetch(feed);
|
Favicon result = faviconFetcher.fetch(feed);
|
||||||
|
|
||||||
Assertions.assertNotNull(result);
|
Assertions.assertNotNull(result);
|
||||||
Assertions.assertEquals(iconBytes, result.getIcon());
|
Assertions.assertEquals(iconBytes, result.icon());
|
||||||
Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType)));
|
Assertions.assertTrue(result.mediaType().isCompatible(MediaType.valueOf(contentType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -114,8 +114,8 @@ class YoutubeFaviconFetcherTest {
|
|||||||
Favicon result = faviconFetcher.fetch(feed);
|
Favicon result = faviconFetcher.fetch(feed);
|
||||||
|
|
||||||
Assertions.assertNotNull(result);
|
Assertions.assertNotNull(result);
|
||||||
Assertions.assertEquals(iconBytes, result.getIcon());
|
Assertions.assertEquals(iconBytes, result.icon());
|
||||||
Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType)));
|
Assertions.assertTrue(result.mediaType().isCompatible(MediaType.valueOf(contentType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -151,8 +151,8 @@ class YoutubeFaviconFetcherTest {
|
|||||||
Favicon result = faviconFetcher.fetch(feed);
|
Favicon result = faviconFetcher.fetch(feed);
|
||||||
|
|
||||||
Assertions.assertNotNull(result);
|
Assertions.assertNotNull(result);
|
||||||
Assertions.assertEquals(iconBytes, result.getIcon());
|
Assertions.assertEquals(iconBytes, result.icon());
|
||||||
Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType)));
|
Assertions.assertTrue(result.mediaType().isCompatible(MediaType.valueOf(contentType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ import com.commafeed.backend.Digests;
|
|||||||
import com.commafeed.backend.HttpGetter;
|
import com.commafeed.backend.HttpGetter;
|
||||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||||
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
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;
|
||||||
|
import com.commafeed.backend.feed.parser.FeedParser.FeedParsingException;
|
||||||
|
import com.commafeed.backend.feed.parser.FeedParserResult;
|
||||||
import com.commafeed.backend.urlprovider.FeedURLProvider;
|
import com.commafeed.backend.urlprovider.FeedURLProvider;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@@ -29,13 +32,33 @@ class FeedFetcherTest {
|
|||||||
private HttpGetter getter;
|
private HttpGetter getter;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private List<FeedURLProvider> urlProviders;
|
private FeedURLProvider urlProvider;
|
||||||
|
|
||||||
private FeedFetcher fetcher;
|
private FeedFetcher fetcher;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void init() {
|
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
|
@Test
|
||||||
|
|||||||
@@ -1,34 +1,271 @@
|
|||||||
package com.commafeed.backend.feed.parser;
|
package com.commafeed.backend.feed.parser;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class FeedCleanerTest {
|
class FeedCleanerTest {
|
||||||
|
|
||||||
FeedCleaner feedCleaner = new FeedCleaner();
|
FeedCleaner feedCleaner = new FeedCleaner();
|
||||||
|
|
||||||
@Test
|
@Nested
|
||||||
void testReplaceHtmlEntitiesWithNumericEntities() {
|
class RemoveCharactersBeforeFirstXmlTag {
|
||||||
String source = "<source>T´l´phone ′</source>";
|
@Test
|
||||||
Assertions.assertEquals("<source>T´l´phone ′</source>", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
|
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
|
@Nested
|
||||||
void testRemoveDoctype() {
|
class RemoveInvalidXmlCharacters {
|
||||||
String source = "<!DOCTYPE html><html><head></head><body></body></html>";
|
@Test
|
||||||
Assertions.assertEquals("<html><head></head><body></body></html>", feedCleaner.removeDoctypeDeclarations(source));
|
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
|
@Nested
|
||||||
void testRemoveMultilineDoctype() {
|
class Entities {
|
||||||
String source = """
|
@Test
|
||||||
<!DOCTYPE
|
void testReplaceHtmlEntitiesWithNumericEntities() {
|
||||||
html
|
String source = "<source>T´l´phone ′</source>";
|
||||||
>
|
Assertions.assertEquals("<source>T´l´phone ′</source>",
|
||||||
<html><head></head><body></body></html>""";
|
feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
|
||||||
Assertions.assertEquals("""
|
}
|
||||||
|
|
||||||
<html><head></head><body></body></html>""", feedCleaner.removeDoctypeDeclarations(source));
|
@Test
|
||||||
|
void replacesMultipleOccurrencesOfSameEntity() {
|
||||||
|
String source = " ";
|
||||||
|
Assertions.assertEquals("   ", 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 = "´′";
|
||||||
|
Assertions.assertEquals("´′", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void replacesCommonHtmlEntities() {
|
||||||
|
String source = "&"";
|
||||||
|
Assertions.assertEquals("&"", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void handlesPartialEntityMatches() {
|
||||||
|
String source = "&lifier";
|
||||||
|
String result = feedCleaner.replaceHtmlEntitiesWithNumericEntities(source);
|
||||||
|
Assertions.assertTrue(result.startsWith("&") || result.equals("&lifier"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = "&test";
|
||||||
|
Assertions.assertEquals("&test", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void handlesEntityAtEndOfString() {
|
||||||
|
String source = "test&";
|
||||||
|
Assertions.assertEquals("test&", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void handlesMixedEntitiesAndText() {
|
||||||
|
String source = "Hello World! Test.";
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -108,12 +108,12 @@ class DatabaseCleaningServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void cleanEntriesForFeedsExceedingCapacityDeletesOldEntries() {
|
void cleanEntriesForFeedsExceedingCapacityDeletesOldEntries() {
|
||||||
FeedCapacity feed1 = Mockito.mock(FeedCapacity.class);
|
FeedCapacity feed1 = Mockito.mock(FeedCapacity.class);
|
||||||
Mockito.when(feed1.getId()).thenReturn(1L);
|
Mockito.when(feed1.id()).thenReturn(1L);
|
||||||
Mockito.when(feed1.getCapacity()).thenReturn(180L);
|
Mockito.when(feed1.capacity()).thenReturn(180L);
|
||||||
|
|
||||||
FeedCapacity feed2 = Mockito.mock(FeedCapacity.class);
|
FeedCapacity feed2 = Mockito.mock(FeedCapacity.class);
|
||||||
Mockito.when(feed2.getId()).thenReturn(2L);
|
Mockito.when(feed2.id()).thenReturn(2L);
|
||||||
Mockito.when(feed2.getCapacity()).thenReturn(120L);
|
Mockito.when(feed2.capacity()).thenReturn(120L);
|
||||||
|
|
||||||
Mockito.when(feedEntryDAO.findFeedsExceedingCapacity(50, BATCH_SIZE))
|
Mockito.when(feedEntryDAO.findFeedsExceedingCapacity(50, BATCH_SIZE))
|
||||||
.thenReturn(Arrays.asList(feed1, feed2))
|
.thenReturn(Arrays.asList(feed1, feed2))
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import com.commafeed.backend.model.User;
|
|
||||||
import com.commafeed.frontend.model.UserModel;
|
import com.commafeed.frontend.model.UserModel;
|
||||||
|
import com.commafeed.frontend.model.request.AdminSaveUserRequest;
|
||||||
import com.commafeed.frontend.model.request.IDRequest;
|
import com.commafeed.frontend.model.request.IDRequest;
|
||||||
import com.commafeed.integration.BaseIT;
|
import com.commafeed.integration.BaseIT;
|
||||||
|
|
||||||
@@ -51,10 +51,11 @@ class AdminIT extends BaseIT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long createUser() {
|
private long createUser() {
|
||||||
User user = new User();
|
AdminSaveUserRequest user = new AdminSaveUserRequest();
|
||||||
user.setName("test");
|
user.setName("test");
|
||||||
user.setPassword("test".getBytes());
|
user.setPassword("Test1234!");
|
||||||
user.setEmail("test@test.com");
|
user.setEmail("test@test.com");
|
||||||
|
user.setEnabled(true);
|
||||||
String response = RestAssured.given()
|
String response = RestAssured.given()
|
||||||
.body(user)
|
.body(user)
|
||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import com.rometools.rome.io.SyndFeedInput;
|
|||||||
|
|
||||||
import io.quarkus.test.junit.QuarkusTest;
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
import io.restassured.RestAssured;
|
import io.restassured.RestAssured;
|
||||||
import io.restassured.common.mapper.TypeRef;
|
|
||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
|
|
||||||
@QuarkusTest
|
@QuarkusTest
|
||||||
@@ -109,17 +108,16 @@ class CategoryIT extends BaseIT {
|
|||||||
Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl(), categoryId);
|
Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl(), categoryId);
|
||||||
Assertions.assertEquals(2, getCategoryEntries(categoryId).getEntries().size());
|
Assertions.assertEquals(2, getCategoryEntries(categoryId).getEntries().size());
|
||||||
|
|
||||||
List<UnreadCount> counts = RestAssured.given()
|
UnreadCount[] counts = RestAssured.given()
|
||||||
.get("rest/category/unreadCount")
|
.get("rest/category/unreadCount")
|
||||||
.then()
|
.then()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
.extract()
|
.extract()
|
||||||
.as(new TypeRef<List<UnreadCount>>() {
|
.as(UnreadCount[].class);
|
||||||
});
|
|
||||||
|
|
||||||
Assertions.assertEquals(1, counts.size());
|
Assertions.assertEquals(1, counts.length);
|
||||||
Assertions.assertEquals(subscriptionId, counts.get(0).getFeedId());
|
Assertions.assertEquals(subscriptionId, counts[0].getFeedId());
|
||||||
Assertions.assertEquals(2, counts.get(0).getUnreadCount());
|
Assertions.assertEquals(2, counts[0].getUnreadCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|||||||
@@ -77,15 +77,14 @@ The table below shows some elements of the CommaFeed main page that are useful f
|
|||||||
article {background-color: lightblue;}
|
article {background-color: lightblue;}
|
||||||
```
|
```
|
||||||
|
|
||||||
|Element Name|Element Description|
|
| Element Name | Element Description |
|
||||||
|---|---|
|
|--------------|-------------------------------------------|
|
||||||
|main|The entire web page|
|
| main | The entire web page |
|
||||||
|header|The header area (logo and toolbar)|
|
| header | The header area (logo and toolbar) |
|
||||||
|nav|The entire sidebar|
|
| nav | The entire sidebar |
|
||||||
|footer|The footer area at the bottom of the page|
|
| footer | The footer area at the bottom of the page |
|
||||||
|article|Entire feed entry|
|
| article | Entire feed entry |
|
||||||
|h3, h2, h1|HTML headers|
|
| h3, h2, h1 | HTML headers |
|
||||||
|
|
||||||
|
|
||||||
## CommaFeed Class Names
|
## 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:
|
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;}
|
.cf-header {background-color: lightblue;}
|
||||||
```
|
```
|
||||||
|
|
||||||
|Class Name|Element Description|
|
| Class Name | Element Description |
|
||||||
|---|---|
|
|--------------------------|--------------------------------------------------------------------------------|
|
||||||
|cf-logo-title|The CommaFeed logo and title in upper left of page|
|
| cf-logo-title | The CommaFeed logo and title in upper left of page |
|
||||||
|cf-logo|The CommaFeed logo|
|
| cf-logo | The CommaFeed logo |
|
||||||
|cf-title|The CommaFeed title|
|
| cf-title | The CommaFeed title |
|
||||||
|cf-toolbar|The entire toolbar of action buttons at the top of the page|
|
| 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-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-treesearch | The search box at the top of the sidebar |
|
||||||
|cf-tree|The entire feed tree in the sidebar|
|
| cf-tree | The entire feed tree in the sidebar |
|
||||||
|cf-treenode|All nodes in the feed tree|
|
| cf-treenode | All nodes in the feed tree |
|
||||||
|cf-treenode-category|Category 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-feed | Feed nodes in the feed tree |
|
||||||
|cf-treenode-icon|Icon within feed nodes|
|
| cf-treenode-icon | Icon within feed nodes |
|
||||||
|cf-treenode-unread-count|Unread count within feed nodes|
|
| cf-treenode-unread-count | Unread count within feed nodes |
|
||||||
|cf-badge|The badge for the unread count|
|
| cf-badge | The badge for the unread count |
|
||||||
|cf-entries-title|Title of feed currently displayed in the content area|
|
| 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-entries | All of the feed entries being displayed in the content area |
|
||||||
|cf-header|The header of a feed entry|
|
| 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-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-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-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-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-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-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.)|
|
| cf-action-button | Each button within the feed entry footer. (note: also used in toolbar.) |
|
||||||
|
|||||||
50
mvnw
vendored
50
mvnw
vendored
@@ -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
|
# Optional ENV vars
|
||||||
# -----------------
|
# -----------------
|
||||||
@@ -105,14 +105,17 @@ trim() {
|
|||||||
printf "%s" "${1}" | tr -d '[:space:]'
|
printf "%s" "${1}" | tr -d '[:space:]'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scriptDir="$(dirname "$0")"
|
||||||
|
scriptName="$(basename "$0")"
|
||||||
|
|
||||||
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
while IFS="=" read -r key value; do
|
while IFS="=" read -r key value; do
|
||||||
case "${key-}" in
|
case "${key-}" in
|
||||||
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||||
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||||
esac
|
esac
|
||||||
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
|
||||||
case "${distributionUrl##*/}" in
|
case "${distributionUrl##*/}" in
|
||||||
maven-mvnd-*bin.*)
|
maven-mvnd-*bin.*)
|
||||||
@@ -130,7 +133,7 @@ maven-mvnd-*bin.*)
|
|||||||
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||||
;;
|
;;
|
||||||
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
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
|
esac
|
||||||
|
|
||||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
# 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
|
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
exit 1
|
exit 1
|
||||||
elif command -v sha256sum >/dev/null; then
|
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
|
distributionSha256Result=true
|
||||||
fi
|
fi
|
||||||
elif command -v shasum >/dev/null; then
|
elif command -v shasum >/dev/null; then
|
||||||
@@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then
|
|||||||
else
|
else
|
||||||
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||||
fi
|
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 || :
|
clean || :
|
||||||
exec_maven "$@"
|
exec_maven "$@"
|
||||||
|
|||||||
56
mvnw.cmd
vendored
56
mvnw.cmd
vendored
@@ -19,7 +19,7 @@
|
|||||||
@REM ----------------------------------------------------------------------------
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
@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
|
||||||
@REM Optional ENV vars
|
@REM Optional ENV vars
|
||||||
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
@SET __MVNW_ARG0_NAME__=
|
@SET __MVNW_ARG0_NAME__=
|
||||||
@SET MVNW_USERNAME=
|
@SET MVNW_USERNAME=
|
||||||
@SET MVNW_PASSWORD=
|
@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
|
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||||
@GOTO :EOF
|
@GOTO :EOF
|
||||||
: end batch / begin powershell #>
|
: end batch / begin powershell #>
|
||||||
@@ -73,16 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
|||||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
if ($env:MVNW_REPOURL) {
|
if ($env:MVNW_REPOURL) {
|
||||||
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
$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,'')"
|
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
|
||||||
}
|
}
|
||||||
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||||
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||||
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
|
|
||||||
|
$MAVEN_M2_PATH = "$HOME/.m2"
|
||||||
if ($env:MAVEN_USER_HOME) {
|
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"
|
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||||
|
|
||||||
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||||
@@ -134,7 +148,33 @@ if ($distributionSha256Sum) {
|
|||||||
|
|
||||||
# unzip and move
|
# unzip and move
|
||||||
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
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 {
|
try {
|
||||||
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
12
pom.xml
12
pom.xml
@@ -5,16 +5,13 @@
|
|||||||
|
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>5.11.1</version>
|
<version>5.12.1</version>
|
||||||
<name>CommaFeed</name>
|
<name>CommaFeed</name>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<maven.compiler.release>17</maven.compiler.release>
|
<maven.compiler.release>17</maven.compiler.release>
|
||||||
|
|
||||||
<sonar.organization>athou</sonar.organization>
|
|
||||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -22,7 +19,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.14.0</version>
|
<version>3.14.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<parameters>true</parameters>
|
<parameters>true</parameters>
|
||||||
|
|
||||||
@@ -41,11 +38,6 @@
|
|||||||
</compilerArgs>
|
</compilerArgs>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
|
||||||
<groupId>org.sonarsource.scanner.maven</groupId>
|
|
||||||
<artifactId>sonar-maven-plugin</artifactId>
|
|
||||||
<version>5.2.0.4988</version>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"customManagers:mavenPropertyVersions",
|
"customManagers:mavenPropertyVersions",
|
||||||
"customManagers:biomeVersions",
|
"customManagers:biomeVersions",
|
||||||
":automergePatch",
|
":automergePatch",
|
||||||
":automergeDigest",
|
":automergeDigest",
|
||||||
":automergeBranch",
|
":automergeBranch",
|
||||||
":automergeRequireAllStatusChecks",
|
":automergeRequireAllStatusChecks",
|
||||||
":maintainLockFilesWeekly"
|
":maintainLockFilesWeekly"
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
"description": "IBM Semeru Runtimes uses a custom versioning scheme",
|
"description": "IBM Semeru Runtimes uses a custom versioning scheme",
|
||||||
"matchDatasources": "docker",
|
"matchDatasources": "docker",
|
||||||
"matchPackageNames": "ibm-semeru-runtimes",
|
"matchPackageNames": "ibm-semeru-runtimes",
|
||||||
"versioning": "regex:^open-(?<major>\\d+)?(\\.(?<minor>\\d+))?(\\.(?<patch>\\d+))?([\\._+](?<build>(\\d\\.?)+))?(-(?<compatibility>.*))?$",
|
"versioning": "regex:^open-jdk-(?<major>\\d+)?(\\.(?<minor>\\d+))?(\\.(?<patch>\\d+))?([\\._+](?<build>(\\d\\.?)+))?(-(?<compatibility>.*))?$",
|
||||||
"allowedVersions": "/^open-(?:8|11|17|21|25)(?:\\.|-|$)/"
|
"allowedVersions": "/^open-jdk-(?:8|11|17|21|25)(?:\\.|-|$)/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user