mirror of
https://github.com/Athou/commafeed.git
synced 2026-10-24 05:59:11 +00:00
Compare commits
484 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27cece3afb | ||
|
|
8124ecb51f | ||
|
|
e67c4c8670 | ||
|
|
fcc6798d63 | ||
|
|
d313ebfc69 | ||
|
|
6edb8f00f6 | ||
|
|
98a261d457 | ||
|
|
e97a69f475 | ||
|
|
e9dc284b26 | ||
|
|
856c52c64b | ||
|
|
386a4a32ef | ||
|
|
dbdb40ff01 | ||
|
|
1bd9b71215 | ||
|
|
f4dc279ec9 | ||
|
|
ca8438623d | ||
|
|
b81aac71f5 | ||
|
|
a4e0d28fa2 | ||
|
|
56d4e4bbb1 | ||
|
|
af1d10529b | ||
|
|
a5f84a3f6d | ||
|
|
69dfbc81cc | ||
|
|
34408ccdb0 | ||
|
|
b0522a6e5d | ||
|
|
2a559a07c0 | ||
|
|
bdf2a6a915 | ||
|
|
e814f75872 | ||
|
|
c0e0791854 | ||
|
|
17dbdb66bf | ||
|
|
6e3e9add36 | ||
|
|
5b25011853 | ||
|
|
804b24b2d6 | ||
|
|
de98b0f7c2 | ||
|
|
709de70fa9 | ||
|
|
1b7bc11f9d | ||
|
|
bbcaacf3dd | ||
|
|
e816b1262c | ||
|
|
fc019333dc | ||
|
|
56b0ddbc34 | ||
|
|
bb74d27d52 | ||
|
|
7b1d19ca71 | ||
|
|
af961678bd | ||
|
|
1579242644 | ||
|
|
2a45f5c596 | ||
|
|
c4560c49b0 | ||
|
|
2b7f75d9ef | ||
|
|
5f68bcbd46 | ||
|
|
c93c072259 | ||
|
|
516344a993 | ||
|
|
4d42fe5a0b | ||
|
|
47bf132fff | ||
|
|
04a9d48505 | ||
|
|
3716ddcf15 | ||
|
|
6c3ab40cb6 | ||
|
|
b7270974e5 | ||
|
|
9bfc53e77a | ||
|
|
f34fac8d17 | ||
|
|
555c11649e | ||
|
|
c308c5d606 | ||
|
|
54e9615994 | ||
|
|
b0b3b323fd | ||
|
|
f740a0b8ba | ||
|
|
e154675e72 | ||
|
|
2b3d3261de | ||
|
|
fab525ca56 | ||
|
|
1c273d1c1a | ||
|
|
90d684367c | ||
|
|
1ffc544ab0 | ||
|
|
cde0cceee1 | ||
|
|
1604fbc55c | ||
|
|
6f586e8866 | ||
|
|
c53455ad61 | ||
|
|
77b3c609f3 | ||
|
|
3b84598d99 | ||
|
|
6f536c0981 | ||
|
|
1f881ea252 | ||
|
|
b14df1b3cb | ||
|
|
e13763baa3 | ||
|
|
502ef67a71 | ||
|
|
05cab626df | ||
|
|
c7d54af7ad | ||
|
|
ad195fcd80 | ||
|
|
b87c68d598 | ||
|
|
33af37ed2e | ||
|
|
c4113fc92b | ||
|
|
ed40025d26 | ||
|
|
752008a551 | ||
|
|
a8bf970dca | ||
|
|
5b94bcdfdd | ||
|
|
a66a0e9485 | ||
|
|
80755e15b8 | ||
|
|
8cede0c6e0 | ||
|
|
9556f0f7d2 | ||
|
|
1b80f3060c | ||
|
|
00811f0be3 | ||
|
|
0fdfc2c59c | ||
|
|
8d25562a76 | ||
|
|
1d0d9d4be6 | ||
|
|
9ed4559625 | ||
|
|
2a53e8f15d | ||
|
|
15fbd18f31 | ||
|
|
2c248a7ca1 | ||
|
|
94d8fdb4ab | ||
|
|
3596439ed4 | ||
|
|
bc732a52ce | ||
|
|
ef096899a2 | ||
|
|
bf4bcf740e | ||
|
|
679281ac26 | ||
|
|
bde3d1408b | ||
|
|
3621a96d3f | ||
|
|
6583ad114f | ||
|
|
f4e42165bf | ||
|
|
00d7be735a | ||
|
|
1388fba8e4 | ||
|
|
c43644861f | ||
|
|
831ca599b8 | ||
|
|
43fbd03292 | ||
|
|
1efd04f6c5 | ||
|
|
e89ccfd74b | ||
|
|
622486160c | ||
|
|
6d172cae9e | ||
|
|
91b60fb147 | ||
|
|
3f9b7f48d5 | ||
|
|
3599fdec65 | ||
|
|
00e86974b3 | ||
|
|
2aa897eb80 | ||
|
|
ce9706b860 | ||
|
|
e140dfe556 | ||
|
|
4a1b845ac2 | ||
|
|
573f8af57e | ||
|
|
33445c93e8 | ||
|
|
f7fcb659f7 | ||
|
|
b0aa723c2f | ||
|
|
48dca41738 | ||
|
|
873d6f487f | ||
|
|
c8d73b129e | ||
|
|
c63e9e1cd2 | ||
|
|
def2ad84bb | ||
|
|
52510e9817 | ||
|
|
69a95488ce | ||
|
|
636841f647 | ||
|
|
1ea521236c | ||
|
|
6966efeda5 | ||
|
|
66ad0c3548 | ||
|
|
e061ccbf79 | ||
|
|
30cc6a111d | ||
|
|
8416ce5124 | ||
|
|
0cacfe391b | ||
|
|
c0eaab48cd | ||
|
|
c696bc3545 | ||
|
|
72505954a9 | ||
|
|
49159dba5c | ||
|
|
41e2d89abc | ||
|
|
6e7de97d93 | ||
|
|
2f20e6b498 | ||
|
|
ecf9158fd7 | ||
|
|
dc7e7321f3 | ||
|
|
0f087ad2a0 | ||
|
|
ff19a6e9eb | ||
|
|
8cec0c0779 | ||
|
|
314384f8e7 | ||
|
|
6c75ad95ad | ||
|
|
76e3b3caff | ||
|
|
c8f71480c9 | ||
|
|
9172a0cf9c | ||
|
|
eb476c6023 | ||
|
|
da4f3bd38e | ||
|
|
c65d628a36 | ||
|
|
589b8c3afc | ||
|
|
2684051da0 | ||
|
|
200db68d05 | ||
|
|
f6d8d32a01 | ||
|
|
72d5af5bb0 | ||
|
|
aabbc54cbb | ||
|
|
09b523bf56 | ||
|
|
5dc7f522bf | ||
|
|
fd58a42824 | ||
|
|
0a6d1c85ed | ||
|
|
13f84a260c | ||
|
|
4132ad9a97 | ||
|
|
1faba92ecc | ||
|
|
b93fc86663 | ||
|
|
7123036e8d | ||
|
|
cce4ea4dc6 | ||
|
|
b8f7df1555 | ||
|
|
ba8cc0477c | ||
|
|
010026bc88 | ||
|
|
a1ba34caf2 | ||
|
|
1214380bee | ||
|
|
03eb09c68c | ||
|
|
055d503862 | ||
|
|
5755ff762b | ||
|
|
88e24787e8 | ||
|
|
ed32d07ac4 | ||
|
|
3a917fcebe | ||
|
|
3535068ca8 | ||
|
|
4c39d4b96b | ||
|
|
8e864c6793 | ||
|
|
aeffecced3 | ||
|
|
4be42dc1e2 | ||
|
|
bdf6c62c29 | ||
|
|
a4592e5199 | ||
|
|
a5268f082d | ||
|
|
b556e72922 | ||
|
|
3602d2533a | ||
|
|
681f2c766a | ||
|
|
6e0a136cd1 | ||
|
|
9e9945a1cc | ||
|
|
3368cead8b | ||
|
|
a7b5126b4a | ||
|
|
a9ef1a03dc | ||
|
|
bc0ec8dbfa | ||
|
|
7bef009922 | ||
|
|
9a467bc27b | ||
|
|
e3934c9403 | ||
|
|
af337aa4d0 | ||
|
|
313d3a01ec | ||
|
|
feb9876cf5 | ||
|
|
9c3a5cb820 | ||
|
|
e219f781bf | ||
|
|
21ecd6ae05 | ||
|
|
2449902c3d | ||
|
|
c1a55037ba | ||
|
|
646a8122c0 | ||
|
|
edc4e6c508 | ||
|
|
5ddeb7b156 | ||
|
|
306624e55f | ||
|
|
6a9ca65cbc | ||
|
|
7db0026a0c | ||
|
|
7358705248 | ||
|
|
0b9ee2eec9 | ||
|
|
42eaf7fc39 | ||
|
|
6783cac9b4 | ||
|
|
628106ce88 | ||
|
|
1ce69b31a7 | ||
|
|
912ac11842 | ||
|
|
5f2fad560c | ||
|
|
b300f364a7 | ||
|
|
049ec41d13 | ||
|
|
f0d17bc84e | ||
|
|
cc442d7409 | ||
|
|
80bac26f21 | ||
|
|
015c39e6f4 | ||
|
|
e0ec3a4ebd | ||
|
|
a022de5cc3 | ||
|
|
a7b5e1e18c | ||
|
|
b8695df1c8 | ||
|
|
5e4382d1d2 | ||
|
|
ce983f4906 | ||
|
|
4a8be29616 | ||
|
|
1622eb642a | ||
|
|
422d992484 | ||
|
|
1065af1233 | ||
|
|
dde39d83be | ||
|
|
94fe7617bf | ||
|
|
e781d85b7c | ||
|
|
c30c9ca380 | ||
|
|
c395916af9 | ||
|
|
1a4037e360 | ||
|
|
f207bfaddd | ||
|
|
3dd298c0c8 | ||
|
|
8e2e4da7c3 | ||
|
|
418e3c109c | ||
|
|
cfda63a609 | ||
|
|
ba68276bfb | ||
|
|
97c700024a | ||
|
|
d4f67d726c | ||
|
|
ea87fa3e5a | ||
|
|
d12d247b3e | ||
|
|
c4a23915bd | ||
|
|
8645c57d13 | ||
|
|
b170e0bc17 | ||
|
|
4199f514f9 | ||
|
|
c96efb3bb4 | ||
|
|
d5571e0196 | ||
|
|
9f7f87b4ae | ||
|
|
4b03fce85d | ||
|
|
4b28d1c635 | ||
|
|
a5e803177f | ||
|
|
9462f284a4 | ||
|
|
4b0f6bd61b | ||
|
|
d6c85214ef | ||
|
|
de770cf801 | ||
|
|
dae0f740c2 | ||
|
|
9452c3e74c | ||
|
|
f826f1d543 | ||
|
|
fd13c80e5a | ||
|
|
75871348d5 | ||
|
|
0140e88595 | ||
|
|
e620b1254b | ||
|
|
77cfcf6c58 | ||
|
|
04e9df3d0a | ||
|
|
a953134089 | ||
|
|
88b9c0d783 | ||
|
|
ff7e0ba95b | ||
|
|
53dfbcaf00 | ||
|
|
d8fca22c00 | ||
|
|
4fb5963bd9 | ||
|
|
862d81f446 | ||
|
|
8ae7943b94 | ||
|
|
46e947d779 | ||
|
|
7780d6020f | ||
|
|
e5803b46f2 | ||
|
|
d776090298 | ||
|
|
f2f3111d49 | ||
|
|
dff1b957cd | ||
|
|
b1ec50c3cb | ||
|
|
85aa188287 | ||
|
|
e7d6526da9 | ||
|
|
d67c6fcc00 | ||
|
|
d9bf084300 | ||
|
|
da3977b0e2 | ||
|
|
7df930bccc | ||
|
|
11f73a1a53 | ||
|
|
2bbcd55e48 | ||
|
|
43890b8d5c | ||
|
|
b6b8e3741b | ||
|
|
c39f00c336 | ||
|
|
e48eaacfef | ||
|
|
6d97691388 | ||
|
|
d64cc996bc | ||
|
|
617d66c9b9 | ||
|
|
0fc50d2105 | ||
|
|
2bb9f28569 | ||
|
|
c1580b8359 | ||
|
|
dde235825d | ||
|
|
eac13b4025 | ||
|
|
59cc9c2706 | ||
|
|
40e20a0d33 | ||
|
|
a42679acaa | ||
|
|
fd2e8fcdb8 | ||
|
|
ebd5a15152 | ||
|
|
6cdd110304 | ||
|
|
3181cda2e0 | ||
|
|
2659c7fc02 | ||
|
|
5747ae3429 | ||
|
|
8d60be0e3f | ||
|
|
7e3f801a5a | ||
|
|
687ef53c65 | ||
|
|
a1fe9e727e | ||
|
|
6d625c88d3 | ||
|
|
c314e2de6b | ||
|
|
bd08b95390 | ||
|
|
aed0cca7e1 | ||
|
|
7d7761fb8d | ||
|
|
6d2fb5acc2 | ||
|
|
ec35d846cf | ||
|
|
1d23907652 | ||
|
|
efc6c10ace | ||
|
|
47a8ad5244 | ||
|
|
5d879c4619 | ||
|
|
6e0dd3d064 | ||
|
|
4657a403d0 | ||
|
|
bf09a07fe2 | ||
|
|
927802ca88 | ||
|
|
3d5bc9ef0e | ||
|
|
706ad7e44d | ||
|
|
88206f521b | ||
|
|
af786b4c11 | ||
|
|
d4c749c997 | ||
|
|
b5aa25cd0b | ||
|
|
ae49fb6dad | ||
|
|
3e24d17951 | ||
|
|
22030e6907 | ||
|
|
39fdcb9af3 | ||
|
|
866998f98b | ||
|
|
0fa71f76e8 | ||
|
|
317f655128 | ||
|
|
a871426f31 | ||
|
|
2caf9ff38a | ||
|
|
1de3a57d50 | ||
|
|
bed1355d63 | ||
|
|
189f9b4f26 | ||
|
|
931b5a22e6 | ||
|
|
4c9e0a8b74 | ||
|
|
589472314b | ||
|
|
f11657fdee | ||
|
|
e4ec081357 | ||
|
|
9e55527a2b | ||
|
|
cca70ffcec | ||
|
|
2a6a717ef2 | ||
|
|
11043554c2 | ||
|
|
cdbef468d7 | ||
|
|
4705b75580 | ||
|
|
245ccf889e | ||
|
|
2ad0a3d622 | ||
|
|
e775f774b5 | ||
|
|
ad2aec44e5 | ||
|
|
11d3effc28 | ||
|
|
2ef12e2868 | ||
|
|
ed8c5ce1ea | ||
|
|
33e507efe5 | ||
|
|
c425f5cf6a | ||
|
|
fc76d7e609 | ||
|
|
1b24bf33ed | ||
|
|
16036897cb | ||
|
|
58ff378735 | ||
|
|
1cee04a233 | ||
|
|
ac11a0efb8 | ||
|
|
f2ea1e3f7a | ||
|
|
76dbbdfbf9 | ||
|
|
153970c146 | ||
|
|
c21287e642 | ||
|
|
284942a82e | ||
|
|
e796916e73 | ||
|
|
004db1762c | ||
|
|
8e05ba8820 | ||
|
|
f4f386b5e5 | ||
|
|
ecbf8bec23 | ||
|
|
5d8c09ccda | ||
|
|
47c5c3d8a0 | ||
|
|
3ddce16d5b | ||
|
|
acefdc44d9 | ||
|
|
d05c5b9d7f | ||
|
|
5d1237d1f4 | ||
|
|
6bac8631ac | ||
|
|
2c803327ad | ||
|
|
451ae5bc51 | ||
|
|
f41ce8c878 | ||
|
|
177c54c813 | ||
|
|
3765e32fd4 | ||
|
|
aab7a16d18 | ||
|
|
f2463af63c | ||
|
|
212c2c3b56 | ||
|
|
3ab00b0cdd | ||
|
|
fad85e9299 | ||
|
|
3cd5e203e2 | ||
|
|
7b081fa870 | ||
|
|
5ce22051d4 | ||
|
|
1e59489fa5 | ||
|
|
1e691b1255 | ||
|
|
61e1bef63f | ||
|
|
46dbb78fbf | ||
|
|
99890707b3 | ||
|
|
f54c841fdc | ||
|
|
e2a6009ee9 | ||
|
|
a34dd15040 | ||
|
|
24c934003d | ||
|
|
5300e1a245 | ||
|
|
90381c670b | ||
|
|
23edea93db | ||
|
|
a51712e363 | ||
|
|
4310e979e1 | ||
|
|
f690b76d87 | ||
|
|
ac29594b67 | ||
|
|
93f535bb87 | ||
|
|
076eb3cf42 | ||
|
|
7b2e0fffbd | ||
|
|
8eaab0dbc3 | ||
|
|
eaa5bc896e | ||
|
|
42d1db5fc3 | ||
|
|
78c017ddaf | ||
|
|
231551d743 | ||
|
|
d0984eaba7 | ||
|
|
15854a72d1 | ||
|
|
7fd2bf0eda | ||
|
|
6a10a2167e | ||
|
|
1ba99d255c | ||
|
|
ff1c2947b6 | ||
|
|
a690d2e0db | ||
|
|
a3df327396 | ||
|
|
ede0016d8e | ||
|
|
c19b091795 | ||
|
|
9e62c8b9f3 | ||
|
|
9f30dc181c | ||
|
|
37fe44f860 | ||
|
|
68aaab8467 | ||
|
|
b951ed1fcd | ||
|
|
9bd9dc568a | ||
|
|
29e4356fee | ||
|
|
0ed31eaa99 | ||
|
|
1e9869b217 | ||
|
|
78cfc2c827 | ||
|
|
a6e5a0d125 | ||
|
|
ea13aecd27 | ||
|
|
d838e8f28f | ||
|
|
c9a7b9e17c | ||
|
|
8fe2d0bc0e | ||
|
|
71e2f1e1e6 | ||
|
|
1ce9d1b9b2 | ||
|
|
b3d6ae467f | ||
|
|
da8d720dc4 | ||
|
|
824c38f8ce | ||
|
|
b0579a70d8 | ||
|
|
f9fe2d0976 |
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@@ -7,6 +7,7 @@ exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- enhancement
|
||||
- feature-request
|
||||
- bug
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
|
||||
52
.github/workflows/ci.yml
vendored
52
.github/workflows/ci.yml
vendored
@@ -23,18 +23,24 @@ jobs:
|
||||
steps:
|
||||
# Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Setup
|
||||
- name: Set up GraalVM
|
||||
uses: graalvm/setup-graalvm@54b4f5a65c1a84b2fdfdc2078fe43df32819e4b1 # v1
|
||||
uses: graalvm/setup-graalvm@186d0493a2df5eb62df5ecc498883d18fd58c303 # v1.6.2
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
distribution: "graalvm"
|
||||
cache: "maven"
|
||||
|
||||
- name: Set up NodeJS cache
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: 'commafeed-client/package-lock.json'
|
||||
|
||||
- name: Install Playwright dependencies
|
||||
run: sudo apt-get install -y libgbm1
|
||||
if: matrix.os != 'windows-latest'
|
||||
@@ -67,20 +73,20 @@ jobs:
|
||||
|
||||
# Upload artifacts
|
||||
- name: Upload cross-platform app
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: matrix.os == 'ubuntu-latest' # we only need to upload the cross-platform artifact once per database
|
||||
with:
|
||||
name: commafeed-${{ matrix.database }}-jvm
|
||||
path: commafeed-server/target/commafeed-*.zip
|
||||
|
||||
- name: Upload native executable
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: commafeed-${{ matrix.database }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: commafeed-server/target/commafeed-*-runner*
|
||||
|
||||
- name: Upload pages
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.database == 'h2' # we only need to upload the pages once
|
||||
with:
|
||||
path: target/pages
|
||||
@@ -98,23 +104,23 @@ jobs:
|
||||
steps:
|
||||
# Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Setup
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Install required packages
|
||||
run: sudo apt-get install -y rename unzip
|
||||
|
||||
# Prepare artifacts
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: commafeed-${{ matrix.database }}-*
|
||||
path: ./artifacts
|
||||
@@ -135,7 +141,7 @@ jobs:
|
||||
|
||||
# Docker
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
if: ${{ env.DOCKERHUB_USERNAME != '' }}
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -143,7 +149,7 @@ jobs:
|
||||
|
||||
## build but don't push for PRs and renovate
|
||||
- name: Docker build - native
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: commafeed-server/src/main/docker/Dockerfile.native
|
||||
@@ -151,7 +157,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
||||
- name: Docker build - jvm
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: commafeed-server/src/main/docker/Dockerfile.jvm
|
||||
@@ -160,7 +166,7 @@ jobs:
|
||||
|
||||
## build and push tag
|
||||
- name: Docker build and push tag - native
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -172,7 +178,7 @@ jobs:
|
||||
athou/commafeed:${{ github.ref_name }}-${{ matrix.database }}
|
||||
|
||||
- name: Docker build and push tag - jvm
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -185,7 +191,7 @@ jobs:
|
||||
|
||||
## build and push master
|
||||
- name: Docker build and push master - native
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -195,7 +201,7 @@ jobs:
|
||||
tags: athou/commafeed:master-${{ matrix.database }}
|
||||
|
||||
- name: Docker build and push master - jvm
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -215,12 +221,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: commafeed-*
|
||||
path: ./artifacts
|
||||
@@ -230,13 +236,13 @@ jobs:
|
||||
run: chmod +x artifacts/*-runner
|
||||
|
||||
- name: Extract Changelog Entry
|
||||
uses: mindsers/changelog-reader-action@32aa5b4c155d76c94e4ec883a223c947b2f02656 # v2
|
||||
uses: mindsers/changelog-reader-action@1faaf50aa09d5793d9a100819973df801febfb31 # v2.4.0
|
||||
id: changelog_reader
|
||||
with:
|
||||
version: ${{ github.ref_name }}
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1
|
||||
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
|
||||
with:
|
||||
name: CommaFeed ${{ github.ref_name }}
|
||||
body: ${{ steps.changelog_reader.outputs.changes }}
|
||||
@@ -249,12 +255,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
|
||||
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -274,5 +280,5 @@ jobs:
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
|
||||
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
id: deployment
|
||||
|
||||
2
.mvn/wrapper/maven-wrapper.properties
vendored
2
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1,3 +1,3 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## [7.2.0]
|
||||
|
||||
- Add support for icons declared in feeds. This is useful for feeds exposed by RSS bridges (#2048)
|
||||
- Search now also works for starred entries (#2217)
|
||||
- The error message when subscribing to a feed is now properly displayed again
|
||||
- Small performance improvements to the feed refresh engine
|
||||
- Removed the Pocket sharing feature because the Pocket service has been discontinued
|
||||
- Prevent the image proxy feature to access any URL to avoid SSRF attacks
|
||||
- Strengthened the SSRF protection by also blocking ULA and CGNAT ranges
|
||||
- Prevent DDOS attacks by filtering OPML files during imports
|
||||
|
||||
## [7.1.0]
|
||||
|
||||
- Display the unread count in the header on mobile, since the tree is hidden by default and the unread count is not visible otherwise (#2055)
|
||||
- The feed refresh engine is now a little bit more performant (#2089)
|
||||
- On shutdown, give the feed refresh engine some time to finish refreshing feeds before killing it. This is controlled by the new `commafeed.shutdown-timeout` setting, which defaults to 2 seconds
|
||||
|
||||
## [7.0.0]
|
||||
|
||||
- Replaced the JEXL filter expression for marking feed entries as read automatically with a user-friendly visual query builder. Expressions are now evaluated with Common Expression Language, which is safer than JEXL and sanboxed by default.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
cd commafeed-client && npx lint-staged
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.5.2/schema.json",
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { LinguiConfig } from "@lingui/conf"
|
||||
import { formatter } from "@lingui/format-po"
|
||||
|
||||
const config: LinguiConfig = {
|
||||
locales: [
|
||||
@@ -38,11 +39,9 @@ const config: LinguiConfig = {
|
||||
exclude: ["src/locales/**"],
|
||||
},
|
||||
],
|
||||
format: "po",
|
||||
formatOptions: {
|
||||
origins: true,
|
||||
format: formatter({
|
||||
lineNumbers: false,
|
||||
},
|
||||
}),
|
||||
sourceLocale: "en",
|
||||
fallbackLocales: {
|
||||
default: "en",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export default {
|
||||
"src/**/*.{js,jsx,ts,tsx}": () => ["npm run i18n:extract", "git diff --exit-code commafeed-client/src/locales/en/messages.po"],
|
||||
}
|
||||
5379
commafeed-client/package-lock.json
generated
5379
commafeed-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,75 +12,73 @@
|
||||
"test:ci": "vitest run",
|
||||
"lint": "biome check",
|
||||
"lint:fix": "biome check --write",
|
||||
"i18n:extract": "lingui extract --clean",
|
||||
"prepare": "cd .. && husky ./commafeed-client/.husky"
|
||||
"i18n:extract": "lingui extract --clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@fontsource/open-sans": "^5.2.7",
|
||||
"@lingui/core": "^5.9.1",
|
||||
"@lingui/react": "^5.9.1",
|
||||
"@mantine/core": "^8.3.15",
|
||||
"@mantine/form": "^8.3.15",
|
||||
"@mantine/hooks": "^8.3.15",
|
||||
"@mantine/modals": "^8.3.15",
|
||||
"@mantine/notifications": "^8.3.15",
|
||||
"@mantine/spotlight": "^8.3.15",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@react-querybuilder/mantine": "^8.14.0",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"axios": "^1.13.5",
|
||||
"dayjs": "^1.11.19",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"interweave": "^13.1.1",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"react": "^19.2.4",
|
||||
"react-async-hook": "^4.0.0",
|
||||
"react-contexify": "^6.0.0",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-draggable": "^4.5.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-infinite-scroller": "^1.2.6",
|
||||
"react-querybuilder": "^8.14.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"react-swipeable": "^7.0.2",
|
||||
"style-to-object": "^1.0.14",
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"tinycon": "^0.6.8",
|
||||
"tss-react": "^4.9.20",
|
||||
"websocket-heartbeat-js": "^1.1.3"
|
||||
"@emotion/react": "11.14.0",
|
||||
"@fontsource/open-sans": "5.2.7",
|
||||
"@lingui/core": "6.5.0",
|
||||
"@lingui/react": "6.5.0",
|
||||
"@mantine/core": "9.4.1",
|
||||
"@mantine/dates": "9.4.1",
|
||||
"@mantine/form": "9.4.1",
|
||||
"@mantine/hooks": "9.4.1",
|
||||
"@mantine/modals": "9.4.1",
|
||||
"@mantine/notifications": "9.4.1",
|
||||
"@mantine/spotlight": "9.4.1",
|
||||
"@monaco-editor/react": "4.7.0",
|
||||
"@react-querybuilder/mantine": "8.20.2",
|
||||
"@reduxjs/toolkit": "2.12.0",
|
||||
"@rolldown/plugin-babel": "0.2.3",
|
||||
"axios": "1.18.1",
|
||||
"dayjs": "1.11.21",
|
||||
"escape-string-regexp": "5.0.0",
|
||||
"interweave": "13.1.1",
|
||||
"monaco-editor": "0.55.1",
|
||||
"mousetrap": "1.6.5",
|
||||
"react": "19.2.7",
|
||||
"react-async-hook": "4.0.0",
|
||||
"react-contexify": "6.0.0",
|
||||
"react-dom": "19.2.7",
|
||||
"react-draggable": "4.7.0",
|
||||
"react-icons": "5.7.0",
|
||||
"react-infinite-scroller": "1.2.6",
|
||||
"react-querybuilder": "8.20.2",
|
||||
"react-redux": "9.3.0",
|
||||
"react-router-dom": "7.18.1",
|
||||
"react-swipeable": "7.0.2",
|
||||
"style-to-object": "2.0.2",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tinycon": "0.6.8",
|
||||
"tss-react": "4.9.21",
|
||||
"websocket-heartbeat-js": "1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.2",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.1",
|
||||
"@lingui/cli": "^5.9.1",
|
||||
"@lingui/vite-plugin": "^5.9.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-infinite-scroller": "^1.2.5",
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@types/tinycon": "^0.6.7",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"@biomejs/biome": "2.5.2",
|
||||
"@lingui/babel-plugin-lingui-macro": "6.5.0",
|
||||
"@lingui/cli": "6.5.0",
|
||||
"@lingui/vite-plugin": "6.5.0",
|
||||
"@testing-library/jest-dom": "6.9.1",
|
||||
"@testing-library/react": "16.3.2",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/mousetrap": "1.6.15",
|
||||
"@types/react": "19.2.17",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/react-infinite-scroller": "1.2.5",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycon": "0.6.7",
|
||||
"@vitejs/plugin-react": "6.0.3",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^28.1.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-checker": "^0.12.0",
|
||||
"vite-tsconfig-paths": "^6.1.1",
|
||||
"vitest": "^4.0.18",
|
||||
"yaml": "^2.8.2"
|
||||
"jsdom": "29.1.1",
|
||||
"typescript": "6.0.3",
|
||||
"vite": "8.1.3",
|
||||
"vite-plugin-checker": "0.14.4",
|
||||
"vitest": "4.1.10",
|
||||
"yaml": "2.9.0"
|
||||
},
|
||||
"overrides": {
|
||||
"react-infinite-scroller": {
|
||||
"react": "^19.2.4"
|
||||
"react": "19.2.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<parent>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>7.0.0</version>
|
||||
<version>7.2.0</version>
|
||||
</parent>
|
||||
<artifactId>commafeed-client</artifactId>
|
||||
<name>CommaFeed Client</name>
|
||||
|
||||
<properties>
|
||||
<!-- renovate: datasource=node-version depName=node -->
|
||||
<node.version>v24.13.1</node.version>
|
||||
<node.version>v24.18.0</node.version>
|
||||
<!-- renovate: datasource=npm depName=npm -->
|
||||
<npm.version>11.10.0</npm.version>
|
||||
<npm.version>11.18.0</npm.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@@ -23,7 +23,7 @@
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>2.0.1</version>
|
||||
<?m2e ignore?>
|
||||
<executions>
|
||||
<execution>
|
||||
@@ -72,7 +72,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy web interface to resources</id>
|
||||
@@ -94,4 +94,49 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<!-- This profile is used to kill the Biome process on Windows -->
|
||||
<!-- npm ci can fail if Biome is running (e.g., in the IDE) because it locks some files -->
|
||||
<profile>
|
||||
<id>kill-biome</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>Windows</family>
|
||||
</os>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.6.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>kill-biome</id>
|
||||
<phase>initialize</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>taskkill</executable>
|
||||
<arguments>
|
||||
<argument>/F</argument>
|
||||
<argument>/IM</argument>
|
||||
<argument>biome.exe</argument>
|
||||
</arguments>
|
||||
<successCodes>
|
||||
<successCode>0</successCode>
|
||||
<!-- taskkill returns 128 if the process is not found, which is fine in this case -->
|
||||
<successCode>128</successCode>
|
||||
</successCodes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
@@ -1,62 +1,40 @@
|
||||
<?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" />
|
||||
height="393.84613"
|
||||
width="393.84613"
|
||||
viewBox="0 0 5.0480766 5.0480766"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<title>CommaFeed favicon</title>
|
||||
<defs
|
||||
id="defs3" />
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,24 +1,19 @@
|
||||
import { i18n } from "@lingui/core"
|
||||
import { I18nProvider } from "@lingui/react"
|
||||
import { MantineProvider } from "@mantine/core"
|
||||
import { MantineProvider, v8CssVariablesResolver } from "@mantine/core"
|
||||
import { ModalsProvider } from "@mantine/modals"
|
||||
import { Notifications } from "@mantine/notifications"
|
||||
import type React from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useEffect } from "react"
|
||||
import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom"
|
||||
import Tinycon from "tinycon"
|
||||
import { Constants } from "@/app/constants"
|
||||
import { redirectTo } from "@/app/redirect/slice"
|
||||
import { redirectToInitialSetup } from "@/app/redirect/thunks"
|
||||
import { reloadServerInfos } from "@/app/server/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import { categoryUnreadCount } from "@/app/utils"
|
||||
import { DisablePullToRefresh } from "@/components/DisablePullToRefresh"
|
||||
import { ErrorBoundary } from "@/components/ErrorBoundary"
|
||||
import { Header } from "@/components/header/Header"
|
||||
import { Tree } from "@/components/sidebar/Tree"
|
||||
import { useAppLoading } from "@/hooks/useAppLoading"
|
||||
import { useBrowserExtension } from "@/hooks/useBrowserExtension"
|
||||
import { useI18n } from "@/i18n"
|
||||
import { AdminUsersPage } from "@/pages/admin/AdminUsersPage"
|
||||
import { MetricsPage } from "@/pages/admin/MetricsPage"
|
||||
@@ -48,6 +43,8 @@ function Providers(
|
||||
<I18nProvider i18n={i18n}>
|
||||
<MantineProvider
|
||||
defaultColorScheme="auto"
|
||||
// keep using css variables from mantine v8
|
||||
cssVariablesResolver={v8CssVariablesResolver}
|
||||
theme={{
|
||||
primaryColor: primaryColor,
|
||||
fontFamily: "Open Sans",
|
||||
@@ -144,83 +141,8 @@ function RedirectHandler() {
|
||||
return null
|
||||
}
|
||||
|
||||
function UnreadCountTitleHandler({
|
||||
enabled,
|
||||
}: Readonly<{
|
||||
enabled?: boolean
|
||||
}>) {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
return <title>{enabled && unreadCount > 0 ? `(${unreadCount}) CommaFeed` : "CommaFeed"}</title>
|
||||
}
|
||||
|
||||
function UnreadCountFaviconHandler({ enabled }: { enabled?: boolean }) {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
useEffect(() => {
|
||||
if (enabled && unreadCount > 0) {
|
||||
Tinycon.setBubble(unreadCount)
|
||||
} else {
|
||||
Tinycon.reset()
|
||||
}
|
||||
}, [unreadCount, enabled])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function BrowserExtensionBadgeUnreadCountHandler() {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const { setBadgeUnreadCount } = useBrowserExtension()
|
||||
useEffect(() => {
|
||||
if (!root) return
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
setBadgeUnreadCount(unreadCount)
|
||||
}, [root, setBadgeUnreadCount])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CustomJsHandler() {
|
||||
const [scriptLoaded, setScriptLoaded] = useState(false)
|
||||
const { loading } = useAppLoading()
|
||||
|
||||
useEffect(() => {
|
||||
if (scriptLoaded || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement("script")
|
||||
script.src = "custom_js.js"
|
||||
script.async = true
|
||||
document.body.appendChild(script)
|
||||
|
||||
setScriptLoaded(true)
|
||||
|
||||
return () => script.remove()
|
||||
}, [scriptLoaded, loading])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CustomCssHandler() {
|
||||
useEffect(() => {
|
||||
const link = document.createElement("link")
|
||||
link.rel = "stylesheet"
|
||||
link.type = "text/css"
|
||||
link.href = "custom_css.css"
|
||||
document.head.appendChild(link)
|
||||
|
||||
return () => link.remove()
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function App() {
|
||||
useI18n()
|
||||
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
||||
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
||||
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -229,13 +151,6 @@ export function App() {
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<UnreadCountTitleHandler enabled={unreadCountTitle} />
|
||||
<UnreadCountFaviconHandler enabled={unreadCountFavicon} />
|
||||
<BrowserExtensionBadgeUnreadCountHandler />
|
||||
<CustomJsHandler />
|
||||
<CustomCssHandler />
|
||||
<DisablePullToRefresh enabled={disablePullToRefresh} />
|
||||
|
||||
<HashRouter>
|
||||
<InitialSetupHandler />
|
||||
<RedirectHandler />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IconType } from "react-icons"
|
||||
import { FaAt } from "react-icons/fa"
|
||||
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiX } from "react-icons/si"
|
||||
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiTumblr, SiX } from "react-icons/si"
|
||||
import type { Category, Entry, SharingSettings } from "./types"
|
||||
|
||||
const categories: Record<string, Omit<Category, "name">> = {
|
||||
@@ -58,12 +58,6 @@ const sharing: {
|
||||
color: "#375672",
|
||||
url: (url, desc) => `https://www.tumblr.com/share/link?url=${url}&name=${desc}`,
|
||||
},
|
||||
pocket: {
|
||||
label: "Pocket",
|
||||
icon: SiPocket,
|
||||
color: "#EF4154",
|
||||
url: (url, desc) => `https://getpocket.com/save?url=${url}&title=${desc}`,
|
||||
},
|
||||
instapaper: {
|
||||
label: "Instapaper",
|
||||
icon: SiInstapaper,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { configureStore } from "@reduxjs/toolkit"
|
||||
import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
|
||||
import { shallowEqual, type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
|
||||
import { entriesSlice } from "@/app/entries/slice"
|
||||
import { redirectSlice } from "@/app/redirect/slice"
|
||||
import { serverSlice } from "@/app/server/slice"
|
||||
@@ -41,3 +41,4 @@ export type AppDispatch = typeof store.dispatch
|
||||
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||
export const useShallowEqualAppSelector: TypedUseSelectorHook<RootState> = selector => useSelector(selector, shallowEqual)
|
||||
|
||||
@@ -249,7 +249,6 @@ export interface SharingSettings {
|
||||
facebook: boolean
|
||||
twitter: boolean
|
||||
tumblr: boolean
|
||||
pocket: boolean
|
||||
instapaper: boolean
|
||||
buffer: boolean
|
||||
}
|
||||
|
||||
@@ -1,32 +1,8 @@
|
||||
import { t } from "@lingui/core/macro"
|
||||
import { showNotification } from "@mantine/notifications"
|
||||
import { createSlice, isAnyOf, type PayloadAction } from "@reduxjs/toolkit"
|
||||
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
||||
import type { LocalSettings, Settings, UserModel, ViewMode } from "@/app/types"
|
||||
import {
|
||||
changeCustomContextMenu,
|
||||
changeDisablePullToRefresh,
|
||||
changeEntriesToKeepOnTopWhenScrolling,
|
||||
changeExternalLinkIconDisplayMode,
|
||||
changeLanguage,
|
||||
changeMarkAllAsReadConfirmation,
|
||||
changeMarkAllAsReadNavigateToUnread,
|
||||
changeMobileFooter,
|
||||
changePrimaryColor,
|
||||
changePushNotificationSettings,
|
||||
changeReadingMode,
|
||||
changeReadingOrder,
|
||||
changeScrollMarks,
|
||||
changeScrollMode,
|
||||
changeScrollSpeed,
|
||||
changeSharingSetting,
|
||||
changeShowRead,
|
||||
changeStarIconDisplayMode,
|
||||
changeUnreadCountFavicon,
|
||||
changeUnreadCountTitle,
|
||||
reloadProfile,
|
||||
reloadSettings,
|
||||
reloadTags,
|
||||
} from "./thunks"
|
||||
import { changeSettings, reloadProfile, reloadSettings, reloadTags } from "./thunks"
|
||||
|
||||
interface UserState {
|
||||
settings?: Settings
|
||||
@@ -73,115 +49,16 @@ export const userSlice = createSlice({
|
||||
builder.addCase(reloadTags.fulfilled, (state, action) => {
|
||||
state.tags = action.payload
|
||||
})
|
||||
builder.addCase(changeReadingMode.pending, (state, action) => {
|
||||
builder.addCase(changeSettings.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.readingMode = action.meta.arg
|
||||
state.settings = { ...state.settings, ...action.meta.arg }
|
||||
})
|
||||
builder.addCase(changeReadingOrder.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.readingOrder = action.meta.arg
|
||||
builder.addCase(changeSettings.fulfilled, () => {
|
||||
showNotification({
|
||||
message: t`Settings saved.`,
|
||||
color: "green",
|
||||
})
|
||||
})
|
||||
builder.addCase(changeLanguage.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.language = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeScrollSpeed.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.scrollSpeed = action.meta.arg ? 400 : 0
|
||||
})
|
||||
builder.addCase(changeShowRead.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.showRead = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeScrollMarks.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.scrollMarks = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeScrollMode.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.scrollMode = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeEntriesToKeepOnTopWhenScrolling.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.entriesToKeepOnTopWhenScrolling = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeStarIconDisplayMode.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.starIconDisplayMode = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeExternalLinkIconDisplayMode.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.externalLinkIconDisplayMode = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.markAllAsReadConfirmation = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeMarkAllAsReadNavigateToUnread.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.markAllAsReadNavigateToNextUnread = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeCustomContextMenu.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.customContextMenu = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeMobileFooter.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.mobileFooter = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeUnreadCountTitle.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.unreadCountTitle = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeUnreadCountFavicon.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.unreadCountFavicon = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeDisablePullToRefresh.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.disablePullToRefresh = action.meta.arg
|
||||
})
|
||||
builder.addCase(changePrimaryColor.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.primaryColor = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeSharingSetting.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
|
||||
})
|
||||
builder.addCase(changePushNotificationSettings.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.pushNotificationSettings = action.meta.arg
|
||||
})
|
||||
|
||||
builder.addMatcher(
|
||||
isAnyOf(
|
||||
changeLanguage.fulfilled,
|
||||
changeScrollSpeed.fulfilled,
|
||||
changeShowRead.fulfilled,
|
||||
changeScrollMarks.fulfilled,
|
||||
changeScrollMode.fulfilled,
|
||||
changeEntriesToKeepOnTopWhenScrolling.fulfilled,
|
||||
changeStarIconDisplayMode.fulfilled,
|
||||
changeExternalLinkIconDisplayMode.fulfilled,
|
||||
changeMarkAllAsReadConfirmation.fulfilled,
|
||||
changeMarkAllAsReadNavigateToUnread.fulfilled,
|
||||
changeCustomContextMenu.fulfilled,
|
||||
changeMobileFooter.fulfilled,
|
||||
changeUnreadCountTitle.fulfilled,
|
||||
changeUnreadCountFavicon.fulfilled,
|
||||
changeDisablePullToRefresh.fulfilled,
|
||||
changePrimaryColor.fulfilled,
|
||||
changeSharingSetting.fulfilled,
|
||||
changePushNotificationSettings.fulfilled
|
||||
),
|
||||
() => {
|
||||
showNotification({
|
||||
message: t`Settings saved.`,
|
||||
color: "green",
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createAppAsyncThunk } from "@/app/async-thunk"
|
||||
import { client } from "@/app/client"
|
||||
import { reloadEntries } from "@/app/entries/thunks"
|
||||
import type { IconDisplayMode, PushNotificationSettings, ReadingMode, ReadingOrder, ScrollMode, SharingSettings } from "@/app/types"
|
||||
import type { Settings } from "@/app/types"
|
||||
|
||||
export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data))
|
||||
|
||||
@@ -9,163 +8,8 @@ export const reloadProfile = createAppAsyncThunk("profile/reload", async () => a
|
||||
|
||||
export const reloadTags = createAppAsyncThunk("entries/tags", async () => await client.entry.getTags().then(r => r.data))
|
||||
|
||||
export const changeReadingMode = createAppAsyncThunk("settings/readingMode", (readingMode: ReadingMode, thunkApi) => {
|
||||
export const changeSettings = createAppAsyncThunk("settings/change", async (newSettings: Partial<Settings>, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, readingMode })
|
||||
thunkApi.dispatch(reloadEntries())
|
||||
await client.user.saveSettings({ ...settings, ...newSettings })
|
||||
})
|
||||
|
||||
export const changeReadingOrder = createAppAsyncThunk("settings/readingOrder", (readingOrder: ReadingOrder, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, readingOrder })
|
||||
thunkApi.dispatch(reloadEntries())
|
||||
})
|
||||
|
||||
export const changeLanguage = createAppAsyncThunk("settings/language", (language: string, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, language })
|
||||
})
|
||||
|
||||
export const changeScrollSpeed = createAppAsyncThunk("settings/scrollSpeed", (speed: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 })
|
||||
})
|
||||
|
||||
export const changeShowRead = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, showRead })
|
||||
})
|
||||
|
||||
export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (scrollMarks: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, scrollMarks })
|
||||
})
|
||||
|
||||
export const changeScrollMode = createAppAsyncThunk("settings/scrollMode", (scrollMode: ScrollMode, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, scrollMode })
|
||||
})
|
||||
|
||||
export const changeEntriesToKeepOnTopWhenScrolling = createAppAsyncThunk(
|
||||
"settings/entriesToKeepOnTopWhenScrolling",
|
||||
(entriesToKeepOnTopWhenScrolling: number, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, entriesToKeepOnTopWhenScrolling })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeStarIconDisplayMode = createAppAsyncThunk(
|
||||
"settings/starIconDisplayMode",
|
||||
(starIconDisplayMode: IconDisplayMode, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, starIconDisplayMode })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeExternalLinkIconDisplayMode = createAppAsyncThunk(
|
||||
"settings/externalLinkIconDisplayMode",
|
||||
(externalLinkIconDisplayMode: IconDisplayMode, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, externalLinkIconDisplayMode })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeMarkAllAsReadConfirmation = createAppAsyncThunk(
|
||||
"settings/markAllAsReadConfirmation",
|
||||
(markAllAsReadConfirmation: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, markAllAsReadConfirmation })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeMarkAllAsReadNavigateToUnread = createAppAsyncThunk(
|
||||
"settings/markAllAsReadNavigateToUnread",
|
||||
(markAllAsReadNavigateToNextUnread: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, markAllAsReadNavigateToNextUnread })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeCustomContextMenu = createAppAsyncThunk("settings/customContextMenu", (customContextMenu: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, customContextMenu })
|
||||
})
|
||||
|
||||
export const changeMobileFooter = createAppAsyncThunk("settings/mobileFooter", (mobileFooter: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, mobileFooter })
|
||||
})
|
||||
|
||||
export const changeUnreadCountTitle = createAppAsyncThunk("settings/unreadCountTitle", (unreadCountTitle: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, unreadCountTitle })
|
||||
})
|
||||
|
||||
export const changeUnreadCountFavicon = createAppAsyncThunk("settings/unreadCountFavicon", (unreadCountFavicon: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, unreadCountFavicon })
|
||||
})
|
||||
|
||||
export const changeDisablePullToRefresh = createAppAsyncThunk(
|
||||
"settings/disablePullToRefresh",
|
||||
(disablePullToRefresh: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, disablePullToRefresh })
|
||||
}
|
||||
)
|
||||
|
||||
export const changePrimaryColor = createAppAsyncThunk("settings/primaryColor", (primaryColor: string, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, primaryColor })
|
||||
})
|
||||
|
||||
export const changeSharingSetting = createAppAsyncThunk(
|
||||
"settings/sharingSetting",
|
||||
(
|
||||
sharingSetting: {
|
||||
site: keyof SharingSettings
|
||||
value: boolean
|
||||
},
|
||||
thunkApi
|
||||
) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({
|
||||
...settings,
|
||||
sharingSettings: {
|
||||
...settings.sharingSettings,
|
||||
[sharingSetting.site]: sharingSetting.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
export const changePushNotificationSettings = createAppAsyncThunk(
|
||||
"settings/pushNotificationSettings",
|
||||
(pushNotificationSettings: PushNotificationSettings, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({
|
||||
...settings,
|
||||
pushNotificationSettings,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg height="512" width="512" viewBox="0 0 6.5625 6.5625" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>CommaFeed logo</title>
|
||||
<rect fill="#f88a14" rx="0.7" ry="0.7" height="6.5625" width="6.5625" />
|
||||
<path d="m1.9761,1.5289c2.9002,0,2.9002,2.9101,2.9002,2.9101" fill="none" stroke="#FFF" stroke-linecap="round"
|
||||
stroke-width="0.78125" />
|
||||
stroke-width="0.78125" />
|
||||
<path d="m1.9688,2.875c1.5705-0.00908,1.5705,1.5639,1.5705,1.5639" fill="none" stroke="#FFF" stroke-linecap="round"
|
||||
stroke-width="0.78125" />
|
||||
stroke-width="0.78125" />
|
||||
<path d="m2.6503,4.4062c0,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.36423,0-0.6595-0.29265-0.6595-0.65365s0.29527-0.65365,0.6595-0.65365,0.68159,0.29265,0.68159,0.65365z"
|
||||
fill="#FFF" />
|
||||
fill="#FFF" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 831 B |
@@ -1,7 +1,6 @@
|
||||
import type { MessageDescriptor } from "@lingui/core"
|
||||
import { useLingui } from "@lingui/react"
|
||||
import { ActionIcon, Box, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core"
|
||||
import type { ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon"
|
||||
import { ActionIcon, type ActionIconVariant, Box, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core"
|
||||
import { forwardRef, type MouseEventHandler, type ReactNode } from "react"
|
||||
import { Constants } from "@/app/constants"
|
||||
import { useActionButton } from "@/hooks/useActionButton"
|
||||
|
||||
@@ -14,6 +14,7 @@ interface ImageWithPlaceholderWhileLoadingProps {
|
||||
placeholderHeight?: number
|
||||
placeholderBackgroundColor?: string
|
||||
placeholderIconSize?: number
|
||||
onError?: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void
|
||||
}
|
||||
|
||||
const useStyles = tss
|
||||
@@ -44,6 +45,7 @@ export function ImageWithPlaceholderWhileLoading({
|
||||
title,
|
||||
width,
|
||||
style,
|
||||
onError,
|
||||
}: Readonly<ImageWithPlaceholderWhileLoadingProps>) {
|
||||
const { classes } = useStyles({
|
||||
placeholderWidth,
|
||||
@@ -70,6 +72,7 @@ export function ImageWithPlaceholderWhileLoading({
|
||||
width={width}
|
||||
height={height}
|
||||
onLoad={() => setLoading(false)}
|
||||
onError={onError}
|
||||
style={{
|
||||
...style,
|
||||
display: loading ? "none" : (style?.display ?? "initial"),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useTimeout } from "@mantine/hooks"
|
||||
import { useState } from "react"
|
||||
import { ImageWithPlaceholderWhileLoading } from "@/components/ImageWithPlaceholderWhileLoading"
|
||||
|
||||
export interface FeedFaviconProps {
|
||||
@@ -6,9 +8,23 @@ export interface FeedFaviconProps {
|
||||
}
|
||||
|
||||
export function FeedFavicon({ url, size = 18 }: Readonly<FeedFaviconProps>) {
|
||||
// the backend always returns a favicon except when the feed has never been fetched
|
||||
// this can happen when the user subscribes to a feed, the feed is added to the tree but the feed has not been fetched yet
|
||||
// in this case we retry every second up to 3 times until the feed is fetched and the favicon is available
|
||||
const [timestamp, setTimestamp] = useState(0)
|
||||
const [retryCount, setRetryCount] = useState(0)
|
||||
const { start: retry } = useTimeout(() => setTimestamp(Date.now()), 1000)
|
||||
|
||||
const urlWithTimestamp = url + (timestamp === 0 ? "" : `?t=${timestamp}`)
|
||||
const handleError = () => {
|
||||
if (retryCount < 3) {
|
||||
setRetryCount(c => c + 1)
|
||||
retry()
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ImageWithPlaceholderWhileLoading
|
||||
src={url}
|
||||
src={urlWithTimestamp}
|
||||
alt="feed favicon"
|
||||
width={size}
|
||||
height={size}
|
||||
@@ -16,6 +32,7 @@ export function FeedFavicon({ url, size = 18 }: Readonly<FeedFaviconProps>) {
|
||||
placeholderHeight={size}
|
||||
placeholderBackgroundColor="inherit"
|
||||
placeholderIconSize={size}
|
||||
onError={handleError}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { msg } from "@lingui/core/macro"
|
||||
import { useLingui } from "@lingui/react"
|
||||
import { Select, type SelectProps } from "@mantine/core"
|
||||
import type { ComboboxItem } from "@mantine/core/lib/components/Combobox/Combobox.types"
|
||||
import { type ComboboxItem, Select, type SelectProps } from "@mantine/core"
|
||||
import { Constants } from "@/app/constants"
|
||||
import { useAppSelector } from "@/app/store"
|
||||
import type { Category } from "@/app/types"
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import { Highlight } from "@mantine/core"
|
||||
import { useAppSelector } from "@/app/store"
|
||||
import type { Entry } from "@/app/types"
|
||||
import { tss } from "@/tss"
|
||||
|
||||
export interface FeedEntryTitleProps {
|
||||
entry: Entry
|
||||
}
|
||||
|
||||
const useStyles = tss.create(() => ({
|
||||
content: {
|
||||
textWrap: "inherit",
|
||||
},
|
||||
}))
|
||||
|
||||
export function FeedEntryTitle(props: Readonly<FeedEntryTitleProps>) {
|
||||
const search = useAppSelector(state => state.entries.search)
|
||||
const keywords = search?.split(" ")
|
||||
|
||||
const { classes } = useStyles()
|
||||
return (
|
||||
<Highlight
|
||||
inherit
|
||||
highlight={keywords ?? ""}
|
||||
// make sure ellipsis is shown when title is too long
|
||||
span
|
||||
className={classes.content}
|
||||
>
|
||||
{props.entry.title}
|
||||
</Highlight>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "react-icons/tb"
|
||||
import { markAllAsReadWithConfirmationIfRequired, reloadEntries, search, selectNextEntry, selectPreviousEntry } from "@/app/entries/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import { changeReadingMode, changeReadingOrder } from "@/app/user/thunks"
|
||||
import { changeSettings } from "@/app/user/thunks"
|
||||
import { ActionButton } from "@/components/ActionButton"
|
||||
import { Loader } from "@/components/Loader"
|
||||
import { useActionButton } from "@/hooks/useActionButton"
|
||||
@@ -73,6 +73,25 @@ export function Header() {
|
||||
}, [setValues, searchFromStore])
|
||||
|
||||
if (!settings) return <Loader />
|
||||
|
||||
const toggleReadingMode = async () => {
|
||||
await dispatch(
|
||||
changeSettings({
|
||||
readingMode: settings.readingMode === "all" ? "unread" : "all",
|
||||
})
|
||||
)
|
||||
dispatch(reloadEntries())
|
||||
}
|
||||
|
||||
const toggleReadingOrder = async () => {
|
||||
await dispatch(
|
||||
changeSettings({
|
||||
readingOrder: settings.readingOrder === "asc" ? "desc" : "asc",
|
||||
})
|
||||
)
|
||||
dispatch(reloadEntries())
|
||||
}
|
||||
|
||||
return (
|
||||
<Center className="cf-toolbar-wrapper">
|
||||
<HeaderToolbar>
|
||||
@@ -121,12 +140,12 @@ export function Header() {
|
||||
<ActionButton
|
||||
icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />}
|
||||
label={settings.readingMode === "all" ? msg`All` : msg`Unread`}
|
||||
onClick={async () => await dispatch(changeReadingMode(settings.readingMode === "all" ? "unread" : "all"))}
|
||||
onClick={toggleReadingMode}
|
||||
/>
|
||||
<ActionButton
|
||||
icon={settings.readingOrder === "asc" ? <TbSortAscending size={iconSize} /> : <TbSortDescending size={iconSize} />}
|
||||
label={settings.readingOrder === "asc" ? msg`Asc` : msg`Desc`}
|
||||
onClick={async () => await dispatch(changeReadingOrder(settings.readingOrder === "asc" ? "desc" : "asc"))}
|
||||
onClick={toggleReadingOrder}
|
||||
/>
|
||||
|
||||
<Popover>
|
||||
|
||||
@@ -1,51 +1,29 @@
|
||||
import { msg } from "@lingui/core/macro"
|
||||
import { useLingui } from "@lingui/react"
|
||||
import { Trans } from "@lingui/react/macro"
|
||||
import { Box, Divider, Group, NumberInput, Radio, Select, type SelectProps, SimpleGrid, Stack, Switch } from "@mantine/core"
|
||||
import type { ComboboxData } from "@mantine/core/lib/components/Combobox/Combobox.types"
|
||||
import {
|
||||
Box,
|
||||
type ComboboxData,
|
||||
Divider,
|
||||
Group,
|
||||
Loader,
|
||||
NumberInput,
|
||||
Radio,
|
||||
Select,
|
||||
type SelectProps,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Switch,
|
||||
} from "@mantine/core"
|
||||
import type { ReactNode } from "react"
|
||||
import { Constants } from "@/app/constants"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import type { IconDisplayMode, ScrollMode, SharingSettings } from "@/app/types"
|
||||
import {
|
||||
changeCustomContextMenu,
|
||||
changeDisablePullToRefresh,
|
||||
changeEntriesToKeepOnTopWhenScrolling,
|
||||
changeExternalLinkIconDisplayMode,
|
||||
changeLanguage,
|
||||
changeMarkAllAsReadConfirmation,
|
||||
changeMarkAllAsReadNavigateToUnread,
|
||||
changeMobileFooter,
|
||||
changePrimaryColor,
|
||||
changeScrollMarks,
|
||||
changeScrollMode,
|
||||
changeScrollSpeed,
|
||||
changeSharingSetting,
|
||||
changeShowRead,
|
||||
changeStarIconDisplayMode,
|
||||
changeUnreadCountFavicon,
|
||||
changeUnreadCountTitle,
|
||||
} from "@/app/user/thunks"
|
||||
import { changeSettings } from "@/app/user/thunks"
|
||||
import { locales } from "@/i18n"
|
||||
|
||||
export function DisplaySettings() {
|
||||
const language = useAppSelector(state => state.user.settings?.language)
|
||||
const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed)
|
||||
const showRead = useAppSelector(state => state.user.settings?.showRead)
|
||||
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
||||
const scrollMode = useAppSelector(state => state.user.settings?.scrollMode)
|
||||
const entriesToKeepOnTop = useAppSelector(state => state.user.settings?.entriesToKeepOnTopWhenScrolling)
|
||||
const starIconDisplayMode = useAppSelector(state => state.user.settings?.starIconDisplayMode)
|
||||
const externalLinkIconDisplayMode = useAppSelector(state => state.user.settings?.externalLinkIconDisplayMode)
|
||||
const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation)
|
||||
const markAllAsReadNavigateToNextUnread = useAppSelector(state => state.user.settings?.markAllAsReadNavigateToNextUnread)
|
||||
const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu)
|
||||
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
|
||||
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
||||
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
||||
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
|
||||
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
|
||||
const primaryColor = useAppSelector(state => state.user.settings?.primaryColor) || Constants.theme.defaultPrimaryColor
|
||||
const settings = useAppSelector(state => state.user.settings)
|
||||
const { _ } = useLingui()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@@ -97,50 +75,51 @@ export function DisplaySettings() {
|
||||
</Group>
|
||||
)
|
||||
|
||||
if (!settings) return <Loader />
|
||||
return (
|
||||
<Stack>
|
||||
<Divider label={<Trans>Display</Trans>} labelPosition="center" />
|
||||
|
||||
<Select
|
||||
label={<Trans>Language</Trans>}
|
||||
value={language}
|
||||
value={settings.language}
|
||||
data={locales.map(l => ({
|
||||
value: l.key,
|
||||
label: l.label,
|
||||
}))}
|
||||
onChange={async s => await (s && dispatch(changeLanguage(s)))}
|
||||
onChange={language => language && dispatch(changeSettings({ language }))}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label={<Trans>Primary color</Trans>}
|
||||
data={colorData}
|
||||
value={primaryColor}
|
||||
onChange={async value => value && (await dispatch(changePrimaryColor(value)))}
|
||||
value={settings.primaryColor}
|
||||
onChange={primaryColor => primaryColor && dispatch(changeSettings({ primaryColor }))}
|
||||
renderOption={colorRenderer}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show feeds and categories with no unread entries</Trans>}
|
||||
checked={showRead}
|
||||
onChange={async e => await dispatch(changeShowRead(e.currentTarget.checked))}
|
||||
checked={settings.showRead}
|
||||
onChange={e => dispatch(changeSettings({ showRead: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show confirmation when marking all entries as read</Trans>}
|
||||
checked={markAllAsReadConfirmation}
|
||||
onChange={async e => await dispatch(changeMarkAllAsReadConfirmation(e.currentTarget.checked))}
|
||||
checked={settings.markAllAsReadConfirmation}
|
||||
onChange={e => dispatch(changeSettings({ markAllAsReadConfirmation: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Navigate to the next category/feed with unread entries when marking all entries as read</Trans>}
|
||||
checked={markAllAsReadNavigateToNextUnread}
|
||||
onChange={async e => await dispatch(changeMarkAllAsReadNavigateToUnread(e.currentTarget.checked))}
|
||||
checked={settings.markAllAsReadNavigateToNextUnread}
|
||||
onChange={e => dispatch(changeSettings({ markAllAsReadNavigateToNextUnread: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>On mobile, show action buttons at the bottom of the screen</Trans>}
|
||||
checked={mobileFooter}
|
||||
onChange={async e => await dispatch(changeMobileFooter(e.currentTarget.checked))}
|
||||
checked={settings.mobileFooter}
|
||||
onChange={e => dispatch(changeSettings({ mobileFooter: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Divider label={<Trans>Scrolling</Trans>} labelPosition="center" />
|
||||
@@ -148,14 +127,14 @@ export function DisplaySettings() {
|
||||
<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))}
|
||||
checked={settings.disablePullToRefresh}
|
||||
onChange={e => dispatch(changeSettings({ disablePullToRefresh: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Radio.Group
|
||||
label={<Trans>Scroll selected entry to the top of the page</Trans>}
|
||||
value={scrollMode}
|
||||
onChange={async value => await dispatch(changeScrollMode(value as ScrollMode))}
|
||||
value={settings.scrollMode}
|
||||
onChange={value => dispatch(changeSettings({ scrollMode: value }))}
|
||||
>
|
||||
<Group mt="xs">
|
||||
{Object.entries(scrollModeOptions).map(e => (
|
||||
@@ -168,56 +147,56 @@ export function DisplaySettings() {
|
||||
label={<Trans>Entries to keep above the selected entry when scrolling</Trans>}
|
||||
description={<Trans>Only applies to compact, cozy and detailed modes</Trans>}
|
||||
min={0}
|
||||
value={entriesToKeepOnTop}
|
||||
onChange={async value => await dispatch(changeEntriesToKeepOnTopWhenScrolling(+value))}
|
||||
value={settings.entriesToKeepOnTopWhenScrolling}
|
||||
onChange={value => dispatch(changeSettings({ entriesToKeepOnTopWhenScrolling: +value }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Scroll smoothly when navigating between entries</Trans>}
|
||||
checked={scrollSpeed ? scrollSpeed > 0 : false}
|
||||
onChange={async e => await dispatch(changeScrollSpeed(e.currentTarget.checked))}
|
||||
checked={settings.scrollSpeed ? settings.scrollSpeed > 0 : false}
|
||||
onChange={e => dispatch(changeSettings({ scrollSpeed: e.currentTarget.checked ? 400 : 0 }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>In expanded view, scrolling through entries mark them as read</Trans>}
|
||||
checked={scrollMarks}
|
||||
onChange={async e => await dispatch(changeScrollMarks(e.currentTarget.checked))}
|
||||
checked={settings.scrollMarks}
|
||||
onChange={e => dispatch(changeSettings({ scrollMarks: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Divider label={<Trans>Browser tab</Trans>} labelPosition="center" />
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show unread count in tab title</Trans>}
|
||||
checked={unreadCountTitle}
|
||||
onChange={async e => await dispatch(changeUnreadCountTitle(e.currentTarget.checked))}
|
||||
checked={settings.unreadCountTitle}
|
||||
onChange={e => dispatch(changeSettings({ unreadCountTitle: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show unread count in tab favicon</Trans>}
|
||||
checked={unreadCountFavicon}
|
||||
onChange={async e => await dispatch(changeUnreadCountFavicon(e.currentTarget.checked))}
|
||||
checked={settings.unreadCountFavicon}
|
||||
onChange={e => dispatch(changeSettings({ unreadCountFavicon: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Divider label={<Trans>Entry headers</Trans>} labelPosition="center" />
|
||||
|
||||
<Select
|
||||
label={<Trans>Show star icon</Trans>}
|
||||
value={starIconDisplayMode}
|
||||
value={settings.starIconDisplayMode}
|
||||
data={displayModeData}
|
||||
onChange={async s => await dispatch(changeStarIconDisplayMode(s as IconDisplayMode))}
|
||||
onChange={value => dispatch(changeSettings({ starIconDisplayMode: value as IconDisplayMode }))}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label={<Trans>Show external link icon</Trans>}
|
||||
value={externalLinkIconDisplayMode}
|
||||
value={settings.externalLinkIconDisplayMode}
|
||||
data={displayModeData}
|
||||
onChange={async s => await dispatch(changeExternalLinkIconDisplayMode(s as IconDisplayMode))}
|
||||
onChange={value => dispatch(changeSettings({ externalLinkIconDisplayMode: value as IconDisplayMode }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show CommaFeed's own context menu on right click</Trans>}
|
||||
checked={customContextMenu}
|
||||
onChange={async e => await dispatch(changeCustomContextMenu(e.currentTarget.checked))}
|
||||
checked={settings.customContextMenu}
|
||||
onChange={e => dispatch(changeSettings({ customContextMenu: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
|
||||
@@ -227,12 +206,14 @@ export function DisplaySettings() {
|
||||
<Switch
|
||||
key={site}
|
||||
label={Constants.sharing[site].label}
|
||||
checked={sharingSettings?.[site]}
|
||||
onChange={async e =>
|
||||
await dispatch(
|
||||
changeSharingSetting({
|
||||
site,
|
||||
value: e.currentTarget.checked,
|
||||
checked={settings.sharingSettings[site]}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
changeSettings({
|
||||
sharingSettings: {
|
||||
...settings.sharingSettings,
|
||||
[site]: e.currentTarget.checked,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { client, errorToStrings } from "@/app/client"
|
||||
import { redirectToSelectedSource } from "@/app/redirect/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import type { PushNotificationSettings as PushNotificationSettingsModel } from "@/app/types"
|
||||
import { changePushNotificationSettings } from "@/app/user/thunks"
|
||||
import { changeSettings } from "@/app/user/thunks"
|
||||
import { Alert } from "@/components/Alert"
|
||||
|
||||
export function PushNotificationSettings() {
|
||||
@@ -25,7 +25,7 @@ export function PushNotificationSettings() {
|
||||
}, [form.initialize, notificationSettings])
|
||||
|
||||
const handleSubmit = (values: PushNotificationSettingsModel) => {
|
||||
dispatch(changePushNotificationSettings(values))
|
||||
dispatch(changeSettings({ pushNotificationSettings: values }))
|
||||
}
|
||||
|
||||
const sendTestPushNotification = useAsyncCallback(client.user.sendTestPushNotification)
|
||||
|
||||
@@ -6,7 +6,11 @@ const useStyles = tss.create(() => ({
|
||||
badge: {
|
||||
width: "3.2rem",
|
||||
// for some reason, mantine Badge has "cursor: 'default'"
|
||||
cursor: "pointer",
|
||||
cursor: "inherit",
|
||||
},
|
||||
indicator: {
|
||||
// ensure the indicator is not shown above the app header
|
||||
zIndex: 0,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -23,7 +27,15 @@ export function UnreadCount(
|
||||
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
|
||||
return (
|
||||
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count} openDelay={Constants.tooltip.delay}>
|
||||
<Indicator disabled={!props.showIndicator} size={4} offset={10} position="middle-start">
|
||||
<Indicator
|
||||
disabled={!props.showIndicator}
|
||||
size={4}
|
||||
offset={10}
|
||||
position="middle-start"
|
||||
classNames={{
|
||||
indicator: classes.indicator,
|
||||
}}
|
||||
>
|
||||
<Badge className={`${classes.badge} cf-badge`} variant="light" fullWidth>
|
||||
{count}
|
||||
</Badge>
|
||||
|
||||
3
commafeed-client/src/globals.d.ts
vendored
Normal file
3
commafeed-client/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// required because of noUncheckedSideEffectImports
|
||||
// https://www.typescriptlang.org/tsconfig/#noUncheckedSideEffectImports
|
||||
declare module "@fontsource/open-sans"
|
||||
@@ -60,7 +60,6 @@ msgstr "إضافة مستخدم"
|
||||
msgid "Admin"
|
||||
msgstr "إداري"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "اسم مستخدم المسؤول"
|
||||
@@ -73,7 +72,6 @@ msgstr "اسم مستخدم المسؤول"
|
||||
msgid "All"
|
||||
msgstr "الكل"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "دائمًا"
|
||||
@@ -173,18 +171,14 @@ msgstr "قم ببناء تعبير تصفية لتحديد ما تريد قرا
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "إلغاء"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "تأكيد"
|
||||
msgid "Confirm password"
|
||||
msgstr "تأكيد كلمة المرور"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "تأكيد كلمة المرور"
|
||||
@@ -294,7 +287,6 @@ msgstr "أيام"
|
||||
msgid "Delete"
|
||||
msgstr "حذف"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "حذف الحساب"
|
||||
@@ -342,13 +334,10 @@ msgstr "اسحب الرابط إلى شريط الإشارات"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "البريد الإلكتروني"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "عنوان البريد الإلكتروني"
|
||||
@@ -399,7 +388,6 @@ msgstr "خيارات الإضافة"
|
||||
msgid "Feed name"
|
||||
msgstr "اسم الخلاصة"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "تحميل الاشتراكات ..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "تحميل العلامات ..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "انتقل إلى اشتراك بإدخال اسمه"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "انتقل إلى الفئة/الخلاصة التالية التي تحتوي على إدخالات غير مقروءة عند تمييز الكل كمقروء"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "أبدا"
|
||||
@@ -657,7 +643,6 @@ msgstr "أبدا"
|
||||
msgid "New password"
|
||||
msgstr "كلمة مرور جديدة"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "كلمة مرور جديدة"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "تصدير OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "ملف OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "الفئة الأصل"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "كلمة المرور"
|
||||
@@ -874,7 +855,6 @@ msgstr "تحديث"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "تم إغلاق التسجيلات في مثيل CommaFeed هذا"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "إعادة تعيين كلمة المرور"
|
||||
@@ -883,7 +863,6 @@ msgstr "إعادة تعيين كلمة المرور"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "نقرة بالزر الأيمن"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "التمرير"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "بحث"
|
||||
@@ -924,7 +901,6 @@ msgstr "تحديد الخلاصة/الفئة التالية غير المقرو
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "تحديد الخلاصة/الفئة السابقة غير المقروءة"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "رابط الخادم"
|
||||
@@ -953,8 +929,6 @@ msgstr "مشاركة"
|
||||
msgid "Sharing sites"
|
||||
msgstr "مشاركة المواقع"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "الحلقة"
|
||||
@@ -1003,7 +977,6 @@ msgstr "إظهار عدد غير المقروء في أيقونة التبويب
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "إظهار عدد غير المقروء في عنوان التبويب"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "قم بالتسجيل"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "شيء سيء حدث للتو ..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "فضاء"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "النجم"
|
||||
msgid "Starred"
|
||||
msgstr "مميز بنجمة"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "قم بالتبديل إلى النسق الفاتح"
|
||||
msgid "System"
|
||||
msgstr "النظام"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "الكلمات"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "غير مقروءة"
|
||||
msgid "Unstar"
|
||||
msgstr "إلغاء النجم"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "إلغاء الاشتراك"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "مفتاح المستخدم"
|
||||
msgid "User name"
|
||||
msgstr "اسم المستخدم"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "اسم المستخدم أو البريد الإلكتروني"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Afegeix usuari"
|
||||
msgid "Admin"
|
||||
msgstr "Administrador"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nom d'usuari de l'administrador"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nom d'usuari de l'administrador"
|
||||
msgid "All"
|
||||
msgstr "Tot"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
@@ -173,18 +171,14 @@ msgstr "Creeu una expressió de filtratge per indicar què voleu llegir. Les ent
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel·la"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Confirma"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmeu la contrasenya"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirma la contrasenya"
|
||||
@@ -294,7 +287,6 @@ msgstr "dies"
|
||||
msgid "Delete"
|
||||
msgstr "Eliminar"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Suprimeix el compte"
|
||||
@@ -342,13 +334,10 @@ msgstr "Arrossegueu l'enllaç a la barra d'adreces d'interès"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Correu electrònic"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Adreça de correu electrònic"
|
||||
@@ -399,7 +388,6 @@ msgstr "Opcions de l'extensió"
|
||||
msgid "Feed name"
|
||||
msgstr "Nom del canal"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "S'estan carregant les subscripcions..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Carregant les etiquetes..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navegueu a una subscripció introduint-ne el nom"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navega a la següent categoria/canal amb entrades no llegides quan es marquen totes les entrades com a llegides"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Mai"
|
||||
@@ -657,7 +643,6 @@ msgstr "Mai"
|
||||
msgid "New password"
|
||||
msgstr "Contrasenya nova"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Contrasenya nova"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Exportació OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Fitxer OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Categoria pare"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Contrasenya"
|
||||
@@ -874,7 +855,6 @@ msgstr "Actualitzar"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Els registres estan tancats en aquesta instància de CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Restableix la contrasenya"
|
||||
@@ -883,7 +863,6 @@ msgstr "Restableix la contrasenya"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic dret"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Desplaçament"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
@@ -924,7 +901,6 @@ msgstr "Selecciona el següent canal/categoria no llegit"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Selecciona el canal/categoria anterior sense llegir"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL del servidor"
|
||||
@@ -953,8 +929,6 @@ msgstr "Comparteix"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Compartir a altres llocs web"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "canvi"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Mostra el recompte de no llegits a la icona de favorits de la pestanya"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostra el recompte de no llegits al títol de la pestanya"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Registra't"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Acaba de passar una cosa dolenta..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espai"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Estrella"
|
||||
msgid "Starred"
|
||||
msgstr "Estrellat"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Canvia al tema clar"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetes"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Sense llegir"
|
||||
msgid "Unstar"
|
||||
msgstr "Desestrellar"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Donar-se de baixa"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Clau d'usuari"
|
||||
msgid "User name"
|
||||
msgstr "Nom d'usuari"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nom d'usuari o correu electrònic"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Přidat uživatele"
|
||||
msgid "Admin"
|
||||
msgstr "Správce"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Uživatelské jméno správce"
|
||||
@@ -73,7 +72,6 @@ msgstr "Uživatelské jméno správce"
|
||||
msgid "All"
|
||||
msgstr "Všechny"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Vždy"
|
||||
@@ -173,18 +171,14 @@ msgstr "Vytvořte výraz filtru pro určení toho, co chcete číst. Položky, k
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Zrušit"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Potvrdit"
|
||||
msgid "Confirm password"
|
||||
msgstr "Potvrďte heslo"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Potvrdit heslo"
|
||||
@@ -294,7 +287,6 @@ msgstr "dní"
|
||||
msgid "Delete"
|
||||
msgstr "Smazat"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Smazat účet"
|
||||
@@ -342,13 +334,10 @@ msgstr "Přetáhněte odkaz na lištu záložek"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mailová adresa"
|
||||
@@ -399,7 +388,6 @@ msgstr "Možnosti rozšíření"
|
||||
msgid "Feed name"
|
||||
msgstr "Název zdroje"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Načítání odběrů..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Načítání značek..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Přejděte na předplatné zadáním jeho názvu"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Po označení všech položek jako přečtených přejít na další kategorii/zdroj s nepřečtenými položkami"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nikdy"
|
||||
@@ -657,7 +643,6 @@ msgstr "Nikdy"
|
||||
msgid "New password"
|
||||
msgstr "Nové heslo"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nové heslo"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Export OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "soubor OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Rodičovská kategorie"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Heslo"
|
||||
@@ -874,7 +855,6 @@ msgstr "Obnovit"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "V této instanci CommaFeed jsou registrace uzavřeny"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Obnovit heslo"
|
||||
@@ -883,7 +863,6 @@ msgstr "Obnovit heslo"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Kliknutí pravým tlačítkem"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Posouvání"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Hledej"
|
||||
@@ -924,7 +901,6 @@ msgstr "Vybrat další nepřečtený zdroj/kategorii"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vybrat předchozí nepřečtený zdroj/kategorii"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL serveru"
|
||||
@@ -953,8 +929,6 @@ msgstr "Sdílejte"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Stránky pro sdílení"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Směna"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Zobrazovat počet nepřečtených v ikoně karty"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Zobrazovat počet nepřečtených v názvu karty"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Zaregistrovat se"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Právě se stalo něco špatného..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Vesmír"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Hvězda"
|
||||
msgid "Starred"
|
||||
msgstr "S hvězdičkou"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Přepnout na světlý motiv"
|
||||
msgid "System"
|
||||
msgstr "Systémový"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Štítky"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Nepřečteno"
|
||||
msgid "Unstar"
|
||||
msgstr "Odstranit hvězdu"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Odhlásit odběr"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Uživatelský klíč"
|
||||
msgid "User name"
|
||||
msgstr "Uživatelské jméno"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Uživatelské jméno nebo e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Ychwanegu defnyddiwr"
|
||||
msgid "Admin"
|
||||
msgstr "Gweinyddol"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Enw defnyddiwr gweinyddwr"
|
||||
@@ -73,7 +72,6 @@ msgstr "Enw defnyddiwr gweinyddwr"
|
||||
msgid "All"
|
||||
msgstr "Pawb"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Bob amser"
|
||||
@@ -173,18 +171,14 @@ msgstr "Adeiladwch fynegiant hidlo i nodi'r hyn rydych chi am ei ddarllen. Bydd
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Diddymu"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Cadarnhau"
|
||||
msgid "Confirm password"
|
||||
msgstr "Cadarnhau'r cyfrinair"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Cadarnhau Cyfrinair"
|
||||
@@ -294,7 +287,6 @@ msgstr "diwrnodau"
|
||||
msgid "Delete"
|
||||
msgstr "Dileu"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Dileu cyfrif"
|
||||
@@ -342,13 +334,10 @@ msgstr "Llusgwch y ddolen i'r bar nod tudalen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-bost"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "cyfeiriad e-bost"
|
||||
@@ -399,7 +388,6 @@ msgstr "Opsiynau estyniad"
|
||||
msgid "Feed name"
|
||||
msgstr "Enw porthiant"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Yn llwytho tanysgrifiadau..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Wrthi'n llwytho tagiau..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Llywiwch i danysgrifiad trwy nodi ei enw"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Llywiwch i'r categori/porthiant nesaf gyda chofnodion heb eu darllen wrth farcio pob cofnod fel un wedi'i ddarllen"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Byth"
|
||||
@@ -657,7 +643,6 @@ msgstr "Byth"
|
||||
msgid "New password"
|
||||
msgstr "Cyfrinair newydd"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Cyfrinair Newydd"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Allforio OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Ffeil OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Categori Rhiant"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "cyfrinair"
|
||||
@@ -874,7 +855,6 @@ msgstr "Adnewyddu"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Mae cofrestriadau ar gau ar yr achos CommaFeed hwn"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Ailosod Cyfrinair"
|
||||
@@ -883,7 +863,6 @@ msgstr "Ailosod Cyfrinair"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic dde"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Sgrolio"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Chwilio"
|
||||
@@ -924,7 +901,6 @@ msgstr "Dewiswch y porthiant/categori nesaf heb ei ddarllen"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Dewiswch y porthiant/categori blaenorol heb ei ddarllen"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL y gweinydd"
|
||||
@@ -953,8 +929,6 @@ msgstr "Rhannu"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Rhannu gwefannau"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "shifft"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Dangos nifer heb ei ddarllen yn ffaficon y tab"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Dangos nifer heb ei ddarllen yn nheitl y tab"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Cofrestrwch"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Mae rhywbeth drwg newydd ddigwydd ..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Gofod"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "seren"
|
||||
msgid "Starred"
|
||||
msgstr "serennog"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Newid i thema golau"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tagiau"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Heb ei ddarllen"
|
||||
msgid "Unstar"
|
||||
msgstr "dad-seren"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Dad-danysgrifio"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Allwedd defnyddiwr"
|
||||
msgid "User name"
|
||||
msgstr "Enw defnyddiwr"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Enw Defnyddiwr neu E-bost"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Tilføj bruger"
|
||||
msgid "Admin"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Administrator brugernavn"
|
||||
@@ -73,7 +72,6 @@ msgstr "Administrator brugernavn"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Altid"
|
||||
@@ -173,18 +171,14 @@ msgstr "Byg et filterudtryk for at angive, hvad du vil læse. Indlæg, der ikke
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Annuller"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Bekræft"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bekræft adgangskode"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Bekræft adgangskode"
|
||||
@@ -294,7 +287,6 @@ msgstr "dage"
|
||||
msgid "Delete"
|
||||
msgstr "Slet"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Slet konto"
|
||||
@@ -342,13 +334,10 @@ msgstr "Træk linket til bogmærkelinjen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mailadresse"
|
||||
@@ -399,7 +388,6 @@ msgstr "Indstillinger for udvidelse"
|
||||
msgid "Feed name"
|
||||
msgstr "Feednavn"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Indlæser abonnementer..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Indlæser tags..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Naviger til et abonnement ved at indtaste dets navn"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Naviger til næste kategori/feed med ulæste indlæg, når alle indlæg markeres som læst"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Aldrig"
|
||||
@@ -657,7 +643,6 @@ msgstr "Aldrig"
|
||||
msgid "New password"
|
||||
msgstr "Ny adgangskode"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Ny adgangskode"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML eksport"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML fil"
|
||||
@@ -791,10 +775,7 @@ msgstr "Forældrekategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Adgangskode"
|
||||
@@ -874,7 +855,6 @@ msgstr "Opdater"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registreringer er lukket på denne CommaFeed-instans"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Nulstil adgangskode"
|
||||
@@ -883,7 +863,6 @@ msgstr "Nulstil adgangskode"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Højreklik"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Rulning"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Søg"
|
||||
@@ -924,7 +901,6 @@ msgstr "Vælg næste ulæste feed/kategori"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vælg forrige ulæste feed/kategori"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "Del"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Delingssider"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Skift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Vis ulæst antal i fanens favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Vis ulæst antal i fanens titel"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Tilmeld dig"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Der er lige sket noget slemt..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Rum"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Stjerne"
|
||||
msgid "Starred"
|
||||
msgstr "Medvirkende"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Skift til lystema"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Ulæst"
|
||||
msgid "Unstar"
|
||||
msgstr "Fjern stjerne"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Afmeld"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Brugernøgle"
|
||||
msgid "User name"
|
||||
msgstr "Brugernavn"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Brugernavn eller e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Benutzer hinzufügen"
|
||||
msgid "Admin"
|
||||
msgstr "Verwaltung"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Administrator-Benutzername"
|
||||
@@ -73,7 +72,6 @@ msgstr "Administrator-Benutzername"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Immer"
|
||||
@@ -173,18 +171,14 @@ msgstr "Erstellen Sie einen Filterausdruck, um anzugeben, was Sie lesen möchten
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Bestätigen"
|
||||
msgid "Confirm password"
|
||||
msgstr "Passwort bestätigen"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Passwort bestätigen"
|
||||
@@ -294,7 +287,6 @@ msgstr "Tage"
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Konto löschen"
|
||||
@@ -342,13 +334,10 @@ msgstr "Link in Lesezeichenleiste ziehen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-Mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
@@ -399,7 +388,6 @@ msgstr "Erweiterungsoptionen"
|
||||
msgid "Feed name"
|
||||
msgstr "Feedname"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Abonnements werden geladen..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Tags werden geladen..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navigieren Sie zu einem Abonnement, indem Sie seinen Namen eingeben"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigieren Sie zum nächsten Feed/Kategorie mit ungelesenen Einträgen, wenn Sie alle Einträge als gelesen markieren"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Niemals"
|
||||
@@ -657,7 +643,6 @@ msgstr "Niemals"
|
||||
msgid "New password"
|
||||
msgstr "Neues Passwort"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Neues Passwort"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-Export"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-Datei"
|
||||
@@ -791,10 +775,7 @@ msgstr "Übergeordnete Kategorie"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
@@ -874,7 +855,6 @@ msgstr "Aktualisieren"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registrierungen sind für diese CommaFeed-Instanz geschlossen"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Passwort zurücksetzen"
|
||||
@@ -883,7 +863,6 @@ msgstr "Passwort zurücksetzen"
|
||||
msgid "REST API"
|
||||
msgstr "REST-API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Rechtsklick"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Scrollen"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Suche"
|
||||
@@ -924,7 +901,6 @@ msgstr "Nächsten ungelesenen Feed/Kategorie auswählen"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vorherigen ungelesenen Feed/Kategorie auswählen"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "Teilen"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Seiten teilen"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Verschiebung"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Anzahl ungelesener Nachrichten im Tab-Favicon anzeigen"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Anzahl ungelesener Nachrichten im Tab-Titel anzeigen"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Melden Sie sich an"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Etwas Schlimmes ist gerade passiert..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Raum"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Stern"
|
||||
msgid "Starred"
|
||||
msgstr "Markiert"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Zum Lightmode wechseln"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Ungelesen"
|
||||
msgid "Unstar"
|
||||
msgstr "Stern entfernen"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Abbestellen"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Benutzerschlüssel"
|
||||
msgid "User name"
|
||||
msgstr "Benutzername"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Benutzername oder E-Mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Add user"
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Admin user name"
|
||||
@@ -73,7 +72,6 @@ msgstr "Admin user name"
|
||||
msgid "All"
|
||||
msgstr "All"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Always"
|
||||
@@ -173,18 +171,14 @@ msgstr "Build a filter expression to indicate what you want to read. Entries tha
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Confirm"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirm password"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirm Password"
|
||||
@@ -294,7 +287,6 @@ msgstr "days"
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Delete account"
|
||||
@@ -342,13 +334,10 @@ msgstr "Drag link to bookmark bar"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mail address"
|
||||
@@ -399,7 +388,6 @@ msgstr "Extension options"
|
||||
msgid "Feed name"
|
||||
msgstr "Feed name"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Loading subscriptions..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Loading tags..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navigate to a subscription by entering its name"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Never"
|
||||
@@ -657,7 +643,6 @@ msgstr "Never"
|
||||
msgid "New password"
|
||||
msgstr "New password"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "New Password"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML export"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML file"
|
||||
@@ -791,10 +775,7 @@ msgstr "Parent Category"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
@@ -874,7 +855,6 @@ msgstr "Refresh"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registrations are closed on this CommaFeed instance"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Reset Password"
|
||||
@@ -883,7 +863,6 @@ msgstr "Reset Password"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Right click"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Scrolling"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Search"
|
||||
@@ -924,7 +901,6 @@ msgstr "Select next unread feed/category"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Select previous unread feed/category"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "Share"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Sharing sites"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Show unread count in tab favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Show unread count in tab title"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Sign up"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Something bad just happened..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Space"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Star"
|
||||
msgid "Starred"
|
||||
msgstr "Starred"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Switch to light theme"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Unread"
|
||||
msgid "Unstar"
|
||||
msgstr "Unstar"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Unsubscribe"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "User key"
|
||||
msgid "User name"
|
||||
msgstr "User name"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "User Name or E-mail"
|
||||
|
||||
@@ -61,7 +61,6 @@ msgstr "Añadir usuario"
|
||||
msgid "Admin"
|
||||
msgstr "Administrador"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nombre de usuario administrador"
|
||||
@@ -74,7 +73,6 @@ msgstr "Nombre de usuario administrador"
|
||||
msgid "All"
|
||||
msgstr "Todo"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Siempre"
|
||||
@@ -174,18 +172,14 @@ msgstr "Crea una expresión de filtrado para indicar lo que quieres leer. Las en
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -241,7 +235,6 @@ msgstr "Confirmar"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmar contraseña"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmar contraseña"
|
||||
@@ -295,7 +288,6 @@ msgstr "días"
|
||||
msgid "Delete"
|
||||
msgstr "Borrar"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Borrar cuenta"
|
||||
@@ -343,13 +335,10 @@ msgstr "Arrastra el enlace a la barra de marcadores"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Correo electrónico"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Dirección de correo electrónico"
|
||||
@@ -400,7 +389,6 @@ msgstr "Opciones de la extensión"
|
||||
msgid "Feed name"
|
||||
msgstr "Nombre del feed"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -571,7 +559,6 @@ msgstr "Cargando suscripciones..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Cargando etiquetas..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -649,7 +636,6 @@ msgstr "Navegar a una suscripción introduciendo su nombre"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navegar a la siguiente categoría/feed con entradas no leídas al marcar todas como leídas"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nunca"
|
||||
@@ -658,7 +644,6 @@ msgstr "Nunca"
|
||||
msgid "New password"
|
||||
msgstr "Nueva contraseña"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nueva contraseña"
|
||||
@@ -765,7 +750,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Exportar OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Archivo OPML"
|
||||
@@ -792,10 +776,7 @@ msgstr "Categoría principal"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
@@ -875,7 +856,6 @@ msgstr "Actualizar"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Los registros están cerrados en esta instancia de CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Restablecer contraseña"
|
||||
@@ -884,7 +864,6 @@ msgstr "Restablecer contraseña"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic derecho"
|
||||
@@ -911,8 +890,6 @@ msgid "Scrolling"
|
||||
msgstr "Desplazarse"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Buscar"
|
||||
@@ -925,7 +902,6 @@ msgstr "Seleccionar el siguiente feed/categoría no leído"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Seleccionar el anterior feed/categoría no leído"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL del servidor"
|
||||
@@ -954,8 +930,6 @@ msgstr "Compartir"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Compartir sitios"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1004,7 +978,6 @@ msgstr "Mostrar recuento de no leídos en la pestaña favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostrar recuento de no leídos en el título de la pestaña"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1014,7 +987,6 @@ msgstr "Registrarse"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Algo malo acaba de pasar..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espacio"
|
||||
@@ -1031,7 +1003,6 @@ msgstr "Estrella"
|
||||
msgid "Starred"
|
||||
msgstr "Destacado"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1065,7 +1036,6 @@ msgstr "Cambiar a tema claro"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetas"
|
||||
@@ -1137,7 +1107,6 @@ msgstr "No leído"
|
||||
msgid "Unstar"
|
||||
msgstr "Desmarcar"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Cancelar suscripción"
|
||||
@@ -1150,7 +1119,6 @@ msgstr "Clave de usuario"
|
||||
msgid "User name"
|
||||
msgstr "Nombre de usuario"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nombre de usuario o correo electrónico"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "افزودن کاربر"
|
||||
msgid "Admin"
|
||||
msgstr "مدیر"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "نام کاربری مدیر"
|
||||
@@ -73,7 +72,6 @@ msgstr "نام کاربری مدیر"
|
||||
msgid "All"
|
||||
msgstr "همه"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "همیشه"
|
||||
@@ -173,18 +171,14 @@ msgstr "یک عبارت فیلتر بسازید تا نشان دهید چه چی
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "لغو"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "تأیید کنید"
|
||||
msgid "Confirm password"
|
||||
msgstr "رمز عبور را تأیید کنید"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "تأیید رمز عبور"
|
||||
@@ -294,7 +287,6 @@ msgstr "روز"
|
||||
msgid "Delete"
|
||||
msgstr "حذف کنید"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "حذف حساب"
|
||||
@@ -342,13 +334,10 @@ msgstr "پیوند را به نوار نشانک بکشید"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "ایمیل"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "آدرس ایمیل"
|
||||
@@ -399,7 +388,6 @@ msgstr "گزینه های افزونه"
|
||||
msgid "Feed name"
|
||||
msgstr "نام فید"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "بارگیری اشتراک ها..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "بارگیری برچسب ها..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "با وارد کردن نام اشتراک، به آن بروید"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "هنگام علامتگذاری همه ورودیها بهعنوان خواندهشده، به دستهبندی/فید بعدی با ورودیهای خواندهنشده بروید"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "هرگز"
|
||||
@@ -657,7 +643,6 @@ msgstr "هرگز"
|
||||
msgid "New password"
|
||||
msgstr "رمز عبور جدید"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "رمز عبور جدید"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "خروجی OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "فایل OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "دسته والد"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "رمز عبور"
|
||||
@@ -874,7 +855,6 @@ msgstr "تازه کردن"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "ثبت نام در این نمونه CommaFeed بسته شده است"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "بازنشانی رمز عبور"
|
||||
@@ -883,7 +863,6 @@ msgstr "بازنشانی رمز عبور"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "راست کلیک"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "پیمایش"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "جستجو"
|
||||
@@ -924,7 +901,6 @@ msgstr "انتخاب فید/دسته بعدی خوانده نشده"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "انتخاب فید/دسته قبلی خوانده نشده"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "آدرس سرور"
|
||||
@@ -953,8 +929,6 @@ msgstr "به اشتراک بگذارید"
|
||||
msgid "Sharing sites"
|
||||
msgstr "اشتراک گذاری سایت ها"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "شیفت"
|
||||
@@ -1003,7 +977,6 @@ msgstr "نمایش تعداد خوانده نشده در فاویکون زبان
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "نمایش تعداد خوانده نشده در عنوان زبانه"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "ثبت نام کنید"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "اتفاق بدی افتاد..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "فضا"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "ستاره"
|
||||
msgid "Starred"
|
||||
msgstr "ستاره دار"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "تغییر به تم روشن"
|
||||
msgid "System"
|
||||
msgstr "سیستم"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "برچسبها"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "خوانده نشده"
|
||||
msgid "Unstar"
|
||||
msgstr "برداشتن ستاره"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "لغو اشتراک"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "کلید کاربر"
|
||||
msgid "User name"
|
||||
msgstr "نام کاربری"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "نام کاربری یا ایمیل"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Lisää käyttäjä"
|
||||
msgid "Admin"
|
||||
msgstr "Järjestelmänvalvoja"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Järjestelmänvalvojan käyttäjänimi"
|
||||
@@ -73,7 +72,6 @@ msgstr "Järjestelmänvalvojan käyttäjänimi"
|
||||
msgid "All"
|
||||
msgstr "Kaikki"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Aina"
|
||||
@@ -173,18 +171,14 @@ msgstr "Rakenna suodatinlauseke määrittääksesi, mitä haluat lukea. Merkinn
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Peruuta"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Vahvista"
|
||||
msgid "Confirm password"
|
||||
msgstr "Vahvista salasana"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Vahvista salasana"
|
||||
@@ -294,7 +287,6 @@ msgstr "päivää"
|
||||
msgid "Delete"
|
||||
msgstr "Poista"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Poista tili"
|
||||
@@ -342,13 +334,10 @@ msgstr "Vedä linkki kirjanmerkkipalkkiin"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Sähköposti"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Sähköpostiosoite"
|
||||
@@ -399,7 +388,6 @@ msgstr "Laajennuksen asetukset"
|
||||
msgid "Feed name"
|
||||
msgstr "Syötteen nimi"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Ladataan tilauksia..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Ladataan tunnisteita..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Siirry tilaukseen kirjoittamalla sen nimi"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Siirry seuraavaan luokkaan/syötteeseen, jossa on lukemattomia merkintöjä, kun kaikki merkinnät merkitään luetuiksi"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Ei koskaan"
|
||||
@@ -657,7 +643,6 @@ msgstr "Ei koskaan"
|
||||
msgid "New password"
|
||||
msgstr "Uusi salasana"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Uusi salasana"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-vienti"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-tiedosto"
|
||||
@@ -791,10 +775,7 @@ msgstr "Pääluokka"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Salasana"
|
||||
@@ -874,7 +855,6 @@ msgstr "Päivitä"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Tämän CommaFeed-esiintymän rekisteröinnit on suljettu"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Nollaa salasana"
|
||||
@@ -883,7 +863,6 @@ msgstr "Nollaa salasana"
|
||||
msgid "REST API"
|
||||
msgstr "REST-API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Oikea napsautus"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Vieritys"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Etsi"
|
||||
@@ -924,7 +901,6 @@ msgstr "Valitse seuraava lukematon syöte/luokka"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Valitse edellinen lukematon syöte/luokka"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Palvelimen URL-osoite"
|
||||
@@ -953,8 +929,6 @@ msgstr "Jaa"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Sivustojen jakaminen"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Vaihto"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Näytä lukemattomien määrä välilehden kuvakkeessa"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Näytä lukemattomien määrä välilehden otsikossa"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Rekisteröidy"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Jotain pahaa tapahtui juuri..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Avaruus"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Tähti"
|
||||
msgid "Starred"
|
||||
msgstr "Tähdellä merkitty"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Vaihda vaaleaan teemaan"
|
||||
msgid "System"
|
||||
msgstr "Järjestelmä"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tunnisteet"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Lukematon"
|
||||
msgid "Unstar"
|
||||
msgstr "Poista tähti"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Peruuta tilaus"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Käyttäjäavain"
|
||||
msgid "User name"
|
||||
msgstr "Käyttäjänimi"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Käyttäjänimi tai sähköpostiosoite"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Ajouter un utilisateur"
|
||||
msgid "Admin"
|
||||
msgstr "Administrateur"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nom de l'administrateur"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nom de l'administrateur"
|
||||
msgid "All"
|
||||
msgstr "Tout"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Toujours"
|
||||
@@ -173,18 +171,14 @@ msgstr "Créez une expression régulière pour filtrer ce que vous voulez lire.
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Confirmer"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmer le mot de passe"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmer le mot de passe"
|
||||
@@ -294,7 +287,6 @@ msgstr "jours"
|
||||
msgid "Delete"
|
||||
msgstr "Effacer"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Effacer le compte"
|
||||
@@ -342,13 +334,10 @@ msgstr "Déplacez le lien vers la barre de favoris"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Adresse e-mail"
|
||||
@@ -399,7 +388,6 @@ msgstr "Options de l'extension"
|
||||
msgid "Feed name"
|
||||
msgstr "Nom du flux"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Chargement des abonnements..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Chargement des marqueurs..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Naviguer vers un abonnement en entrant son nom"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Aller vers la catégorie/le flux comportant des entrées non lues suivant après avoir marqué toutes les entrées lues"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Jamais"
|
||||
@@ -657,7 +643,6 @@ msgstr "Jamais"
|
||||
msgid "New password"
|
||||
msgstr "Nouveau mot de passe"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nouveau mot de passe"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Export du fichier OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Fichier OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Catégorie parente"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
@@ -874,7 +855,6 @@ msgstr "Rafraîchir"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Les inscriptions sont fermées sur cette instance de CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Réinitialiser le mot de passe"
|
||||
@@ -883,7 +863,6 @@ msgstr "Réinitialiser le mot de passe"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic droit"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Défilement"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Rechercher"
|
||||
@@ -924,7 +901,6 @@ msgstr "Sélectionner l'article non lu suivant/la catégorie non lue suivante"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Sélectionner l'article non lu précédent/la catégorie non lue précédente"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL du serveur"
|
||||
@@ -953,8 +929,6 @@ msgstr "Partager"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Sites de partage"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Maj"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Afficher le nombre d'entrées non lues dans la favicône de l'onglet"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Afficher le nombre d'entrées non lues dans le titre de l'onglet"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Créer un compte"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Quelque chose s'est mal passé..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espace"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Ajouter aux favoris"
|
||||
msgid "Starred"
|
||||
msgstr "Favoris"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Activer le mode clair"
|
||||
msgid "System"
|
||||
msgstr "Système"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Marqueurs"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Non lu"
|
||||
msgid "Unstar"
|
||||
msgstr "Retirer des favoris"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Se désabonner"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Clé utilisateur"
|
||||
msgid "User name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nom d'utilisateur ou e-mail"
|
||||
|
||||
@@ -61,7 +61,6 @@ msgstr "Engadir persoa usuaria"
|
||||
msgid "Admin"
|
||||
msgstr "Administración"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Identificador de admin"
|
||||
@@ -74,7 +73,6 @@ msgstr "Identificador de admin"
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
@@ -174,18 +172,14 @@ msgstr "Crea unha expresión de filtrado para indicar o que queres ler. Os artig
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -241,7 +235,6 @@ msgstr "Confirmar"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmar contrasinal"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmar contrasinal"
|
||||
@@ -295,7 +288,6 @@ msgstr "días"
|
||||
msgid "Delete"
|
||||
msgstr "Eliminar"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Eliminar conta"
|
||||
@@ -343,13 +335,10 @@ msgstr "Arrastra a ligazón á barra de marcadores"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Correo electrónico"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Enderezo de correo electrónico"
|
||||
@@ -400,7 +389,6 @@ msgstr "Opcións do complemento"
|
||||
msgid "Feed name"
|
||||
msgstr "Nome da canle"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -571,7 +559,6 @@ msgstr "Cargando subscricións..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Cargando etiquetas..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -649,7 +636,6 @@ msgstr "Vai a unha subscrición ao escribir o seu nome"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Vai á seguinte categoría/canle con artigos sen ler cando marques todos artigos como lidos"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nunca"
|
||||
@@ -658,7 +644,6 @@ msgstr "Nunca"
|
||||
msgid "New password"
|
||||
msgstr "Novo contrasinal"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Novo contrasinal"
|
||||
@@ -765,7 +750,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Exportación OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Ficheiro OPML"
|
||||
@@ -792,10 +776,7 @@ msgstr "Categoría superior"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Contrasinal"
|
||||
@@ -875,7 +856,6 @@ msgstr "Actualizar"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Non se admiten novas contas nesta instancia de CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Restablecer contrasinal"
|
||||
@@ -884,7 +864,6 @@ msgstr "Restablecer contrasinal"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Botón dereito"
|
||||
@@ -911,8 +890,6 @@ msgid "Scrolling"
|
||||
msgstr "Desprazamento"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Busca"
|
||||
@@ -925,7 +902,6 @@ msgstr "Seleccionar a seguinte canle/categoría sen ler"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Seleccionar a canle/categoría anterior sen ler"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL do servidor"
|
||||
@@ -954,8 +930,6 @@ msgstr "Compartir"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Servizos para compartir"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Maiúsculas"
|
||||
@@ -1004,7 +978,6 @@ msgstr "Mostrar a conta de artigos sen ler na icona da pestana"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostar a conta de artigos sen ler no título da pestana"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1014,7 +987,6 @@ msgstr "Crear conta"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Algo malo pasou..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espazo"
|
||||
@@ -1031,7 +1003,6 @@ msgstr "Pór estrela"
|
||||
msgid "Starred"
|
||||
msgstr "Con estrela"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1065,7 +1036,6 @@ msgstr "Cambiar ao decorado claro"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetas"
|
||||
@@ -1137,7 +1107,6 @@ msgstr "Sen ler"
|
||||
msgid "Unstar"
|
||||
msgstr "Retirar estrela"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Cancelar a subscrición"
|
||||
@@ -1150,7 +1119,6 @@ msgstr "Clave de usuario"
|
||||
msgid "User name"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Identificador ou correo electrónico"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Felhasználó hozzáadása"
|
||||
msgid "Admin"
|
||||
msgstr "Adminisztráció"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Adminisztrátor felhasználóneve"
|
||||
@@ -73,7 +72,6 @@ msgstr "Adminisztrátor felhasználóneve"
|
||||
msgid "All"
|
||||
msgstr "Mind"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Mindig"
|
||||
@@ -173,18 +171,14 @@ msgstr "Hozzon létre egy szűrőkifejezést annak megjelölésére, hogy mit sz
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Mégse"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Erősítse meg"
|
||||
msgid "Confirm password"
|
||||
msgstr "Erősítse meg a jelszót"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Jelszó megerősítése"
|
||||
@@ -294,7 +287,6 @@ msgstr "nap"
|
||||
msgid "Delete"
|
||||
msgstr "Törlés"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Fiók törlése"
|
||||
@@ -342,13 +334,10 @@ msgstr "Húzza a hivatkozást a könyvjelzősávra"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mail cím"
|
||||
@@ -399,7 +388,6 @@ msgstr "Bővítmény beállításai"
|
||||
msgid "Feed name"
|
||||
msgstr "Hírcsatorna neve"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Előfizetések betöltése..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Címkék betöltése..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navigáljon egy előfizetéshez a nevének megadásával"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigálás a következő olvasatlan bejegyzéseket tartalmazó kategóriára/hírcsatornára, ha minden bejegyzést olvasottnak jelöl"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Soha"
|
||||
@@ -657,7 +643,6 @@ msgstr "Soha"
|
||||
msgid "New password"
|
||||
msgstr "Új jelszó"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Új jelszó"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML exportálás"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML fájl"
|
||||
@@ -791,10 +775,7 @@ msgstr "Szülő kategória"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Jelszó"
|
||||
@@ -874,7 +855,6 @@ msgstr "Frissítés"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "A regisztrációk le vannak zárva ezen a CommaFeed példányon"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Jelszó visszaállítása"
|
||||
@@ -883,7 +863,6 @@ msgstr "Jelszó visszaállítása"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Jobb klikk"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Görgetés"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Keresés"
|
||||
@@ -924,7 +901,6 @@ msgstr "Következő olvasatlan feed/kategória kiválasztása"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Előző olvasatlan feed/kategória kiválasztása"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Szerver URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "Oszd meg"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Webhelyek megosztása"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Olvasatlan darabszám megjelenítése a tab faviconján"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Olvasatlan darabszám megjelenítése a tab címében"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Regisztráció"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Valami rossz történt..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Szóköz"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Csillag"
|
||||
msgid "Starred"
|
||||
msgstr "Csillaggal megjelölve"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Váltás világos témára"
|
||||
msgid "System"
|
||||
msgstr "Rendszer"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Címkék"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Olvasatlan"
|
||||
msgid "Unstar"
|
||||
msgstr "Csillag eltávolítása"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Leiratkozás"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Felhasználói kulcs"
|
||||
msgid "User name"
|
||||
msgstr "Felhasználónév"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Felhasználói név vagy e-mail cím"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Tambahkan pengguna"
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nama pengguna admin"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nama pengguna admin"
|
||||
msgid "All"
|
||||
msgstr "Semua"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Selalu"
|
||||
@@ -173,18 +171,14 @@ msgstr "Bangun ekspresi filter untuk menunjukkan apa yang ingin Anda baca. Entri
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Batal"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Konfirmasi"
|
||||
msgid "Confirm password"
|
||||
msgstr "Konfirmasi kata sandi"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Konfirmasi Kata Sandi"
|
||||
@@ -294,7 +287,6 @@ msgstr "hari"
|
||||
msgid "Delete"
|
||||
msgstr "Hapus"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Hapus akun"
|
||||
@@ -342,13 +334,10 @@ msgstr "Seret tautan ke bilah bookmark"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Email"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Alamat email"
|
||||
@@ -399,7 +388,6 @@ msgstr "Opsi ekstensi"
|
||||
msgid "Feed name"
|
||||
msgstr "Nama umpan"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Memuat langganan..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Memuat tag..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navigasikan ke langganan dengan memasukkan namanya"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigasikan ke kategori/umpan berikutnya dengan entri yang belum dibaca saat menandai semua entri sebagai telah dibaca"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Tidak pernah"
|
||||
@@ -657,7 +643,6 @@ msgstr "Tidak pernah"
|
||||
msgid "New password"
|
||||
msgstr "Kata sandi baru"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Kata Sandi Baru"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "ekspor OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "file OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Kategori Induk"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Kata Sandi"
|
||||
@@ -874,7 +855,6 @@ msgstr "Segarkan"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Pendaftaran ditutup pada instans CommaFeed ini"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Atur Ulang Kata Sandi"
|
||||
@@ -883,7 +863,6 @@ msgstr "Atur Ulang Kata Sandi"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Klik kanan"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Menggulir"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Pencarian"
|
||||
@@ -924,7 +901,6 @@ msgstr "Pilih umpan/kategori belum dibaca berikutnya"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Pilih umpan/kategori belum dibaca sebelumnya"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL Server"
|
||||
@@ -953,8 +929,6 @@ msgstr "Bagikan"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Berbagi situs"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Pergeseran"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Tampilkan jumlah yang belum dibaca di favicon tab"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Tampilkan jumlah yang belum dibaca di judul tab"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Daftar"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Sesuatu yang buruk baru saja terjadi..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Spasi"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Bintang"
|
||||
msgid "Starred"
|
||||
msgstr "Berbintang"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Beralih ke tema terang"
|
||||
msgid "System"
|
||||
msgstr "Sistem"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tag"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Belum Dibaca"
|
||||
msgid "Unstar"
|
||||
msgstr "Hapus bintang"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Berhenti berlangganan"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Kunci pengguna"
|
||||
msgid "User name"
|
||||
msgstr "Nama pengguna"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nama Pengguna atau Email"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Aggiungi utente"
|
||||
msgid "Admin"
|
||||
msgstr "Ammin"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nome utente amministratore"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nome utente amministratore"
|
||||
msgid "All"
|
||||
msgstr "Tutto"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
@@ -173,18 +171,14 @@ msgstr "Crea un'espressione di filtro per indicare cosa vuoi leggere. Le voci ch
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Annulla"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Conferma"
|
||||
msgid "Confirm password"
|
||||
msgstr "Conferma password"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Conferma password"
|
||||
@@ -294,7 +287,6 @@ msgstr "giorni"
|
||||
msgid "Delete"
|
||||
msgstr "Elimina"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Elimina account"
|
||||
@@ -342,13 +334,10 @@ msgstr "Trascina il collegamento sulla barra dei preferiti"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Indirizzo e-mail"
|
||||
@@ -399,7 +388,6 @@ msgstr "Opzioni estensione"
|
||||
msgid "Feed name"
|
||||
msgstr "Nome del feed"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Caricamento abbonamenti..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Caricamento tag..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navigare verso un abbonamento inserendo il suo nome"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Passa alla categoria/feed successivo con voci non lette quando contrassegni tutte le voci come lette"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Mai"
|
||||
@@ -657,7 +643,6 @@ msgstr "Mai"
|
||||
msgid "New password"
|
||||
msgstr "Nuova password"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nuova password"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Esportazione OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "File OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Categoria padre"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
@@ -874,7 +855,6 @@ msgstr "Aggiorna"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Le registrazioni sono chiuse su questa istanza CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Reimposta password"
|
||||
@@ -883,7 +863,6 @@ msgstr "Reimposta password"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic destro"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Scorrimento"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
@@ -924,7 +901,6 @@ msgstr "Seleziona il feed/categoria non letto successivo"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Seleziona il feed/categoria non letto precedente"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL del server"
|
||||
@@ -953,8 +929,6 @@ msgstr "Condividi"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Condivisione di siti"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Cambio"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Mostra il numero di non letti nella favicon della scheda"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostra il numero di non letti nel titolo della scheda"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Iscriviti"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "È appena successo qualcosa di brutto..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Spazio"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Stella"
|
||||
msgid "Starred"
|
||||
msgstr "Speciali"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Passa al tema chiaro"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tag"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Non letto"
|
||||
msgid "Unstar"
|
||||
msgstr "Elimina le stelle"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Annulla iscrizione"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Chiave utente"
|
||||
msgid "User name"
|
||||
msgstr "Nome utente"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nome utente o e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "ユーザー追加"
|
||||
msgid "Admin"
|
||||
msgstr "管理者"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "管理者ユーザー名"
|
||||
@@ -73,7 +72,6 @@ msgstr "管理者ユーザー名"
|
||||
msgid "All"
|
||||
msgstr "すべて"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "常に"
|
||||
@@ -173,18 +171,14 @@ msgstr "読みたい内容を指定するためのフィルタ式を作成しま
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "確認"
|
||||
msgid "Confirm password"
|
||||
msgstr "パスワード確認"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "パスワードの確認"
|
||||
@@ -294,7 +287,6 @@ msgstr "日"
|
||||
msgid "Delete"
|
||||
msgstr "削除"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "アカウント削除"
|
||||
@@ -342,13 +334,10 @@ msgstr "リンクをブックマークバーにドラッグ"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "メール"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "メールアドレス"
|
||||
@@ -399,7 +388,6 @@ msgstr "拡張機能オプション"
|
||||
msgid "Feed name"
|
||||
msgstr "フィード名"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "サブスクリプションを読み込んでいます..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "タグを読み込んでいます..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "名前を入力してサブスクリプションに移動します"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "すべてのエントリーを既読にするとき、未読エントリーのある次のカテゴリ/フィードに移動する"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "しない"
|
||||
@@ -657,7 +643,6 @@ msgstr "しない"
|
||||
msgid "New password"
|
||||
msgstr "新しいパスワード"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "新しいパスワード"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML エクスポート"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPMLファイル"
|
||||
@@ -791,10 +775,7 @@ msgstr "親カテゴリ"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "パスワード"
|
||||
@@ -874,7 +855,6 @@ msgstr "リフレッシュ"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "このCommaFeedインスタンスの登録は終了しています"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "パスワードをリセット"
|
||||
@@ -883,7 +863,6 @@ msgstr "パスワードをリセット"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "右クリック"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "スクロール"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "検索"
|
||||
@@ -924,7 +901,6 @@ msgstr "次の未読フィード/カテゴリを選択"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "前の未読フィード/カテゴリを選択"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "サーバーURL"
|
||||
@@ -953,8 +929,6 @@ msgstr "シェア"
|
||||
msgid "Sharing sites"
|
||||
msgstr "共有サイト"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "未読数をタブのアイコンに表示する"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "未読数をタブのタイトルに表示する"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "サインアップ"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "何か悪いことが起きました..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Space"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "スター"
|
||||
msgid "Starred"
|
||||
msgstr "スター付き"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "ライトテーマに切り替え"
|
||||
msgid "System"
|
||||
msgstr "システム"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "タグ"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "未読"
|
||||
msgid "Unstar"
|
||||
msgstr "スターを外す"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "退会"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "ユーザーキー"
|
||||
msgid "User name"
|
||||
msgstr "ユーザー名"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "ユーザー名またはメールアドレス"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "사용자 추가"
|
||||
msgid "Admin"
|
||||
msgstr "관리자"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "관리자 사용자 이름"
|
||||
@@ -73,7 +72,6 @@ msgstr "관리자 사용자 이름"
|
||||
msgid "All"
|
||||
msgstr "전체"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "항상"
|
||||
@@ -173,18 +171,14 @@ msgstr "읽고 싶은 항목을 지정하기 위한 필터 표현식을 작성
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "취소"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "확인"
|
||||
msgid "Confirm password"
|
||||
msgstr "비밀번호 확인"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "비밀번호 확인"
|
||||
@@ -294,7 +287,6 @@ msgstr "일"
|
||||
msgid "Delete"
|
||||
msgstr "삭제"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "계정 삭제"
|
||||
@@ -342,13 +334,10 @@ msgstr "링크를 북마크바로 드래그"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "이메일"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "이메일 주소"
|
||||
@@ -399,7 +388,6 @@ msgstr "확장 프로그램 옵션"
|
||||
msgid "Feed name"
|
||||
msgstr "피드 이름"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "구독 로드 중..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "태그 로드 중..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "이름을 입력하여 구독으로 이동"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "모든 항목을 읽음으로 표시할 때 읽지 않은 항목이 있는 다음 카테고리/피드로 이동"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "안 함"
|
||||
@@ -657,7 +643,6 @@ msgstr "안 함"
|
||||
msgid "New password"
|
||||
msgstr "새 비밀번호"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "새 비밀번호"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML 내보내기"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML 파일"
|
||||
@@ -791,10 +775,7 @@ msgstr "부모 카테고리"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "비밀번호"
|
||||
@@ -874,7 +855,6 @@ msgstr "새로 고침"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "이 CommaFeed 인스턴스에 대한 등록이 마감되었습니다."
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "비밀번호 재설정"
|
||||
@@ -883,7 +863,6 @@ msgstr "비밀번호 재설정"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "오른쪽 클릭"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "스크롤"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "검색"
|
||||
@@ -924,7 +901,6 @@ msgstr "다음 읽지 않은 피드/카테고리 선택"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "이전 읽지 않은 피드/카테고리 선택"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "서버 URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "공유"
|
||||
msgid "Sharing sites"
|
||||
msgstr "사이트 공유"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "시프트"
|
||||
@@ -1003,7 +977,6 @@ msgstr "탭 파비콘에 읽지 않은 개수 표시"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "탭 제목에 읽지 않은 개수 표시"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "가입"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "뭔가 안 좋은 일이 일어났어..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "우주"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "스타"
|
||||
msgid "Starred"
|
||||
msgstr "별표"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "밝은 테마로 전환"
|
||||
msgid "System"
|
||||
msgstr "시스템"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "태그"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "읽지 않음"
|
||||
msgid "Unstar"
|
||||
msgstr "별표 제거"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "구독 취소"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "사용자 키"
|
||||
msgid "User name"
|
||||
msgstr "사용자 이름"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "사용자 이름 또는 이메일"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Tambah pengguna"
|
||||
msgid "Admin"
|
||||
msgstr "Pentadbir"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nama pengguna pentadbir"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nama pengguna pentadbir"
|
||||
msgid "All"
|
||||
msgstr "Semua"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sentiasa"
|
||||
@@ -173,18 +171,14 @@ msgstr "Bina ungkapan penapis untuk menunjukkan perkara yang anda mahu baca. Ent
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Batal"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Sahkan"
|
||||
msgid "Confirm password"
|
||||
msgstr "Sahkan kata laluan"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Sahkan Kata Laluan"
|
||||
@@ -294,7 +287,6 @@ msgstr "hari"
|
||||
msgid "Delete"
|
||||
msgstr "Padam"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Padam akaun"
|
||||
@@ -342,13 +334,10 @@ msgstr "Seret pautan ke bar penanda halaman"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mel"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Alamat e-mel"
|
||||
@@ -399,7 +388,6 @@ msgstr "Pilihan ekstensi"
|
||||
msgid "Feed name"
|
||||
msgstr "Nama suapan"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Memuatkan langganan..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Memuatkan tag..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navigasi ke langganan dengan memasukkan namanya"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigasi ke kategori/suapan seterusnya dengan entri yang belum dibaca apabila menandakan semua entri sebagai dibaca"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Tidak pernah"
|
||||
@@ -657,7 +643,6 @@ msgstr "Tidak pernah"
|
||||
msgid "New password"
|
||||
msgstr "Kata laluan baharu"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Kata Laluan Baharu"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML eksport"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Fail OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Kategori Induk"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Kata Laluan"
|
||||
@@ -874,7 +855,6 @@ msgstr "Muat semula"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Pendaftaran ditutup pada contoh CommaFeed ini"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Tetap Semula Kata Laluan"
|
||||
@@ -883,7 +863,6 @@ msgstr "Tetap Semula Kata Laluan"
|
||||
msgid "REST API"
|
||||
msgstr "REHAT API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Klik kanan"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Menatal"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Cari"
|
||||
@@ -924,7 +901,6 @@ msgstr "Pilih suapan/kategori belum dibaca seterusnya"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Pilih suapan/kategori belum dibaca sebelumnya"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL Pelayan"
|
||||
@@ -953,8 +929,6 @@ msgstr "Kongsi"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Berkongsi tapak"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Anjakan"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Tunjukkan kiraan belum dibaca dalam favicon tab"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Tunjukkan kiraan belum dibaca dalam tajuk tab"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Daftar"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Sesuatu yang buruk baru saja berlaku..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Angkasa"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Bintang"
|
||||
msgid "Starred"
|
||||
msgstr "Dibintangi"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Tukar kepada tema cahaya"
|
||||
msgid "System"
|
||||
msgstr "Sistem"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tag"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Belum dibaca"
|
||||
msgid "Unstar"
|
||||
msgstr "Nyahbintang"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Nyahlanggan"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Kunci pengguna"
|
||||
msgid "User name"
|
||||
msgstr "Nama pengguna"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nama Pengguna atau E-mel"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Legg til bruker"
|
||||
msgid "Admin"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Administrator brukernavn"
|
||||
@@ -73,7 +72,6 @@ msgstr "Administrator brukernavn"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Alltid"
|
||||
@@ -173,18 +171,14 @@ msgstr "Bygg et filteruttrykk for å angi hva du vil lese. Oppføringer som ikke
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Bekreft"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bekreft passord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Bekreft passord"
|
||||
@@ -294,7 +287,6 @@ msgstr "dager"
|
||||
msgid "Delete"
|
||||
msgstr "Slett"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Slett konto"
|
||||
@@ -342,13 +334,10 @@ msgstr "Dra lenken til bokmerkelinjen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-post"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-postadresse"
|
||||
@@ -399,7 +388,6 @@ msgstr "Utvidelsesvalg"
|
||||
msgid "Feed name"
|
||||
msgstr "Feednavn"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Laster abonnementer..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Laster tagger..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Naviger til et abonnement ved å skrive inn navnet"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Naviger til neste kategori/feed med uleste oppføringer når du markerer alle oppføringer som lest"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Aldri"
|
||||
@@ -657,7 +643,6 @@ msgstr "Aldri"
|
||||
msgid "New password"
|
||||
msgstr "Nytt passord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nytt passord"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-eksport"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-fil"
|
||||
@@ -791,10 +775,7 @@ msgstr "Overordnet kategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Passord"
|
||||
@@ -874,7 +855,6 @@ msgstr "Oppdater"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registreringer er stengt på denne CommaFeed-forekomsten"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Tilbakestill passord"
|
||||
@@ -883,7 +863,6 @@ msgstr "Tilbakestill passord"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Høyreklikk"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Rulling"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Søk"
|
||||
@@ -924,7 +901,6 @@ msgstr "Velg neste uleste feed/kategori"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Velg forrige uleste feed/kategori"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "Del"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Delingssider"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Skift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Vis antall uleste i fanens favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Vis antall uleste i fanetittelen"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Meld deg på"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Noe ille skjedde akkurat..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Mellomrom"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Stjerne"
|
||||
msgid "Starred"
|
||||
msgstr "Stjerne"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Bytt til lystema"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tagger"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Ulest"
|
||||
msgid "Unstar"
|
||||
msgstr "Fjern stjerne"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Avslutt abonnement"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Brukernøkkel"
|
||||
msgid "User name"
|
||||
msgstr "Brukernavn"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Brukernavn eller e-post"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Gebruiker toevoegen"
|
||||
msgid "Admin"
|
||||
msgstr "Beheerder"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Gebruikersnaam beheerder"
|
||||
@@ -73,7 +72,6 @@ msgstr "Gebruikersnaam beheerder"
|
||||
msgid "All"
|
||||
msgstr "Alles"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Altijd"
|
||||
@@ -173,18 +171,14 @@ msgstr "Maak een filterexpressie om aan te geven wat u wilt lezen. Items die nie
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Annuleren"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Bevestigen"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bevestig wachtwoord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Bevestig wachtwoord"
|
||||
@@ -294,7 +287,6 @@ msgstr "dagen"
|
||||
msgid "Delete"
|
||||
msgstr "Verwijderen"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Account verwijderen"
|
||||
@@ -342,13 +334,10 @@ msgstr "Link naar bladwijzerbalk slepen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mailadres"
|
||||
@@ -399,7 +388,6 @@ msgstr "Extensie opties"
|
||||
msgid "Feed name"
|
||||
msgstr "Feednaam"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Abonnementen laden..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Tags laden..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navigeer naar een abonnement door de naam in te voeren"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigeer naar de volgende categorie/feed met ongelezen items wanneer alle items als gelezen worden gemarkeerd"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nooit"
|
||||
@@ -657,7 +643,6 @@ msgstr "Nooit"
|
||||
msgid "New password"
|
||||
msgstr "Nieuw wachtwoord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nieuw wachtwoord"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-export"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-bestand"
|
||||
@@ -791,10 +775,7 @@ msgstr "Oudercategorie"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Wachtwoord"
|
||||
@@ -874,7 +855,6 @@ msgstr "Vernieuwen"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registraties zijn gesloten op deze CommaFeed-instantie"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Wachtwoord herstellen"
|
||||
@@ -883,7 +863,6 @@ msgstr "Wachtwoord herstellen"
|
||||
msgid "REST API"
|
||||
msgstr "REST-API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Rechtermuisklik"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Scrollen"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Zoeken"
|
||||
@@ -924,7 +901,6 @@ msgstr "Selecteer volgende ongelezen feed/categorie"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Selecteer vorige ongelezen feed/categorie"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "Delen"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Sites delen"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Toon aantal ongelezen in tabblad-favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Toon aantal ongelezen in tabbladtitel"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Aanmelden"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Er is net iets ergs gebeurd..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Ruimte"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Ster"
|
||||
msgid "Starred"
|
||||
msgstr "Met ster"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Schakel over naar licht thema"
|
||||
msgid "System"
|
||||
msgstr "Systeem"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Ongelezen"
|
||||
msgid "Unstar"
|
||||
msgstr "Sterren uit"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Afmelden"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Gebruikerssleutel"
|
||||
msgid "User name"
|
||||
msgstr "Gebruikersnaam"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Gebruikersnaam of e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Legg til brukar"
|
||||
msgid "Admin"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Brukarnamn for administrator"
|
||||
@@ -73,7 +72,6 @@ msgstr "Brukarnamn for administrator"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Alltid"
|
||||
@@ -173,18 +171,14 @@ msgstr "Bygg eit filteruttrykk for å angis kva du vil lesa. Innlegg som ikkje s
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Bekreft"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bekreft passord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Stadfest passord"
|
||||
@@ -294,7 +287,6 @@ msgstr "dagar"
|
||||
msgid "Delete"
|
||||
msgstr "Slett"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Slett konto"
|
||||
@@ -342,13 +334,10 @@ msgstr "Dra lenken til bokmerkelinjen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-post"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-postadresse"
|
||||
@@ -399,7 +388,6 @@ msgstr "Val for utviding"
|
||||
msgid "Feed name"
|
||||
msgstr "Feednavn"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Laster abonnementer..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Laster tagger..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Naviger til et abonnement ved å skrive inn navnet"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Naviger til neste kategori/suap med ulesne innlegg når du markerer alle innlegg som lesne"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Aldri"
|
||||
@@ -657,7 +643,6 @@ msgstr "Aldri"
|
||||
msgid "New password"
|
||||
msgstr "Nytt passord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nytt passord"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-eksport"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-fil"
|
||||
@@ -791,10 +775,7 @@ msgstr "Overordnet kategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Passord"
|
||||
@@ -874,7 +855,6 @@ msgstr "Oppdater"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registreringer er stengt på denne CommaFeed-forekomsten"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Tilbakestill passord"
|
||||
@@ -883,7 +863,6 @@ msgstr "Tilbakestill passord"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Høgreklikk"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Rulling"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Søk"
|
||||
@@ -924,7 +901,6 @@ msgstr "Vel neste ulesne suap/kategori"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vel førre ulesne suap/kategori"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "Del"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Delingssider"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Skift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Vis talet på ulesne i favicon for fana"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Vis talet på ulesne i tittelen på fana"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Meld deg på"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Noe ille skjedde akkurat..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Mellomrom"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Stjerne"
|
||||
msgid "Starred"
|
||||
msgstr "Stjernemerka"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Bytt til lyst tema"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tagger"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Ulesen"
|
||||
msgid "Unstar"
|
||||
msgstr "Fjern stjerne"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Avslutt abonnement"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Brukarnøkkel"
|
||||
msgid "User name"
|
||||
msgstr "Brukarnamn"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Brukarnamn eller e-post"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Dodaj użytkownika"
|
||||
msgid "Admin"
|
||||
msgstr "Administracja"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nazwa użytkownika administratora"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nazwa użytkownika administratora"
|
||||
msgid "All"
|
||||
msgstr "Wszystkie"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Zawsze"
|
||||
@@ -173,18 +171,14 @@ msgstr "Zbuduj wyrażenie filtrujące, aby wskazać, co chcesz czytać. Wpisy, k
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Anuluj"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Potwierdź"
|
||||
msgid "Confirm password"
|
||||
msgstr "Potwierdź hasło"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Potwierdź hasło"
|
||||
@@ -294,7 +287,6 @@ msgstr "dni"
|
||||
msgid "Delete"
|
||||
msgstr "Usuń"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Usuń konto"
|
||||
@@ -342,13 +334,10 @@ msgstr "Przeciągnij link do paska zakładek"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Adres e-mail"
|
||||
@@ -399,7 +388,6 @@ msgstr "Opcje rozszerzenia"
|
||||
msgid "Feed name"
|
||||
msgstr "nazwa kanału"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Ładowanie subskrypcji..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Ładowanie tagów..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Przejdź do subskrypcji, wpisując jej nazwę"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Przejdź do następnej kategorii/kanału z nieprzeczytanymi wpisami po oznaczeniu wszystkich wpisów jako przeczytane"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nigdy"
|
||||
@@ -657,7 +643,6 @@ msgstr "Nigdy"
|
||||
msgid "New password"
|
||||
msgstr "Nowe hasło"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nowe hasło"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Eksport OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Plik OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Kategoria nadrzędna"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Hasło"
|
||||
@@ -874,7 +855,6 @@ msgstr "Odśwież"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Rejestracje są zamknięte w tej instancji CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Zresetuj hasło"
|
||||
@@ -883,7 +863,6 @@ msgstr "Zresetuj hasło"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Prawe kliknięcie"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Przewijanie"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Szukaj"
|
||||
@@ -924,7 +901,6 @@ msgstr "Wybierz następny nieprzeczytany kanał/kategorię"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Wybierz poprzedni nieprzeczytany kanał/kategorię"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL serwera"
|
||||
@@ -953,8 +929,6 @@ msgstr "Udostępnij"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Udostępnianie witryn"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "zmiana"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Pokazuj liczbę nieprzeczytanych na ikonie karty"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Pokazuj liczbę nieprzeczytanych w tytule karty"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Zarejestruj się"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Coś złego właśnie się stało..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Przestrzeń"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Gwiazda"
|
||||
msgid "Starred"
|
||||
msgstr "Oznaczone gwiazdką"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Przełącz na jasny motyw"
|
||||
msgid "System"
|
||||
msgstr "Systemowy"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tagi"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Nieprzeczytane"
|
||||
msgid "Unstar"
|
||||
msgstr "Usuń gwiazdkę"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Anuluj subskrypcję"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Klucz użytkownika"
|
||||
msgid "User name"
|
||||
msgstr "Nazwa użytkownika"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nazwa użytkownika lub e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Adicionar usuário"
|
||||
msgid "Admin"
|
||||
msgstr "Administrador"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nome de usuário do administrador"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nome de usuário do administrador"
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
@@ -173,18 +171,14 @@ msgstr "Crie uma expressão de filtro para indicar o que você deseja ler. As en
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Confirmar"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmar senha"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmar senha"
|
||||
@@ -294,7 +287,6 @@ msgstr "dias"
|
||||
msgid "Delete"
|
||||
msgstr "Excluir"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Excluir conta"
|
||||
@@ -342,13 +334,10 @@ msgstr "Arraste o link para a barra de favoritos"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Endereço de e-mail"
|
||||
@@ -399,7 +388,6 @@ msgstr "Opções da extensão"
|
||||
msgid "Feed name"
|
||||
msgstr "Nome do feed"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Carregando assinaturas..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Carregando tags..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navegue até uma assinatura digitando seu nome"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navegar para a próxima categoria/feed com entradas não lidas quando todas as entrafas forem lidas"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nunca"
|
||||
@@ -657,7 +643,6 @@ msgstr "Nunca"
|
||||
msgid "New password"
|
||||
msgstr "Nova senha"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nova senha"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Exportação OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Arquivo OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Categoria Pai"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
@@ -874,7 +855,6 @@ msgstr "Atualizar"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Os registros estão fechados nesta instância do CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Redefinir senha"
|
||||
@@ -883,7 +863,6 @@ msgstr "Redefinir senha"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clique com o botão direito"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Deslizar"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Pesquisar"
|
||||
@@ -924,7 +901,6 @@ msgstr "Selecionar próximo feed/categoria não lido"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Selecionar feed/categoria não lido anterior"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL do servidor"
|
||||
@@ -953,8 +929,6 @@ msgstr "Compartilhar"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Compartilhando sites"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Mostrar contagem de não lidos no favion da aba"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostrar contagem de não lidos no título da aba"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Inscreva-se"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Algo ruim acabou de acontecer..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espaço"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Estrela"
|
||||
msgid "Starred"
|
||||
msgstr "Com estrela"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Mudar para tema claro"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetas"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Não lido"
|
||||
msgid "Unstar"
|
||||
msgstr "Desestrelar"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Cancelar inscrição"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Chave do usuário"
|
||||
msgid "User name"
|
||||
msgstr "Nome de usuário"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nome de usuário ou e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Добавить пользователя"
|
||||
msgid "Admin"
|
||||
msgstr "Админ"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Имя администратора"
|
||||
@@ -73,7 +72,6 @@ msgstr "Имя администратора"
|
||||
msgid "All"
|
||||
msgstr "Все"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Всегда"
|
||||
@@ -173,18 +171,14 @@ msgstr "Создайте выражение фильтра, чтобы указ
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Подтвердить"
|
||||
msgid "Confirm password"
|
||||
msgstr "Подтвердить пароль"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Подтвердите пароль"
|
||||
@@ -294,7 +287,6 @@ msgstr "дней"
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Удалить учетную запись"
|
||||
@@ -342,13 +334,10 @@ msgstr "Перетащите ссылку на панель закладок"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Электронная почта"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Адрес электронной почты"
|
||||
@@ -399,7 +388,6 @@ msgstr "Параметры расширения"
|
||||
msgid "Feed name"
|
||||
msgstr "Имя фида"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Загрузка подписок..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Загрузка тегов..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Перейдите к подписке, введя ее имя."
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Переходить к следующей категории/фиду с непрочитанными записями при пометке всех записей как прочитанных"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Никогда"
|
||||
@@ -657,7 +643,6 @@ msgstr "Никогда"
|
||||
msgid "New password"
|
||||
msgstr "Новый пароль"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Новый пароль"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Экспорт OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML файл"
|
||||
@@ -791,10 +775,7 @@ msgstr "Родительская категория"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
@@ -874,7 +855,6 @@ msgstr "Обновить"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Регистрация закрыта для этого экземпляра CommaFeed."
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Сброс пароля"
|
||||
@@ -883,7 +863,6 @@ msgstr "Сброс пароля"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Правый клик"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Прокрутка"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
@@ -924,7 +901,6 @@ msgstr "Выбрать следующий непрочитанный фид/ка
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Выбрать предыдущий непрочитанный фид/категорию"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL сервера"
|
||||
@@ -953,8 +929,6 @@ msgstr "Поделиться"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Обмен сайтами"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Показывать количество непрочитанных в
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Показывать количество непрочитанных в заголовке вкладки"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Зарегистрироваться"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Только что случилось что-то плохое..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Пробел"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "В избранное"
|
||||
msgid "Starred"
|
||||
msgstr "Избранное"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Переключиться на светлую тему"
|
||||
msgid "System"
|
||||
msgstr "Системная"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Теги"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Не прочитано"
|
||||
msgid "Unstar"
|
||||
msgstr "Удалить из избранного"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Отписаться"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Ключ пользователя"
|
||||
msgid "User name"
|
||||
msgstr "Имя пользователя"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Имя пользователя или адрес электронной почты"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Pridať užívateľa"
|
||||
msgid "Admin"
|
||||
msgstr "Správca"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Meno správcu"
|
||||
@@ -73,7 +72,6 @@ msgstr "Meno správcu"
|
||||
msgid "All"
|
||||
msgstr "Všetky"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Vždy"
|
||||
@@ -173,18 +171,14 @@ msgstr "Vytvorte výraz filtra na určenie toho, čo chcete čítať. Záznamy,
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Zrušiť"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Potvrdiť"
|
||||
msgid "Confirm password"
|
||||
msgstr "Potvrďte heslo"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Potvrdiť heslo"
|
||||
@@ -294,7 +287,6 @@ msgstr "dni"
|
||||
msgid "Delete"
|
||||
msgstr "Vymazať"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Vymažte účet"
|
||||
@@ -342,13 +334,10 @@ msgstr "Presuňte odkaz na lištu so záložkami"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mailová adresa"
|
||||
@@ -399,7 +388,6 @@ msgstr "Možnosti rozšírenia"
|
||||
msgid "Feed name"
|
||||
msgstr "Názov informačného kanála"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Načítavam odbery..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Načítavam značky..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Prejdite na predplatné zadaním jeho názvu"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Pri označovaní všetkých položiek ako prečítaných prejdite na ďalšiu kategóriu/kanál s neprečítanými položkami"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nikdy"
|
||||
@@ -657,7 +643,6 @@ msgstr "Nikdy"
|
||||
msgid "New password"
|
||||
msgstr "Nové heslo"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nové heslo"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Export OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Súbor OPML"
|
||||
@@ -791,10 +775,7 @@ msgstr "Rodičovská kategória"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Heslo"
|
||||
@@ -874,7 +855,6 @@ msgstr "Obnoviť"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "V tejto inštancii CommaFeed sú registrácie uzavreté"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Obnoviť heslo"
|
||||
@@ -883,7 +863,6 @@ msgstr "Obnoviť heslo"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Kliknutie pravým tlačidlom"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Rolovanie"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Hľadaj"
|
||||
@@ -924,7 +901,6 @@ msgstr "Vybrať ďalší neprečítaný kanál/kategóriu"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vybrať predchádzajúci neprečítaný kanál/kategóriu"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL servera"
|
||||
@@ -953,8 +929,6 @@ msgstr "Zdieľať"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Zdieľanie stránok"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Smena"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Zobraziť počet neprečítaných v ikone karty"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Zobraziť počet neprečítaných v názve karty"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Zaregistrujte sa"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Práve sa stalo niečo zlé..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Medzera"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Hviezda"
|
||||
msgid "Starred"
|
||||
msgstr "S hviezdičkou"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Prepnite na svetlú tému"
|
||||
msgid "System"
|
||||
msgstr "Systém"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Značky"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Neprečítané"
|
||||
msgid "Unstar"
|
||||
msgstr "Odobrať hviezdičku"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Zrušte odber"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Používateľský kľúč"
|
||||
msgid "User name"
|
||||
msgstr "Meno používateľa"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Meno používateľa alebo e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Lägg till användare"
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Administratörsanvändarnamn"
|
||||
@@ -73,7 +72,6 @@ msgstr "Administratörsanvändarnamn"
|
||||
msgid "All"
|
||||
msgstr "Alla"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Alltid"
|
||||
@@ -173,18 +171,14 @@ msgstr "Skapa ett filteruttryck för att ange vad du vill läsa. Poster som inte
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Bekräfta"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bekräfta lösenord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Bekräfta lösenord"
|
||||
@@ -294,7 +287,6 @@ msgstr "dagar"
|
||||
msgid "Delete"
|
||||
msgstr "Ta bort"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Radera konto"
|
||||
@@ -342,13 +334,10 @@ msgstr "Dra länken till bokmärkesfältet"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-post"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-postadress"
|
||||
@@ -399,7 +388,6 @@ msgstr "Tilläggsalternativ"
|
||||
msgid "Feed name"
|
||||
msgstr "Flödesnamn"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Laddar prenumerationer..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Laddar taggar..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Navigera till ett abonnemang genom att ange dess namn"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigera till nästa kategori/flöde med olästa poster när alla poster markeras som lästa"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Aldrig"
|
||||
@@ -657,7 +643,6 @@ msgstr "Aldrig"
|
||||
msgid "New password"
|
||||
msgstr "Nytt lösenord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nytt lösenord"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-export"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-fil"
|
||||
@@ -791,10 +775,7 @@ msgstr "Föräldrakategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Lösenord"
|
||||
@@ -874,7 +855,6 @@ msgstr "Uppdatera"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registreringar är stängda på denna CommaFeed-instans"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Återställ lösenord"
|
||||
@@ -883,7 +863,6 @@ msgstr "Återställ lösenord"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Högerklick"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Rullning"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Sök"
|
||||
@@ -924,7 +901,6 @@ msgstr "Välj nästa olästa flöde/kategori"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Välj föregående olästa flöde/kategori"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +929,6 @@ msgstr "Dela"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Delningssajter"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Skift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Visa antal olästa i flikikonen"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Visa antal olästa i fliktiteln"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Anmäl dig"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Något dåligt hände precis..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Rymden"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Stjärna"
|
||||
msgid "Starred"
|
||||
msgstr "Starmed"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Byt till ljustema"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Taggar"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Oläst"
|
||||
msgid "Unstar"
|
||||
msgstr "Ta bort stjärna"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Avsluta prenumeration"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Användarnyckel"
|
||||
msgid "User name"
|
||||
msgstr "Användarnamn"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Användarnamn eller e-post"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Kullanıcı ekle"
|
||||
msgid "Admin"
|
||||
msgstr "Yönetici"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Yönetici kullanıcı adı"
|
||||
@@ -73,7 +72,6 @@ msgstr "Yönetici kullanıcı adı"
|
||||
msgid "All"
|
||||
msgstr "Tümü"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Her zaman"
|
||||
@@ -173,18 +171,14 @@ msgstr "Ne okumak istediğinizi belirtmek için bir filtre ifadesi oluşturun. E
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "İptal"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "Onayla"
|
||||
msgid "Confirm password"
|
||||
msgstr "Şifreyi onayla"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Şifreyi Onayla"
|
||||
@@ -294,7 +287,6 @@ msgstr "gün"
|
||||
msgid "Delete"
|
||||
msgstr "Sil"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Hesabı sil"
|
||||
@@ -342,13 +334,10 @@ msgstr "Bağlantıyı yer işareti çubuğuna sürükleyin"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-posta"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-posta adresi"
|
||||
@@ -399,7 +388,6 @@ msgstr "Eklenti ayarları"
|
||||
msgid "Feed name"
|
||||
msgstr "Yayın adı"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "Abonelikler yükleniyor..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Etiketler yükleniyor..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "Adını girerek bir aboneliğe gidin"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Tüm girişleri okundu olarak işaretlerken okunmamış girişleri olan bir sonraki kategoriye/beslemeye git"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Asla"
|
||||
@@ -657,7 +643,6 @@ msgstr "Asla"
|
||||
msgid "New password"
|
||||
msgstr "Yeni şifre"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Yeni Şifre"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML dışa aktarma"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML dosyası"
|
||||
@@ -791,10 +775,7 @@ msgstr "Üst Kategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Şifre"
|
||||
@@ -874,7 +855,6 @@ msgstr "Yenile"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Bu CommaFeed örneğinde kayıtlar kapalı"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Şifreyi Sıfırla"
|
||||
@@ -883,7 +863,6 @@ msgstr "Şifreyi Sıfırla"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Sağ tık"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "Kaydırma"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Ara"
|
||||
@@ -924,7 +901,6 @@ msgstr "Sonraki okunmamış beslemeyi/kategoriyi seç"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Önceki okunmamış beslemeyi/kategoriyi seç"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Sunucu URL'si"
|
||||
@@ -953,8 +929,6 @@ msgstr "Paylaş"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Siteleri paylaşma"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Vardiya"
|
||||
@@ -1003,7 +977,6 @@ msgstr "Sekme simgesinde okunmamış sayısını göster"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Sekme başlığında okunmamış sayısını göster"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "Kaydolun"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Az önce kötü bir şey oldu..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Uzay"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "Yıldız"
|
||||
msgid "Starred"
|
||||
msgstr "Yıldızlı"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "Aydınlık temaya geç"
|
||||
msgid "System"
|
||||
msgstr "Sistem"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiketler"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "Okunmamış"
|
||||
msgid "Unstar"
|
||||
msgstr "Yıldızı kaldır"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Aboneliği iptal et"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "Kullanıcı anahtarı"
|
||||
msgid "User name"
|
||||
msgstr "Kullanıcı adı"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Kullanıcı Adı veya E-posta"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "添加用户"
|
||||
msgid "Admin"
|
||||
msgstr "管理员"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "管理员用户名"
|
||||
@@ -73,7 +72,6 @@ msgstr "管理员用户名"
|
||||
msgid "All"
|
||||
msgstr "全部"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "总是"
|
||||
@@ -173,18 +171,14 @@ msgstr "构建过滤表达式以指示您想阅读的内容。不匹配的条目
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -240,7 +234,6 @@ msgstr "确认"
|
||||
msgid "Confirm password"
|
||||
msgstr "确认密码"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "确认密码"
|
||||
@@ -294,7 +287,6 @@ msgstr "天"
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "删除帐号"
|
||||
@@ -342,13 +334,10 @@ msgstr "拖动链接到书签栏"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "电子邮件"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "电子邮件地址"
|
||||
@@ -399,7 +388,6 @@ msgstr "扩展选项"
|
||||
msgid "Feed name"
|
||||
msgstr "信息流名称"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -570,7 +558,6 @@ msgstr "正在加载订阅..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "正在加载标签..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +635,6 @@ msgstr "通过输入订阅名称导航到订阅"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "将所有条目标记为已读时导航到下一个包含未读条目的类别/信息流"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "从不"
|
||||
@@ -657,7 +643,6 @@ msgstr "从不"
|
||||
msgid "New password"
|
||||
msgstr "新密码"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "新密码"
|
||||
@@ -764,7 +749,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML 导出"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML 文件"
|
||||
@@ -791,10 +775,7 @@ msgstr "父类别"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
@@ -874,7 +855,6 @@ msgstr "刷新"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "此 CommaFeed 实例上的注册已关闭"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "重置密码"
|
||||
@@ -883,7 +863,6 @@ msgstr "重置密码"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "右键单击"
|
||||
@@ -910,8 +889,6 @@ msgid "Scrolling"
|
||||
msgstr "滚动"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
@@ -924,7 +901,6 @@ msgstr "选择下一个未读信息流/类别"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "选择上一个未读信息流/类别"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "服务器网址"
|
||||
@@ -953,8 +929,6 @@ msgstr "分享"
|
||||
msgid "Sharing sites"
|
||||
msgstr "共享站点"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +977,6 @@ msgstr "在标签页图标上显示未读数量"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "在标签页标题中显示未读数量"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +986,6 @@ msgstr "注册"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "刚刚发生了不好的事情……"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "空格"
|
||||
@@ -1030,7 +1002,6 @@ msgstr "星标"
|
||||
msgid "Starred"
|
||||
msgstr "已加星标"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1035,6 @@ msgstr "切换到浅色主题"
|
||||
msgid "System"
|
||||
msgstr "跟随系统"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "标签"
|
||||
@@ -1136,7 +1106,6 @@ msgstr "未读"
|
||||
msgid "Unstar"
|
||||
msgstr "取消星标"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "取消订阅"
|
||||
@@ -1149,7 +1118,6 @@ msgstr "用户密钥"
|
||||
msgid "User name"
|
||||
msgstr "用户名"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "用户名或电子邮件"
|
||||
|
||||
@@ -8,9 +8,11 @@ import { Constants } from "@/app/constants"
|
||||
import type { EntrySourceType } from "@/app/entries/slice"
|
||||
import { loadEntries } from "@/app/entries/thunks"
|
||||
import { redirectToCategoryDetails, redirectToFeedDetails, redirectToTagDetails } from "@/app/redirect/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import { flattenCategoryTree } from "@/app/utils"
|
||||
import { useAppDispatch, useAppSelector, useShallowEqualAppSelector } from "@/app/store"
|
||||
import { categoryHasNewEntries, categoryUnreadCount, flattenCategoryTree } from "@/app/utils"
|
||||
import { FeedEntries } from "@/components/content/FeedEntries"
|
||||
import { UnreadCount } from "@/components/sidebar/UnreadCount"
|
||||
import { useMobile } from "@/hooks/useMobile"
|
||||
import { tss } from "@/tss"
|
||||
|
||||
function NoSubscriptionHelp() {
|
||||
@@ -33,6 +35,12 @@ const useStyles = tss.create(() => ({
|
||||
sourceWebsiteLink: {
|
||||
color: "inherit",
|
||||
textDecoration: "none",
|
||||
overflow: "hidden",
|
||||
},
|
||||
titleText: {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -48,6 +56,33 @@ export function FeedEntriesPage(props: Readonly<FeedEntriesPageProps>) {
|
||||
const sourceLabel = useAppSelector(state => state.entries.sourceLabel)
|
||||
const sourceWebsiteUrl = useAppSelector(state => state.entries.sourceWebsiteUrl)
|
||||
const hasMore = useAppSelector(state => state.entries.hasMore)
|
||||
const mobile = useMobile()
|
||||
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
|
||||
const { unreadCount, hasNewEntries } = useShallowEqualAppSelector(state => {
|
||||
const root = state.tree.rootCategory
|
||||
if (!root) return { unreadCount: 0, hasNewEntries: false }
|
||||
|
||||
if (props.sourceType === "category") {
|
||||
const category = id === Constants.categories.all.id ? root : flattenCategoryTree(root).find(c => c.id === id)
|
||||
return {
|
||||
unreadCount: categoryUnreadCount(category),
|
||||
hasNewEntries: categoryHasNewEntries(category),
|
||||
}
|
||||
}
|
||||
|
||||
if (props.sourceType === "feed") {
|
||||
const feed = flattenCategoryTree(root)
|
||||
.flatMap(c => c.feeds)
|
||||
.find(f => f.id === +id)
|
||||
return {
|
||||
unreadCount: feed?.unread ?? 0,
|
||||
hasNewEntries: !!feed?.hasNewEntries,
|
||||
}
|
||||
}
|
||||
|
||||
return { unreadCount: 0, hasNewEntries: false }
|
||||
})
|
||||
const showUnreadCount = mobile || !sidebarVisible
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
let title: React.ReactNode = sourceLabel
|
||||
@@ -89,16 +124,23 @@ export function FeedEntriesPage(props: Readonly<FeedEntriesPageProps>) {
|
||||
return (
|
||||
// add some room at the bottom of the page in order to be able to scroll the current entry at the top of the page when expanding
|
||||
<Box mb={viewport.height * 0.7}>
|
||||
<Group gap="xl" className="cf-entries-title">
|
||||
<Group className="cf-entries-title" wrap="nowrap">
|
||||
{sourceWebsiteUrl && (
|
||||
<a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}>
|
||||
<Title order={3}>{title}</Title>
|
||||
<Title order={3} className={classes.titleText}>
|
||||
{title}
|
||||
</Title>
|
||||
</a>
|
||||
)}
|
||||
{!sourceWebsiteUrl && <Title order={3}>{title}</Title>}
|
||||
{!sourceWebsiteUrl && (
|
||||
<Title order={3} className={classes.titleText}>
|
||||
{title}
|
||||
</Title>
|
||||
)}
|
||||
<ActionIcon onClick={titleClicked} variant="subtle" color={theme.primaryColor}>
|
||||
<TbEdit size={18} />
|
||||
</ActionIcon>
|
||||
{showUnreadCount && <UnreadCount unreadCount={unreadCount} showIndicator={hasNewEntries} />}
|
||||
</Group>
|
||||
|
||||
<FeedEntries />
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { msg } from "@lingui/core/macro"
|
||||
import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core"
|
||||
import { type ReactNode, type RefObject, Suspense, useEffect, useRef } from "react"
|
||||
import { type ReactNode, type RefObject, Suspense, useEffect, useRef, useState } from "react"
|
||||
import Draggable from "react-draggable"
|
||||
import { TbMenu2, TbPlus, TbX } from "react-icons/tb"
|
||||
import { Outlet } from "react-router-dom"
|
||||
import { useSwipeable } from "react-swipeable"
|
||||
import Tinycon from "tinycon"
|
||||
import { Constants } from "@/app/constants"
|
||||
import { redirectToAdd, redirectToRootCategory } from "@/app/redirect/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
@@ -12,8 +13,10 @@ import { setMobileMenuOpen } from "@/app/tree/slice"
|
||||
import { reloadTree } from "@/app/tree/thunks"
|
||||
import { setSidebarWidth } from "@/app/user/slice"
|
||||
import { reloadProfile, reloadSettings, reloadTags } from "@/app/user/thunks"
|
||||
import { categoryUnreadCount } from "@/app/utils"
|
||||
import { ActionButton } from "@/components/ActionButton"
|
||||
import { AnnouncementDialog } from "@/components/AnnouncementDialog"
|
||||
import { DisablePullToRefresh } from "@/components/DisablePullToRefresh"
|
||||
import { Loader } from "@/components/Loader"
|
||||
import { Logo } from "@/components/Logo"
|
||||
import { MarkAllAsReadConfirmationDialog } from "@/components/MarkAllAsReadConfirmationDialog"
|
||||
@@ -51,6 +54,78 @@ function LogoAndTitle() {
|
||||
)
|
||||
}
|
||||
|
||||
function UnreadCountTitleHandler({
|
||||
enabled,
|
||||
}: Readonly<{
|
||||
enabled?: boolean
|
||||
}>) {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
return <title>{enabled && unreadCount > 0 ? `(${unreadCount}) CommaFeed` : "CommaFeed"}</title>
|
||||
}
|
||||
|
||||
function UnreadCountFaviconHandler({ enabled }: { enabled?: boolean }) {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
useEffect(() => {
|
||||
if (enabled && unreadCount > 0) {
|
||||
Tinycon.setBubble(unreadCount)
|
||||
} else {
|
||||
Tinycon.reset()
|
||||
}
|
||||
}, [unreadCount, enabled])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function BrowserExtensionBadgeUnreadCountHandler() {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const { setBadgeUnreadCount } = useBrowserExtension()
|
||||
useEffect(() => {
|
||||
if (!root) return
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
setBadgeUnreadCount(unreadCount)
|
||||
}, [root, setBadgeUnreadCount])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CustomJsHandler() {
|
||||
const [scriptLoaded, setScriptLoaded] = useState(false)
|
||||
const { loading } = useAppLoading()
|
||||
|
||||
useEffect(() => {
|
||||
if (scriptLoaded || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement("script")
|
||||
script.src = "custom_js.js"
|
||||
script.async = true
|
||||
document.body.appendChild(script)
|
||||
|
||||
setScriptLoaded(true)
|
||||
|
||||
return () => script.remove()
|
||||
}, [scriptLoaded, loading])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CustomCssHandler() {
|
||||
useEffect(() => {
|
||||
const link = document.createElement("link")
|
||||
link.rel = "stylesheet"
|
||||
link.type = "text/css"
|
||||
link.href = "custom_css.css"
|
||||
document.head.appendChild(link)
|
||||
|
||||
return () => link.remove()
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const useStyles = tss
|
||||
.withParams<{
|
||||
sidebarWidth: number
|
||||
@@ -75,6 +150,9 @@ export default function Layout(props: Readonly<LayoutProps>) {
|
||||
const draggableSeparator = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { loading } = useAppLoading()
|
||||
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
||||
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
||||
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
|
||||
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
|
||||
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
|
||||
const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval)
|
||||
@@ -173,61 +251,69 @@ export default function Layout(props: Readonly<LayoutProps>) {
|
||||
|
||||
if (loading) return <LoadingPage />
|
||||
return (
|
||||
<Box {...swipeHandlers}>
|
||||
<AppShell
|
||||
header={{ height: Constants.layout.headerHeight, collapsed: headerInFooter }}
|
||||
footer={{ height: Constants.layout.headerHeight, collapsed: !headerInFooter }}
|
||||
navbar={{
|
||||
width: sidebarWidth,
|
||||
breakpoint: Constants.layout.mobileBreakpoint,
|
||||
collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible },
|
||||
}}
|
||||
padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }}
|
||||
>
|
||||
<AppShell.Header>{!headerInFooter && header}</AppShell.Header>
|
||||
<AppShell.Footer>{headerInFooter && header}</AppShell.Footer>
|
||||
<AppShell.Navbar p={sidebarPadding}>
|
||||
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm">
|
||||
<Box className={classes.sidebarContent}>{props.sidebar}</Box>
|
||||
</AppShell.Section>
|
||||
</AppShell.Navbar>
|
||||
<OnDesktop>
|
||||
<Draggable
|
||||
nodeRef={draggableSeparator as RefObject<HTMLElement>}
|
||||
axis="x"
|
||||
defaultPosition={{
|
||||
x: sidebarWidth,
|
||||
y: 0,
|
||||
}}
|
||||
bounds={{
|
||||
left: 120,
|
||||
right: 1000,
|
||||
}}
|
||||
grid={[30, 30]}
|
||||
onDrag={(_e, data) => {
|
||||
dispatch(setSidebarWidth(data.x))
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
ref={draggableSeparator}
|
||||
style={{
|
||||
position: "fixed",
|
||||
height: "100%",
|
||||
width: "10px",
|
||||
cursor: "ew-resize",
|
||||
<>
|
||||
<UnreadCountTitleHandler enabled={unreadCountTitle} />
|
||||
<UnreadCountFaviconHandler enabled={unreadCountFavicon} />
|
||||
<BrowserExtensionBadgeUnreadCountHandler />
|
||||
<CustomJsHandler />
|
||||
<CustomCssHandler />
|
||||
<DisablePullToRefresh enabled={disablePullToRefresh} />
|
||||
<Box {...swipeHandlers}>
|
||||
<AppShell
|
||||
header={{ height: Constants.layout.headerHeight, collapsed: headerInFooter }}
|
||||
footer={{ height: Constants.layout.headerHeight, collapsed: !headerInFooter }}
|
||||
navbar={{
|
||||
width: sidebarWidth,
|
||||
breakpoint: Constants.layout.mobileBreakpoint,
|
||||
collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible },
|
||||
}}
|
||||
padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }}
|
||||
>
|
||||
<AppShell.Header>{!headerInFooter && header}</AppShell.Header>
|
||||
<AppShell.Footer>{headerInFooter && header}</AppShell.Footer>
|
||||
<AppShell.Navbar p={sidebarPadding}>
|
||||
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm">
|
||||
<Box className={classes.sidebarContent}>{props.sidebar}</Box>
|
||||
</AppShell.Section>
|
||||
</AppShell.Navbar>
|
||||
<OnDesktop>
|
||||
<Draggable
|
||||
nodeRef={draggableSeparator as RefObject<HTMLElement>}
|
||||
axis="x"
|
||||
defaultPosition={{
|
||||
x: sidebarWidth,
|
||||
y: 0,
|
||||
}}
|
||||
/>
|
||||
</Draggable>
|
||||
</OnDesktop>
|
||||
bounds={{
|
||||
left: 120,
|
||||
right: 1000,
|
||||
}}
|
||||
grid={[30, 30]}
|
||||
onDrag={(_e, data) => {
|
||||
dispatch(setSidebarWidth(data.x))
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
ref={draggableSeparator}
|
||||
style={{
|
||||
position: "fixed",
|
||||
height: "100%",
|
||||
width: "10px",
|
||||
cursor: "ew-resize",
|
||||
}}
|
||||
/>
|
||||
</Draggable>
|
||||
</OnDesktop>
|
||||
|
||||
<AppShell.Main>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<AnnouncementDialog />
|
||||
<MarkAllAsReadConfirmationDialog />
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
</Box>
|
||||
<AppShell.Main>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<AnnouncementDialog />
|
||||
<MarkAllAsReadConfirmationDialog />
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
import { lingui } from "@lingui/vite-plugin"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import babel from "@rolldown/plugin-babel"
|
||||
import react, { reactCompilerPreset } from "@vitejs/plugin-react"
|
||||
import { defineConfig } from "vite"
|
||||
import checker from "vite-plugin-checker"
|
||||
import tsconfigPaths from "vite-tsconfig-paths"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(() => ({
|
||||
plugins: [
|
||||
react({
|
||||
babel: {
|
||||
plugins: [
|
||||
// support for lingui macros
|
||||
// needs to be before the react compiler plugin
|
||||
"@lingui/babel-plugin-lingui-macro",
|
||||
// react compiler
|
||||
["babel-plugin-react-compiler", { target: "19" }],
|
||||
],
|
||||
},
|
||||
react(),
|
||||
babel({
|
||||
presets: [reactCompilerPreset()],
|
||||
plugins: ["@lingui/babel-plugin-lingui-macro"],
|
||||
}),
|
||||
lingui(),
|
||||
tsconfigPaths(),
|
||||
checker({
|
||||
typescript: true,
|
||||
biome: {
|
||||
@@ -43,22 +35,32 @@ export default defineConfig(() => ({
|
||||
"/logout": "http://localhost:8083",
|
||||
},
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 3500,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: id => {
|
||||
// output mantine as its own chunk because it is quite large
|
||||
if (id.includes("@mantine")) {
|
||||
return "mantine"
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
legacy: {
|
||||
// required for websocket-heartbeat-js
|
||||
inconsistentCjsInterop: true,
|
||||
},
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
setupFiles: "./src/setupTests.ts",
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 4000,
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
codeSplitting: {
|
||||
groups: [
|
||||
// output mantine as its own chunk because it is quite large
|
||||
{
|
||||
name: "mantine",
|
||||
test: "@mantine",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="23">
|
||||
<profile kind="CodeFormatterProfile" name="CommaFeed" version="23">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="140"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<module name="Checker">
|
||||
<property name="charset" value="UTF-8" />
|
||||
<property name="fileExtensions" value="java" />
|
||||
@@ -40,11 +40,6 @@
|
||||
<module name="AvoidStarImport" />
|
||||
<module name="AvoidStaticImport" />
|
||||
<module name="IllegalImport" />
|
||||
<module name="ImportOrder">
|
||||
<property name="groups" value="/^java\./,javax,jakarta,org,com" />
|
||||
<property name="ordered" value="true" />
|
||||
<property name="separated" value="true" />
|
||||
</module>
|
||||
<module name="RedundantImport" />
|
||||
<module name="UnusedImports" />
|
||||
|
||||
@@ -60,7 +55,7 @@
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="ignore|ignored" />
|
||||
<message key="catch.block.empty"
|
||||
value="Empty catch block. You can use the name 'ignore' or 'ignored' for the exception variable if you really want an empty catch block, but you should strongly consider at the very least logging something." />
|
||||
value="Empty catch block. You can use the name 'ignore' or 'ignored' for the exception variable if you really want an empty catch block, but you should strongly consider at the very least logging something." />
|
||||
</module>
|
||||
<module name="LeftCurly" />
|
||||
<module name="NeedBraces" />
|
||||
@@ -76,7 +71,7 @@
|
||||
<module name="FallThrough" />
|
||||
<module name="IllegalInstantiation">
|
||||
<property name="classes"
|
||||
value="java.lang.Boolean, java.lang.Byte, java.lang.Character, java.lang.Double, java.lang.Float, java.lang.Integer, java.lang.Long, java.lang.Short" />
|
||||
value="java.lang.Boolean, java.lang.Byte, java.lang.Character, java.lang.Double, java.lang.Float, java.lang.Integer, java.lang.Long, java.lang.Short" />
|
||||
</module>
|
||||
<module name="IllegalType" />
|
||||
<module name="ModifiedControlVariable">
|
||||
@@ -113,12 +108,6 @@
|
||||
<module name="NoLineWrap" />
|
||||
<module name="NoWhitespaceBefore" />
|
||||
<module name="ParenPad" />
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="format" value="^\t* +\t*\S" />
|
||||
<property name="message" value="Line has leading space characters; indentation should be performed with tabs only." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
<module name="WhitespaceAround" />
|
||||
|
||||
</module>
|
||||
</module>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#Organize Import Order
|
||||
#Wed Jan 29 15:15:04 CET 2025
|
||||
0=java
|
||||
1=javax
|
||||
2=jakarta
|
||||
3=org
|
||||
4=com
|
||||
@@ -1,20 +1,20 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>7.0.0</version>
|
||||
<version>7.2.0</version>
|
||||
</parent>
|
||||
<artifactId>commafeed-server</artifactId>
|
||||
<name>CommaFeed Server</name>
|
||||
<packaging>quarkus</packaging>
|
||||
|
||||
<properties>
|
||||
<quarkus.version>3.31.4</quarkus.version>
|
||||
<querydsl.version>7.1</querydsl.version>
|
||||
<quarkus.version>3.37.2</quarkus.version>
|
||||
<querydsl.version>7.4.0</querydsl.version>
|
||||
<rome.version>2.1.0</rome.version>
|
||||
|
||||
<build.database>h2</build.database>
|
||||
@@ -29,6 +29,12 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<!-- the quarkus bom declares a dependency on an old version of protobuf, we need to override it for cel-java -->
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>4.35.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -43,7 +49,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-help-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<version>3.5.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>initialize</phase>
|
||||
@@ -156,7 +162,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.5</version>
|
||||
<version>3.5.6</version>
|
||||
<configuration>
|
||||
<argLine>@{argLine}</argLine>
|
||||
<systemPropertyVariables>
|
||||
@@ -168,7 +174,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.5.5</version>
|
||||
<version>3.5.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -213,7 +219,7 @@
|
||||
<plugin>
|
||||
<groupId>io.github.git-commit-id</groupId>
|
||||
<artifactId>git-commit-id-maven-plugin</artifactId>
|
||||
<version>9.0.2</version>
|
||||
<version>10.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -237,7 +243,7 @@
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>13.2.0</version>
|
||||
<version>13.7.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
@@ -266,7 +272,7 @@
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<version>3.8.0</version>
|
||||
<?m2e ignore?>
|
||||
<executions>
|
||||
<execution>
|
||||
@@ -279,12 +285,9 @@
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
<java>
|
||||
<eclipse>
|
||||
<file>${project.basedir}/dev/EclipseCodeFormatter.xml</file>
|
||||
</eclipse>
|
||||
<importOrder>
|
||||
<file>${project.basedir}/dev/eclipse.importorder</file>
|
||||
</importOrder>
|
||||
<googleJavaFormat>
|
||||
<style>AOSP</style>
|
||||
</googleJavaFormat>
|
||||
</java>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -295,15 +298,14 @@
|
||||
<dependency>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed-client</artifactId>
|
||||
<version>7.0.0</version>
|
||||
<version>7.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- compile-time processors -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.42</version>
|
||||
<scope>provided</scope>
|
||||
<version>1.18.46</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.kohsuke.metainf-services</groupId>
|
||||
@@ -362,7 +364,7 @@
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-json</artifactId>
|
||||
<version>4.2.38</version>
|
||||
<version>4.2.39</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign.querydsl</groupId>
|
||||
@@ -393,12 +395,12 @@
|
||||
<dependency>
|
||||
<groupId>dev.cel</groupId>
|
||||
<artifactId>cel</artifactId>
|
||||
<version>0.11.1</version>
|
||||
<version>0.13.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.passay</groupId>
|
||||
<artifactId>passay</artifactId>
|
||||
<version>1.6.6</version>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
@@ -423,12 +425,12 @@
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.22.1</version>
|
||||
<version>1.22.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>78.2</version>
|
||||
<version>78.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.cssparser</groupId>
|
||||
@@ -443,18 +445,23 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
<version>5.6</version>
|
||||
<version>5.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.hakky54</groupId>
|
||||
<artifactId>ayza-for-apache5</artifactId>
|
||||
<version>10.0.3</version>
|
||||
<version>10.0.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.brotli</groupId>
|
||||
<artifactId>dec</artifactId>
|
||||
<version>0.1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.seancfoley</groupId>
|
||||
<artifactId>ipaddress</artifactId>
|
||||
<version>5.6.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
@@ -465,13 +472,13 @@
|
||||
<dependency>
|
||||
<groupId>io.quarkiverse.playwright</groupId>
|
||||
<artifactId>quarkus-playwright</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.7</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mock-server</groupId>
|
||||
<artifactId>mockserver-junit-jupiter</artifactId>
|
||||
<version>5.15.0</version>
|
||||
<version>7.4.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ibm-semeru-runtimes:open-jdk-25.0.2_10-jre@sha256:b02e4cd184d9ece59b01129af1d0a069fa01e4f0f798f0bc4f3ff1a8391ba694
|
||||
FROM ibm-semeru-runtimes:open-jdk-25.0.3.0-jre@sha256:43ec947fee9c4c2ed880620ae9e78911ac5eef66391e5a2c43f956dcc9000491
|
||||
EXPOSE 8082
|
||||
|
||||
RUN mkdir -p /commafeed/data
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:13.3@sha256:2c91e484d93f0830a7e05a2b9d92a7b102be7cab562198b984a84fdbc7806d91
|
||||
FROM debian:13.5@sha256:d07d1b51c39f51188e60be9b64e6bf769fa94e187f092bc32b91305cfa34ba5a
|
||||
ARG TARGETARCH
|
||||
|
||||
EXPOSE 8082
|
||||
|
||||
@@ -1,34 +1,43 @@
|
||||
package com.commafeed;
|
||||
|
||||
import jakarta.enterprise.event.Observes;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import com.commafeed.backend.feed.FeedRefreshEngine;
|
||||
import com.commafeed.backend.feed.ImageProxyUrl;
|
||||
import com.commafeed.backend.task.TaskScheduler;
|
||||
import com.commafeed.security.password.PasswordConstraintValidator;
|
||||
|
||||
import io.quarkus.runtime.ShutdownEvent;
|
||||
import io.quarkus.runtime.StartupEvent;
|
||||
import jakarta.enterprise.event.Observes;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
@RequiredArgsConstructor
|
||||
public class CommaFeedApplication {
|
||||
|
||||
private final FeedRefreshEngine feedRefreshEngine;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final FeedRefreshEngine feedRefreshEngine;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
public void start(@Observes StartupEvent ev) {
|
||||
PasswordConstraintValidator.setMinimumPasswordLength(config.users().minimumPasswordLength());
|
||||
public void start(@Observes StartupEvent ev) {
|
||||
log.info("starting up...");
|
||||
|
||||
feedRefreshEngine.start();
|
||||
taskScheduler.start();
|
||||
}
|
||||
PasswordConstraintValidator.setMinimumPasswordLength(
|
||||
config.users().minimumPasswordLength());
|
||||
|
||||
public void stop(@Observes ShutdownEvent ev) {
|
||||
feedRefreshEngine.stop();
|
||||
taskScheduler.stop();
|
||||
}
|
||||
if (config.imageProxyEnabled()) {
|
||||
ImageProxyUrl.generateKey();
|
||||
}
|
||||
|
||||
feedRefreshEngine.start();
|
||||
taskScheduler.start();
|
||||
}
|
||||
|
||||
public void stop(@Observes ShutdownEvent ev) {
|
||||
log.info("shutting down...");
|
||||
|
||||
feedRefreshEngine.stop();
|
||||
taskScheduler.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,400 +1,360 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
|
||||
import com.commafeed.backend.feed.FeedRefreshIntervalCalculator;
|
||||
|
||||
import io.quarkus.runtime.annotations.ConfigDocSection;
|
||||
import io.quarkus.runtime.annotations.ConfigPhase;
|
||||
import io.quarkus.runtime.annotations.ConfigRoot;
|
||||
import io.quarkus.runtime.configuration.MemorySize;
|
||||
import io.smallrye.config.ConfigMapping;
|
||||
import io.smallrye.config.WithDefault;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* CommaFeed configuration
|
||||
*
|
||||
* Default values are for production, they can be overridden in application.properties for other profiles
|
||||
* <p>Default values are for production, they can be overridden in application.properties for other
|
||||
* profiles
|
||||
*/
|
||||
@ConfigMapping(prefix = "commafeed")
|
||||
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
|
||||
public interface CommaFeedConfiguration {
|
||||
/**
|
||||
* Whether to expose a robots.txt file that disallows web crawlers and search engine indexers.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean hideFromWebCrawlers();
|
||||
/**
|
||||
* Whether to expose a robots.txt file that disallows web crawlers and search engine indexers.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean hideFromWebCrawlers();
|
||||
|
||||
/**
|
||||
* If enabled, images in feed entries will be proxied through the server instead of accessed directly by the browser.
|
||||
*
|
||||
* This is useful if commafeed is accessed through a restricting proxy that blocks some feeds that are followed.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean imageProxyEnabled();
|
||||
/**
|
||||
* If enabled, images in feed entries will be proxied through the server instead of accessed
|
||||
* directly by the browser.
|
||||
*
|
||||
* <p>This is useful if commafeed is accessed through a restricting proxy that blocks some feeds
|
||||
* that are followed.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean imageProxyEnabled();
|
||||
|
||||
/**
|
||||
* Enable password recovery via email.
|
||||
*
|
||||
* Quarkus mailer will need to be configured.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean passwordRecoveryEnabled();
|
||||
/**
|
||||
* Enable password recovery via email.
|
||||
*
|
||||
* <p>Quarkus mailer will need to be configured.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean passwordRecoveryEnabled();
|
||||
|
||||
/**
|
||||
* Message displayed in a notification at the bottom of the page.
|
||||
*/
|
||||
Optional<String> announcement();
|
||||
/** Message displayed in a notification at the bottom of the page. */
|
||||
Optional<String> announcement();
|
||||
|
||||
/**
|
||||
* Google Auth key for fetching Youtube channel favicons.
|
||||
*/
|
||||
Optional<String> googleAuthKey();
|
||||
/** Google Auth key for fetching Youtube channel favicons. */
|
||||
Optional<String> googleAuthKey();
|
||||
|
||||
/**
|
||||
* HTTP client configuration
|
||||
*/
|
||||
@ConfigDocSection
|
||||
HttpClient httpClient();
|
||||
/** HTTP client configuration */
|
||||
@ConfigDocSection
|
||||
HttpClient httpClient();
|
||||
|
||||
/**
|
||||
* Feed refresh engine settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
FeedRefresh feedRefresh();
|
||||
/** Feed refresh engine settings. */
|
||||
@ConfigDocSection
|
||||
FeedRefresh feedRefresh();
|
||||
|
||||
/**
|
||||
* Push notification settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
PushNotifications pushNotifications();
|
||||
/** Push notification settings. */
|
||||
@ConfigDocSection
|
||||
PushNotifications pushNotifications();
|
||||
|
||||
/**
|
||||
* Database settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
Database database();
|
||||
/** Database settings. */
|
||||
@ConfigDocSection
|
||||
Database database();
|
||||
|
||||
/**
|
||||
* Users settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
Users users();
|
||||
/** Users settings. */
|
||||
@ConfigDocSection
|
||||
Users users();
|
||||
|
||||
/**
|
||||
* Websocket settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
Websocket websocket();
|
||||
/** Websocket settings. */
|
||||
@ConfigDocSection
|
||||
Websocket websocket();
|
||||
|
||||
interface HttpClient {
|
||||
/**
|
||||
* User-Agent string that will be used by the http client, leave empty for the default one.
|
||||
*/
|
||||
Optional<String> userAgent();
|
||||
/**
|
||||
* Duration to wait for the feed refresh engine and the task scheduler to stop when the
|
||||
* application is shutting down.
|
||||
*/
|
||||
@WithDefault("2s")
|
||||
Duration shutdownTimeout();
|
||||
|
||||
/**
|
||||
* Time to wait for a connection to be established.
|
||||
*/
|
||||
@WithDefault("5s")
|
||||
Duration connectTimeout();
|
||||
interface HttpClient {
|
||||
/**
|
||||
* User-Agent string that will be used by the http client, leave empty for the default one.
|
||||
*/
|
||||
Optional<String> userAgent();
|
||||
|
||||
/**
|
||||
* Time to wait for SSL handshake to complete.
|
||||
*/
|
||||
@WithDefault("5s")
|
||||
Duration sslHandshakeTimeout();
|
||||
/** Time to wait for a connection to be established. */
|
||||
@WithDefault("5s")
|
||||
Duration connectTimeout();
|
||||
|
||||
/**
|
||||
* Time to wait between two packets before timeout.
|
||||
*/
|
||||
@WithDefault("10s")
|
||||
Duration socketTimeout();
|
||||
/** Time to wait for SSL handshake to complete. */
|
||||
@WithDefault("5s")
|
||||
Duration sslHandshakeTimeout();
|
||||
|
||||
/**
|
||||
* Time to wait for the full response to be received.
|
||||
*/
|
||||
@WithDefault("10s")
|
||||
Duration responseTimeout();
|
||||
/** Time to wait between two packets before timeout. */
|
||||
@WithDefault("10s")
|
||||
Duration socketTimeout();
|
||||
|
||||
/**
|
||||
* Time to live for a connection in the pool.
|
||||
*/
|
||||
@WithDefault("30s")
|
||||
Duration connectionTimeToLive();
|
||||
/** Time to wait for the full response to be received. */
|
||||
@WithDefault("10s")
|
||||
Duration responseTimeout();
|
||||
|
||||
/**
|
||||
* Time between eviction runs for idle connections.
|
||||
*/
|
||||
@WithDefault("1m")
|
||||
Duration idleConnectionsEvictionInterval();
|
||||
/** Time to live for a connection in the pool. */
|
||||
@WithDefault("30s")
|
||||
Duration connectionTimeToLive();
|
||||
|
||||
/**
|
||||
* If a feed is larger than this, it will be discarded to prevent memory issues while parsing the feed.
|
||||
*/
|
||||
@WithDefault("5M")
|
||||
MemorySize maxResponseSize();
|
||||
/** Time between eviction runs for idle connections. */
|
||||
@WithDefault("1m")
|
||||
Duration idleConnectionsEvictionInterval();
|
||||
|
||||
/**
|
||||
* Prevent access to local addresses to mitigate server-side request forgery (SSRF) attacks, which could potentially expose internal
|
||||
* resources.
|
||||
*
|
||||
* You may want to enable this if you host a public instance of CommaFeed with regisration open.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean blockLocalAddresses();
|
||||
/**
|
||||
* If a feed is larger than this, it will be discarded to prevent memory issues while
|
||||
* parsing the feed.
|
||||
*/
|
||||
@WithDefault("5M")
|
||||
MemorySize maxResponseSize();
|
||||
|
||||
/**
|
||||
* HTTP client cache configuration
|
||||
*/
|
||||
@ConfigDocSection
|
||||
HttpClientCache cache();
|
||||
}
|
||||
/**
|
||||
* Prevent access to local addresses to mitigate server-side request forgery (SSRF) attacks,
|
||||
* which could potentially expose internal resources.
|
||||
*
|
||||
* <p>You may want to enable this if you host a public instance of CommaFeed with
|
||||
* registrations open.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean blockLocalAddresses();
|
||||
|
||||
interface HttpClientCache {
|
||||
/**
|
||||
* Whether to enable the cache. This cache is used to avoid spamming feeds in short bursts (e.g. when subscribing to a feed for the
|
||||
* first time or when clicking "fetch all my feeds now").
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
/** HTTP client cache configuration */
|
||||
@ConfigDocSection
|
||||
HttpClientCache cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum amount of memory the cache can use.
|
||||
*/
|
||||
@WithDefault("10M")
|
||||
MemorySize maximumMemorySize();
|
||||
interface HttpClientCache {
|
||||
/**
|
||||
* Whether to enable the cache. This cache is used to avoid spamming feeds in short bursts
|
||||
* (e.g. when subscribing to a feed for the first time or when clicking "fetch all my feeds
|
||||
* now").
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
|
||||
/**
|
||||
* Duration after which an entry is removed from the cache.
|
||||
*/
|
||||
@WithDefault("1m")
|
||||
Duration expiration();
|
||||
}
|
||||
/** Maximum amount of memory the cache can use. */
|
||||
@WithDefault("10M")
|
||||
MemorySize maximumMemorySize();
|
||||
|
||||
interface FeedRefresh {
|
||||
/**
|
||||
* Default amount of time CommaFeed will wait before refreshing a feed.
|
||||
*/
|
||||
@WithDefault("5m")
|
||||
Duration interval();
|
||||
/** Duration after which an entry is removed from the cache. */
|
||||
@WithDefault("1m")
|
||||
Duration expiration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum amount of time CommaFeed will wait before refreshing a feed. This is used as an upper bound when:
|
||||
*
|
||||
* <ul>
|
||||
* <li>an error occurs while refreshing a feed and we're backing off exponentially</li>
|
||||
* <li>we receive a Cache-Control header from the feed</li>
|
||||
* <li>we receive a Retry-After header from the feed</li>
|
||||
* </ul>
|
||||
*/
|
||||
@WithDefault("4h")
|
||||
Duration maxInterval();
|
||||
interface FeedRefresh {
|
||||
/** Default amount of time CommaFeed will wait before refreshing a feed. */
|
||||
@WithDefault("5m")
|
||||
Duration interval();
|
||||
|
||||
/**
|
||||
* If enabled, CommaFeed will calculate the next refresh time based on the feed's average time between entries and the time since
|
||||
* the last entry was published. The interval will be sometimes between the default refresh interval
|
||||
* (`commafeed.feed-refresh.interval`) and the maximum refresh interval (`commafeed.feed-refresh.max-interval`).
|
||||
*
|
||||
* See {@link FeedRefreshIntervalCalculator} for details.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean intervalEmpirical();
|
||||
/**
|
||||
* Maximum amount of time CommaFeed will wait before refreshing a feed. This is used as an
|
||||
* upper bound when:
|
||||
*
|
||||
* <ul>
|
||||
* <li>an error occurs while refreshing a feed and we're backing off exponentially
|
||||
* <li>we receive a Cache-Control header from the feed
|
||||
* <li>we receive a Retry-After header from the feed
|
||||
* </ul>
|
||||
*/
|
||||
@WithDefault("4h")
|
||||
Duration maxInterval();
|
||||
|
||||
/**
|
||||
* Feed refresh engine error handling settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
FeedRefreshErrorHandling errors();
|
||||
/**
|
||||
* If enabled, CommaFeed will calculate the next refresh time based on the feed's average
|
||||
* time between entries and the time since the last entry was published. The interval will
|
||||
* be sometimes between the default refresh interval (`commafeed.feed-refresh.interval`) and
|
||||
* the maximum refresh interval (`commafeed.feed-refresh.max-interval`).
|
||||
*
|
||||
* <p>See {@link FeedRefreshIntervalCalculator} for details.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean intervalEmpirical();
|
||||
|
||||
/**
|
||||
* Amount of http threads used to fetch feeds.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("3")
|
||||
int httpThreads();
|
||||
/** Feed refresh engine error handling settings. */
|
||||
@ConfigDocSection
|
||||
FeedRefreshErrorHandling errors();
|
||||
|
||||
/**
|
||||
* Amount of threads used to insert new entries in the database.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("1")
|
||||
int databaseThreads();
|
||||
/** Amount of http threads used to fetch feeds. */
|
||||
@Min(1)
|
||||
@WithDefault("3")
|
||||
int httpThreads();
|
||||
|
||||
/**
|
||||
* Duration after which a user is considered inactive. Feeds for inactive users are not refreshed until they log in again.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration userInactivityPeriod();
|
||||
/** Amount of threads used to insert new entries in the database. */
|
||||
@Min(1)
|
||||
@WithDefault("1")
|
||||
int databaseThreads();
|
||||
|
||||
/**
|
||||
* Duration after which the evaluation of a filtering expresion to mark an entry as read is considered to have timed out.
|
||||
*/
|
||||
@WithDefault("500ms")
|
||||
Duration filteringExpressionEvaluationTimeout();
|
||||
/**
|
||||
* Duration after which a user is considered inactive. Feeds for inactive users are not
|
||||
* refreshed until they log in again.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration userInactivityPeriod();
|
||||
|
||||
/**
|
||||
* Duration after which the "Fetch all my feeds now" action is available again after use to avoid spamming feeds.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration forceRefreshCooldownDuration();
|
||||
}
|
||||
/**
|
||||
* Duration after which the evaluation of a filtering expresion to mark an entry as read is
|
||||
* considered to have timed out.
|
||||
*/
|
||||
@WithDefault("500ms")
|
||||
Duration filteringExpressionEvaluationTimeout();
|
||||
|
||||
interface PushNotifications {
|
||||
/**
|
||||
* Whether to enable push notifications to notify users of new entries in their feeds.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
/**
|
||||
* Duration after which the "Fetch all my feeds now" action is available again after use to
|
||||
* avoid spamming feeds.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration forceRefreshCooldownDuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Amount of threads used to send external notifications about new entries.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("5")
|
||||
int threads();
|
||||
interface PushNotifications {
|
||||
/** Whether to enable push notifications to notify users of new entries in their feeds. */
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
|
||||
/**
|
||||
* Maximum amount of notifications that can be queued before new notifications are discarded.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("100")
|
||||
int queueCapacity();
|
||||
}
|
||||
/** Amount of threads used to send external notifications about new entries. */
|
||||
@Min(1)
|
||||
@WithDefault("5")
|
||||
int threads();
|
||||
|
||||
interface FeedRefreshErrorHandling {
|
||||
/**
|
||||
* Number of retries before backoff is applied.
|
||||
*/
|
||||
@Min(0)
|
||||
@WithDefault("3")
|
||||
int retriesBeforeBackoff();
|
||||
/**
|
||||
* Maximum amount of notifications that can be queued before new notifications are
|
||||
* discarded.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("100")
|
||||
int queueCapacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Duration to wait before retrying after an error. Will be multiplied by the number of errors since the last successful fetch.
|
||||
*/
|
||||
@WithDefault("1h")
|
||||
Duration backoffInterval();
|
||||
}
|
||||
interface FeedRefreshErrorHandling {
|
||||
/** Number of retries before backoff is applied. */
|
||||
@Min(0)
|
||||
@WithDefault("3")
|
||||
int retriesBeforeBackoff();
|
||||
|
||||
interface Database {
|
||||
/**
|
||||
* Timeout applied to all database queries.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration queryTimeout();
|
||||
/**
|
||||
* Duration to wait before retrying after an error. Will be multiplied by the number of
|
||||
* errors since the last successful fetch.
|
||||
*/
|
||||
@WithDefault("1h")
|
||||
Duration backoffInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Database cleanup settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
Cleanup cleanup();
|
||||
interface Database {
|
||||
/**
|
||||
* Timeout applied to all database queries.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration queryTimeout();
|
||||
|
||||
interface Cleanup {
|
||||
/**
|
||||
* Maximum age of feed entries in the database. Older entries will be deleted.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("365d")
|
||||
Duration entriesMaxAge();
|
||||
/** Database cleanup settings. */
|
||||
@ConfigDocSection
|
||||
Cleanup cleanup();
|
||||
|
||||
/**
|
||||
* Maximum age of feed entry statuses (read/unread) in the database. Older statuses will be deleted.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration statusesMaxAge();
|
||||
interface Cleanup {
|
||||
/**
|
||||
* Maximum age of feed entries in the database. Older entries will be deleted.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("365d")
|
||||
Duration entriesMaxAge();
|
||||
|
||||
/**
|
||||
* Maximum number of entries per feed to keep in the database.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("500")
|
||||
int maxFeedCapacity();
|
||||
/**
|
||||
* Maximum age of feed entry statuses (read/unread) in the database. Older statuses will
|
||||
* be deleted.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration statusesMaxAge();
|
||||
|
||||
/**
|
||||
* Limit the number of feeds a user can subscribe to.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
int maxFeedsPerUser();
|
||||
/**
|
||||
* Maximum number of entries per feed to keep in the database.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("500")
|
||||
int maxFeedCapacity();
|
||||
|
||||
/**
|
||||
* Rows to delete per query while cleaning up old entries.
|
||||
*/
|
||||
@Positive
|
||||
@WithDefault("100")
|
||||
int batchSize();
|
||||
/**
|
||||
* Limit the number of feeds a user can subscribe to.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
int maxFeedsPerUser();
|
||||
|
||||
/**
|
||||
* Whether to keep starred entries when cleaning up old entries.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean keepStarredEntries();
|
||||
/** Rows to delete per query while cleaning up old entries. */
|
||||
@Positive
|
||||
@WithDefault("100")
|
||||
int batchSize();
|
||||
|
||||
default Instant statusesInstantThreshold() {
|
||||
return statusesMaxAge().toMillis() > 0 ? Instant.now().minus(statusesMaxAge()) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Whether to keep starred entries when cleaning up old entries. */
|
||||
@WithDefault("true")
|
||||
boolean keepStarredEntries();
|
||||
|
||||
interface Users {
|
||||
/**
|
||||
* Whether to let users create accounts for themselves.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean allowRegistrations();
|
||||
default Instant statusesInstantThreshold() {
|
||||
return statusesMaxAge().toMillis() > 0
|
||||
? Instant.now().minus(statusesMaxAge())
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum password length for user accounts.
|
||||
*/
|
||||
@WithDefault("4")
|
||||
int minimumPasswordLength();
|
||||
interface Users {
|
||||
/** Whether to let users create accounts for themselves. */
|
||||
@WithDefault("false")
|
||||
boolean allowRegistrations();
|
||||
|
||||
/**
|
||||
* Whether an email address is required when creating a user account.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean emailAddressRequired();
|
||||
/** Minimum password length for user accounts. */
|
||||
@WithDefault("4")
|
||||
int minimumPasswordLength();
|
||||
|
||||
/**
|
||||
* Whether to create a demo account the first time the app starts.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean createDemoAccount();
|
||||
}
|
||||
/** Whether an email address is required when creating a user account. */
|
||||
@WithDefault("false")
|
||||
boolean emailAddressRequired();
|
||||
|
||||
interface Websocket {
|
||||
/**
|
||||
* Enable websocket connection so the server can notify web clients that there are new entries for feeds.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
/** Whether to create a demo account the first time the app starts. */
|
||||
@WithDefault("false")
|
||||
boolean createDemoAccount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interval at which the client will send a ping message on the websocket to keep the connection alive.
|
||||
*/
|
||||
@WithDefault("15m")
|
||||
Duration pingInterval();
|
||||
interface Websocket {
|
||||
/**
|
||||
* Enable websocket connection so the server can notify web clients that there are new
|
||||
* entries for feeds.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
|
||||
/**
|
||||
* If the websocket connection is disabled or the connection is lost, the client will reload the feed tree at this interval.
|
||||
*/
|
||||
@WithDefault("30s")
|
||||
Duration treeReloadInterval();
|
||||
}
|
||||
/**
|
||||
* Interval at which the client will send a ping message on the websocket to keep the
|
||||
* connection alive.
|
||||
*/
|
||||
@WithDefault("15m")
|
||||
Duration pingInterval();
|
||||
|
||||
/**
|
||||
* If the websocket connection is disabled or the connection is lost, the client will reload
|
||||
* the feed tree at this interval.
|
||||
*/
|
||||
@WithDefault("30s")
|
||||
Duration treeReloadInterval();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class CommaFeedConstants {
|
||||
public static final String USERNAME_DEMO = "demo";
|
||||
public static final String USERNAME_DEMO = "demo";
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.time.InstantSource;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import jakarta.enterprise.inject.Produces;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import java.time.InstantSource;
|
||||
|
||||
@Singleton
|
||||
public class CommaFeedProducers {
|
||||
|
||||
@Produces
|
||||
@Singleton
|
||||
public InstantSource instantSource() {
|
||||
return InstantSource.system();
|
||||
}
|
||||
|
||||
@Produces
|
||||
@Singleton
|
||||
public MetricRegistry metricRegistry() {
|
||||
return new MetricRegistry();
|
||||
}
|
||||
@Produces
|
||||
@Singleton
|
||||
public InstantSource instantSource() {
|
||||
return InstantSource.system();
|
||||
}
|
||||
|
||||
@Produces
|
||||
@Singleton
|
||||
public MetricRegistry metricRegistry() {
|
||||
return new MetricRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
package com.commafeed;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Singleton
|
||||
@Getter
|
||||
public class CommaFeedVersion {
|
||||
|
||||
private final String version;
|
||||
private final String gitCommit;
|
||||
private final String version;
|
||||
private final String gitCommit;
|
||||
|
||||
public CommaFeedVersion() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
try (InputStream stream = getClass().getResourceAsStream("/git.properties")) {
|
||||
if (stream != null) {
|
||||
properties.load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
this.version = properties.getProperty("git.build.version", "unknown");
|
||||
this.gitCommit = properties.getProperty("git.commit.id.abbrev", "unknown");
|
||||
}
|
||||
public CommaFeedVersion() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
try (InputStream stream = getClass().getResourceAsStream("/git.properties")) {
|
||||
if (stream != null) {
|
||||
properties.load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
this.version = properties.getProperty("git.build.version", "unknown");
|
||||
this.gitCommit = properties.getProperty("git.commit.id.abbrev", "unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,54 @@
|
||||
package com.commafeed;
|
||||
|
||||
import com.commafeed.security.CookieService;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import io.quarkus.security.AuthenticationFailedException;
|
||||
import io.quarkus.security.UnauthorizedException;
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.validation.ValidationException;
|
||||
import jakarta.ws.rs.core.NewCookie;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jboss.resteasy.reactive.RestResponse;
|
||||
import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder;
|
||||
import org.jboss.resteasy.reactive.RestResponse.Status;
|
||||
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
|
||||
|
||||
import com.commafeed.security.CookieService;
|
||||
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import io.quarkus.security.AuthenticationFailedException;
|
||||
import io.quarkus.security.UnauthorizedException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Provider
|
||||
@Priority(1)
|
||||
public class ExceptionMappers {
|
||||
|
||||
private final CookieService cookieService;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final CookieService cookieService;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
@ServerExceptionMapper(UnauthorizedException.class)
|
||||
public RestResponse<UnauthorizedResponse> unauthorized(UnauthorizedException e) {
|
||||
return RestResponse.status(Status.UNAUTHORIZED, new UnauthorizedResponse(e.getMessage(), config.users().allowRegistrations()));
|
||||
}
|
||||
@ServerExceptionMapper(UnauthorizedException.class)
|
||||
public RestResponse<UnauthorizedResponse> unauthorized(UnauthorizedException e) {
|
||||
return RestResponse.status(
|
||||
Status.UNAUTHORIZED,
|
||||
new UnauthorizedResponse(e.getMessage(), config.users().allowRegistrations()));
|
||||
}
|
||||
|
||||
@ServerExceptionMapper(AuthenticationFailedException.class)
|
||||
public RestResponse<AuthenticationFailed> authenticationFailed(AuthenticationFailedException e) {
|
||||
NewCookie logoutCookie = cookieService.buildLogoutCookie();
|
||||
return ResponseBuilder.create(Status.UNAUTHORIZED, new AuthenticationFailed(e.getMessage())).cookie(logoutCookie).build();
|
||||
}
|
||||
@ServerExceptionMapper(AuthenticationFailedException.class)
|
||||
public RestResponse<AuthenticationFailed> authenticationFailed(
|
||||
AuthenticationFailedException e) {
|
||||
NewCookie logoutCookie = cookieService.buildLogoutCookie();
|
||||
return ResponseBuilder.create(Status.UNAUTHORIZED, new AuthenticationFailed(e.getMessage()))
|
||||
.cookie(logoutCookie)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ServerExceptionMapper(ValidationException.class)
|
||||
public RestResponse<ValidationFailed> validationFailed(ValidationException e) {
|
||||
return RestResponse.status(Status.BAD_REQUEST, new ValidationFailed(e.getMessage()));
|
||||
}
|
||||
@ServerExceptionMapper(ValidationException.class)
|
||||
public RestResponse<ValidationFailed> validationFailed(ValidationException e) {
|
||||
return RestResponse.status(Status.BAD_REQUEST, new ValidationFailed(e.getMessage()));
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record UnauthorizedResponse(String message, boolean allowRegistrations) {
|
||||
}
|
||||
@RegisterForReflection
|
||||
public record UnauthorizedResponse(String message, boolean allowRegistrations) {}
|
||||
|
||||
@RegisterForReflection
|
||||
public record AuthenticationFailed(String message) {
|
||||
}
|
||||
@RegisterForReflection
|
||||
public record AuthenticationFailed(String message) {}
|
||||
|
||||
@RegisterForReflection
|
||||
public record ValidationFailed(String message) {
|
||||
}
|
||||
@RegisterForReflection
|
||||
public record ValidationFailed(String message) {}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import com.codahale.metrics.json.MetricsModule;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
import io.quarkus.jackson.ObjectMapperCustomizer;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Singleton
|
||||
public class JacksonCustomizer implements ObjectMapperCustomizer {
|
||||
@Override
|
||||
public void customize(ObjectMapper objectMapper) {
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
@Override
|
||||
public void customize(ObjectMapper objectMapper) {
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
|
||||
// read and write instants as milliseconds instead of nanoseconds
|
||||
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)
|
||||
.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
|
||||
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
|
||||
// read and write instants as milliseconds instead of nanoseconds
|
||||
objectMapper
|
||||
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)
|
||||
.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
|
||||
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
|
||||
|
||||
// add support for serializing metrics
|
||||
objectMapper.registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
|
||||
}
|
||||
// add support for serializing metrics
|
||||
objectMapper.registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,221 +6,343 @@ import com.codahale.metrics.Histogram;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.Timer;
|
||||
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
|
||||
@RegisterForReflection(
|
||||
targets = {
|
||||
// metrics
|
||||
MetricRegistry.class, Meter.class, Gauge.class, Counter.class, Timer.class, Histogram.class,
|
||||
targets = {
|
||||
// metrics
|
||||
MetricRegistry.class,
|
||||
Meter.class,
|
||||
Gauge.class,
|
||||
Counter.class,
|
||||
Timer.class,
|
||||
Histogram.class,
|
||||
|
||||
// rome
|
||||
java.util.Date.class, com.rometools.opml.feed.synd.impl.TreeCategoryImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndFeedImpl.class, com.rometools.rome.feed.module.DCSubjectImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndEntryImpl.class, com.rometools.modules.psc.types.SimpleChapter.class,
|
||||
com.rometools.rome.feed.synd.SyndCategoryImpl.class, com.rometools.rome.feed.synd.SyndImageImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndContentImpl.class, com.rometools.rome.feed.synd.SyndEnclosureImpl.class,
|
||||
// rome
|
||||
java.util.Date.class,
|
||||
com.rometools.opml.feed.synd.impl.TreeCategoryImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndFeedImpl.class,
|
||||
com.rometools.rome.feed.module.DCSubjectImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndEntryImpl.class,
|
||||
com.rometools.modules.psc.types.SimpleChapter.class,
|
||||
com.rometools.rome.feed.synd.SyndCategoryImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndImageImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndContentImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndEnclosureImpl.class,
|
||||
|
||||
// rome cloneable
|
||||
com.rometools.modules.activitystreams.types.Article.class, com.rometools.modules.activitystreams.types.Audio.class,
|
||||
com.rometools.modules.activitystreams.types.Bookmark.class, com.rometools.modules.activitystreams.types.Comment.class,
|
||||
com.rometools.modules.activitystreams.types.Event.class, com.rometools.modules.activitystreams.types.File.class,
|
||||
com.rometools.modules.activitystreams.types.Folder.class, com.rometools.modules.activitystreams.types.List.class,
|
||||
com.rometools.modules.activitystreams.types.Note.class, com.rometools.modules.activitystreams.types.Person.class,
|
||||
com.rometools.modules.activitystreams.types.Photo.class, com.rometools.modules.activitystreams.types.PhotoAlbum.class,
|
||||
com.rometools.modules.activitystreams.types.Place.class, com.rometools.modules.activitystreams.types.Playlist.class,
|
||||
com.rometools.modules.activitystreams.types.Product.class, com.rometools.modules.activitystreams.types.Review.class,
|
||||
com.rometools.modules.activitystreams.types.Service.class, com.rometools.modules.activitystreams.types.Song.class,
|
||||
com.rometools.modules.activitystreams.types.Status.class, com.rometools.modules.base.types.DateTimeRange.class,
|
||||
com.rometools.modules.base.types.FloatUnit.class, com.rometools.modules.base.types.GenderEnumeration.class,
|
||||
com.rometools.modules.base.types.IntUnit.class, com.rometools.modules.base.types.PriceTypeEnumeration.class,
|
||||
com.rometools.modules.base.types.ShippingType.class, com.rometools.modules.base.types.ShortDate.class,
|
||||
com.rometools.modules.base.types.Size.class, com.rometools.modules.base.types.YearType.class,
|
||||
com.rometools.modules.content.ContentItem.class, com.rometools.modules.georss.GeoRSSPoint.class,
|
||||
com.rometools.modules.georss.geometries.Envelope.class, com.rometools.modules.georss.geometries.LineString.class,
|
||||
com.rometools.modules.georss.geometries.LinearRing.class, com.rometools.modules.georss.geometries.Point.class,
|
||||
com.rometools.modules.georss.geometries.Polygon.class, com.rometools.modules.georss.geometries.Position.class,
|
||||
com.rometools.modules.georss.geometries.PositionList.class, com.rometools.modules.mediarss.types.MediaGroup.class,
|
||||
com.rometools.modules.mediarss.types.Metadata.class, com.rometools.modules.mediarss.types.Thumbnail.class,
|
||||
com.rometools.modules.opensearch.entity.OSQuery.class, com.rometools.modules.photocast.types.PhotoDate.class,
|
||||
com.rometools.modules.sle.types.DateValue.class, com.rometools.modules.sle.types.Group.class,
|
||||
com.rometools.modules.sle.types.NumberValue.class, com.rometools.modules.sle.types.Sort.class,
|
||||
com.rometools.modules.sle.types.StringValue.class, com.rometools.modules.yahooweather.types.Astronomy.class,
|
||||
com.rometools.modules.yahooweather.types.Atmosphere.class, com.rometools.modules.yahooweather.types.Condition.class,
|
||||
com.rometools.modules.yahooweather.types.Forecast.class, com.rometools.modules.yahooweather.types.Location.class,
|
||||
com.rometools.modules.yahooweather.types.Units.class, com.rometools.modules.yahooweather.types.Wind.class,
|
||||
com.rometools.opml.feed.opml.Attribute.class, com.rometools.opml.feed.opml.Opml.class,
|
||||
com.rometools.opml.feed.opml.Outline.class, com.rometools.rome.feed.atom.Category.class,
|
||||
com.rometools.rome.feed.atom.Content.class, com.rometools.rome.feed.atom.Entry.class,
|
||||
com.rometools.rome.feed.atom.Feed.class, com.rometools.rome.feed.atom.Generator.class,
|
||||
com.rometools.rome.feed.atom.Link.class, com.rometools.rome.feed.atom.Person.class,
|
||||
com.rometools.rome.feed.rss.Category.class, com.rometools.rome.feed.rss.Channel.class,
|
||||
com.rometools.rome.feed.rss.Cloud.class, com.rometools.rome.feed.rss.Content.class,
|
||||
com.rometools.rome.feed.rss.Description.class, com.rometools.rome.feed.rss.Enclosure.class,
|
||||
com.rometools.rome.feed.rss.Guid.class, com.rometools.rome.feed.rss.Image.class, com.rometools.rome.feed.rss.Item.class,
|
||||
com.rometools.rome.feed.rss.Source.class, com.rometools.rome.feed.rss.TextInput.class,
|
||||
com.rometools.rome.feed.synd.SyndLinkImpl.class, com.rometools.rome.feed.synd.SyndPersonImpl.class,
|
||||
java.util.ArrayList.class,
|
||||
// rome cloneable
|
||||
com.rometools.modules.activitystreams.types.Article.class,
|
||||
com.rometools.modules.activitystreams.types.Audio.class,
|
||||
com.rometools.modules.activitystreams.types.Bookmark.class,
|
||||
com.rometools.modules.activitystreams.types.Comment.class,
|
||||
com.rometools.modules.activitystreams.types.Event.class,
|
||||
com.rometools.modules.activitystreams.types.File.class,
|
||||
com.rometools.modules.activitystreams.types.Folder.class,
|
||||
com.rometools.modules.activitystreams.types.List.class,
|
||||
com.rometools.modules.activitystreams.types.Note.class,
|
||||
com.rometools.modules.activitystreams.types.Person.class,
|
||||
com.rometools.modules.activitystreams.types.Photo.class,
|
||||
com.rometools.modules.activitystreams.types.PhotoAlbum.class,
|
||||
com.rometools.modules.activitystreams.types.Place.class,
|
||||
com.rometools.modules.activitystreams.types.Playlist.class,
|
||||
com.rometools.modules.activitystreams.types.Product.class,
|
||||
com.rometools.modules.activitystreams.types.Review.class,
|
||||
com.rometools.modules.activitystreams.types.Service.class,
|
||||
com.rometools.modules.activitystreams.types.Song.class,
|
||||
com.rometools.modules.activitystreams.types.Status.class,
|
||||
com.rometools.modules.base.types.DateTimeRange.class,
|
||||
com.rometools.modules.base.types.FloatUnit.class,
|
||||
com.rometools.modules.base.types.GenderEnumeration.class,
|
||||
com.rometools.modules.base.types.IntUnit.class,
|
||||
com.rometools.modules.base.types.PriceTypeEnumeration.class,
|
||||
com.rometools.modules.base.types.ShippingType.class,
|
||||
com.rometools.modules.base.types.ShortDate.class,
|
||||
com.rometools.modules.base.types.Size.class,
|
||||
com.rometools.modules.base.types.YearType.class,
|
||||
com.rometools.modules.content.ContentItem.class,
|
||||
com.rometools.modules.georss.GeoRSSPoint.class,
|
||||
com.rometools.modules.georss.geometries.Envelope.class,
|
||||
com.rometools.modules.georss.geometries.LineString.class,
|
||||
com.rometools.modules.georss.geometries.LinearRing.class,
|
||||
com.rometools.modules.georss.geometries.Point.class,
|
||||
com.rometools.modules.georss.geometries.Polygon.class,
|
||||
com.rometools.modules.georss.geometries.Position.class,
|
||||
com.rometools.modules.georss.geometries.PositionList.class,
|
||||
com.rometools.modules.mediarss.types.MediaGroup.class,
|
||||
com.rometools.modules.mediarss.types.Metadata.class,
|
||||
com.rometools.modules.mediarss.types.Thumbnail.class,
|
||||
com.rometools.modules.opensearch.entity.OSQuery.class,
|
||||
com.rometools.modules.photocast.types.PhotoDate.class,
|
||||
com.rometools.modules.sle.types.DateValue.class,
|
||||
com.rometools.modules.sle.types.Group.class,
|
||||
com.rometools.modules.sle.types.NumberValue.class,
|
||||
com.rometools.modules.sle.types.Sort.class,
|
||||
com.rometools.modules.sle.types.StringValue.class,
|
||||
com.rometools.modules.yahooweather.types.Astronomy.class,
|
||||
com.rometools.modules.yahooweather.types.Atmosphere.class,
|
||||
com.rometools.modules.yahooweather.types.Condition.class,
|
||||
com.rometools.modules.yahooweather.types.Forecast.class,
|
||||
com.rometools.modules.yahooweather.types.Location.class,
|
||||
com.rometools.modules.yahooweather.types.Units.class,
|
||||
com.rometools.modules.yahooweather.types.Wind.class,
|
||||
com.rometools.opml.feed.opml.Attribute.class,
|
||||
com.rometools.opml.feed.opml.Opml.class,
|
||||
com.rometools.opml.feed.opml.Outline.class,
|
||||
com.rometools.rome.feed.atom.Category.class,
|
||||
com.rometools.rome.feed.atom.Content.class,
|
||||
com.rometools.rome.feed.atom.Entry.class,
|
||||
com.rometools.rome.feed.atom.Feed.class,
|
||||
com.rometools.rome.feed.atom.Generator.class,
|
||||
com.rometools.rome.feed.atom.Link.class,
|
||||
com.rometools.rome.feed.atom.Person.class,
|
||||
com.rometools.rome.feed.rss.Category.class,
|
||||
com.rometools.rome.feed.rss.Channel.class,
|
||||
com.rometools.rome.feed.rss.Cloud.class,
|
||||
com.rometools.rome.feed.rss.Content.class,
|
||||
com.rometools.rome.feed.rss.Description.class,
|
||||
com.rometools.rome.feed.rss.Enclosure.class,
|
||||
com.rometools.rome.feed.rss.Guid.class,
|
||||
com.rometools.rome.feed.rss.Image.class,
|
||||
com.rometools.rome.feed.rss.Item.class,
|
||||
com.rometools.rome.feed.rss.Source.class,
|
||||
com.rometools.rome.feed.rss.TextInput.class,
|
||||
com.rometools.rome.feed.synd.SyndLinkImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndPersonImpl.class,
|
||||
java.util.ArrayList.class,
|
||||
|
||||
// rome modules
|
||||
com.rometools.modules.sse.modules.Conflict.class, com.rometools.modules.sse.modules.Conflicts.class,
|
||||
com.rometools.modules.cc.CreativeCommonsImpl.class, com.rometools.modules.feedpress.modules.FeedpressModuleImpl.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleImpl.class, com.rometools.modules.sse.modules.Sharing.class,
|
||||
com.rometools.modules.georss.SimpleModuleImpl.class, com.rometools.modules.atom.modules.AtomLinkModuleImpl.class,
|
||||
com.rometools.modules.itunes.EntryInformationImpl.class, com.rometools.modules.sse.modules.Update.class,
|
||||
com.rometools.modules.photocast.PhotocastModuleImpl.class, com.rometools.modules.itunes.FeedInformationImpl.class,
|
||||
com.rometools.modules.yahooweather.YWeatherModuleImpl.class, com.rometools.modules.feedburner.FeedBurnerImpl.class,
|
||||
com.rometools.modules.sse.modules.Related.class, com.rometools.modules.fyyd.modules.FyydModuleImpl.class,
|
||||
com.rometools.modules.psc.modules.PodloveSimpleChapterModuleImpl.class, com.rometools.modules.thr.ThreadingModuleImpl.class,
|
||||
com.rometools.modules.sse.modules.Sync.class, com.rometools.modules.sle.SimpleListExtensionImpl.class,
|
||||
com.rometools.modules.slash.SlashImpl.class, com.rometools.modules.sse.modules.History.class,
|
||||
com.rometools.modules.georss.GMLModuleImpl.class, com.rometools.modules.base.CustomTagsImpl.class,
|
||||
com.rometools.modules.base.GoogleBaseImpl.class, com.rometools.modules.sle.SleEntryImpl.class,
|
||||
com.rometools.modules.mediarss.MediaEntryModuleImpl.class, com.rometools.modules.content.ContentModuleImpl.class,
|
||||
com.rometools.modules.georss.W3CGeoModuleImpl.class, com.rometools.rome.feed.module.DCModuleImpl.class,
|
||||
com.rometools.modules.mediarss.MediaModuleImpl.class, com.rometools.rome.feed.module.SyModuleImpl.class,
|
||||
// rome modules
|
||||
com.rometools.modules.sse.modules.Conflict.class,
|
||||
com.rometools.modules.sse.modules.Conflicts.class,
|
||||
com.rometools.modules.cc.CreativeCommonsImpl.class,
|
||||
com.rometools.modules.feedpress.modules.FeedpressModuleImpl.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleImpl.class,
|
||||
com.rometools.modules.sse.modules.Sharing.class,
|
||||
com.rometools.modules.georss.SimpleModuleImpl.class,
|
||||
com.rometools.modules.atom.modules.AtomLinkModuleImpl.class,
|
||||
com.rometools.modules.itunes.EntryInformationImpl.class,
|
||||
com.rometools.modules.sse.modules.Update.class,
|
||||
com.rometools.modules.photocast.PhotocastModuleImpl.class,
|
||||
com.rometools.modules.itunes.FeedInformationImpl.class,
|
||||
com.rometools.modules.yahooweather.YWeatherModuleImpl.class,
|
||||
com.rometools.modules.feedburner.FeedBurnerImpl.class,
|
||||
com.rometools.modules.sse.modules.Related.class,
|
||||
com.rometools.modules.fyyd.modules.FyydModuleImpl.class,
|
||||
com.rometools.modules.psc.modules.PodloveSimpleChapterModuleImpl.class,
|
||||
com.rometools.modules.thr.ThreadingModuleImpl.class,
|
||||
com.rometools.modules.sse.modules.Sync.class,
|
||||
com.rometools.modules.sle.SimpleListExtensionImpl.class,
|
||||
com.rometools.modules.slash.SlashImpl.class,
|
||||
com.rometools.modules.sse.modules.History.class,
|
||||
com.rometools.modules.georss.GMLModuleImpl.class,
|
||||
com.rometools.modules.base.CustomTagsImpl.class,
|
||||
com.rometools.modules.base.GoogleBaseImpl.class,
|
||||
com.rometools.modules.sle.SleEntryImpl.class,
|
||||
com.rometools.modules.mediarss.MediaEntryModuleImpl.class,
|
||||
com.rometools.modules.content.ContentModuleImpl.class,
|
||||
com.rometools.modules.georss.W3CGeoModuleImpl.class,
|
||||
com.rometools.rome.feed.module.DCModuleImpl.class,
|
||||
com.rometools.modules.mediarss.MediaModuleImpl.class,
|
||||
com.rometools.rome.feed.module.SyModuleImpl.class,
|
||||
|
||||
// extracted from all 3 rome.properties files of rome library
|
||||
com.rometools.rome.io.impl.RSS090Parser.class, com.rometools.rome.io.impl.RSS091NetscapeParser.class,
|
||||
com.rometools.rome.io.impl.RSS091UserlandParser.class, com.rometools.rome.io.impl.RSS092Parser.class,
|
||||
com.rometools.rome.io.impl.RSS093Parser.class, com.rometools.rome.io.impl.RSS094Parser.class,
|
||||
com.rometools.rome.io.impl.RSS10Parser.class, com.rometools.rome.io.impl.RSS20wNSParser.class,
|
||||
com.rometools.rome.io.impl.RSS20Parser.class, com.rometools.rome.io.impl.Atom10Parser.class,
|
||||
com.rometools.rome.io.impl.Atom03Parser.class,
|
||||
|
||||
com.rometools.rome.io.impl.SyModuleParser.class, com.rometools.rome.io.impl.DCModuleParser.class,
|
||||
|
||||
com.rometools.rome.io.impl.RSS090Generator.class, com.rometools.rome.io.impl.RSS091NetscapeGenerator.class,
|
||||
com.rometools.rome.io.impl.RSS091UserlandGenerator.class, com.rometools.rome.io.impl.RSS092Generator.class,
|
||||
com.rometools.rome.io.impl.RSS093Generator.class, com.rometools.rome.io.impl.RSS094Generator.class,
|
||||
com.rometools.rome.io.impl.RSS10Generator.class, com.rometools.rome.io.impl.RSS20Generator.class,
|
||||
com.rometools.rome.io.impl.Atom10Generator.class, com.rometools.rome.io.impl.Atom03Generator.class,
|
||||
|
||||
com.rometools.rome.feed.synd.impl.ConverterForAtom10.class, com.rometools.rome.feed.synd.impl.ConverterForAtom03.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS090.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS091Netscape.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS091Userland.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS092.class, com.rometools.rome.feed.synd.impl.ConverterForRSS093.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS094.class, com.rometools.rome.feed.synd.impl.ConverterForRSS10.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS20.class,
|
||||
|
||||
com.rometools.modules.mediarss.io.RSS20YahooParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class, com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class, com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class, com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class, com.rometools.modules.sle.io.ModuleParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class, com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class, com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class, com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class, com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class, com.rometools.modules.sle.io.ModuleParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class, com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS1.class, com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class, com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class, com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressParser.class, com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class, com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.atom.io.AtomModuleParser.class, com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class, com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class, com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class, com.rometools.modules.sle.io.ItemParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS1.class, com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class, com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class, com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class, com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class, com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class, com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class, com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class, com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.thr.io.ThreadingModuleParser.class, com.rometools.modules.psc.io.PodloveSimpleChapterParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.itunes.io.ITunesGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class, com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class, com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class, com.rometools.modules.atom.io.AtomModuleGenerator.class,
|
||||
com.rometools.modules.sle.io.ModuleGenerator.class, com.rometools.modules.yahooweather.io.WeatherModuleGenerator.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressGenerator.class, com.rometools.modules.fyyd.io.FyydGenerator.class,
|
||||
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class, com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class, com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class, com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class, com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressGenerator.class, com.rometools.modules.fyyd.io.FyydGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class, com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class, com.rometools.modules.itunes.io.ITunesGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class, com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class, com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class, com.rometools.modules.atom.io.AtomModuleGenerator.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleGenerator.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterGenerator.class,
|
||||
|
||||
com.rometools.modules.base.io.GoogleBaseGenerator.class, com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class, com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class, com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class, com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.base.io.CustomTagGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class, com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class, com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class, com.rometools.modules.thr.io.ThreadingModuleGenerator.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterGenerator.class,
|
||||
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
|
||||
com.rometools.opml.io.impl.OPML10Generator.class, com.rometools.opml.io.impl.OPML20Generator.class,
|
||||
|
||||
com.rometools.opml.io.impl.OPML10Parser.class, com.rometools.opml.io.impl.OPML20Parser.class,
|
||||
|
||||
com.rometools.opml.feed.synd.impl.ConverterForOPML10.class, com.rometools.opml.feed.synd.impl.ConverterForOPML20.class, })
|
||||
|
||||
public class NativeImageClasses {
|
||||
}
|
||||
// extracted from all 3 rome.properties files of rome library
|
||||
com.rometools.rome.io.impl.RSS090Parser.class,
|
||||
com.rometools.rome.io.impl.RSS091NetscapeParser.class,
|
||||
com.rometools.rome.io.impl.RSS091UserlandParser.class,
|
||||
com.rometools.rome.io.impl.RSS092Parser.class,
|
||||
com.rometools.rome.io.impl.RSS093Parser.class,
|
||||
com.rometools.rome.io.impl.RSS094Parser.class,
|
||||
com.rometools.rome.io.impl.RSS10Parser.class,
|
||||
com.rometools.rome.io.impl.RSS20wNSParser.class,
|
||||
com.rometools.rome.io.impl.RSS20Parser.class,
|
||||
com.rometools.rome.io.impl.Atom10Parser.class,
|
||||
com.rometools.rome.io.impl.Atom03Parser.class,
|
||||
com.rometools.rome.io.impl.SyModuleParser.class,
|
||||
com.rometools.rome.io.impl.DCModuleParser.class,
|
||||
com.rometools.rome.io.impl.RSS090Generator.class,
|
||||
com.rometools.rome.io.impl.RSS091NetscapeGenerator.class,
|
||||
com.rometools.rome.io.impl.RSS091UserlandGenerator.class,
|
||||
com.rometools.rome.io.impl.RSS092Generator.class,
|
||||
com.rometools.rome.io.impl.RSS093Generator.class,
|
||||
com.rometools.rome.io.impl.RSS094Generator.class,
|
||||
com.rometools.rome.io.impl.RSS10Generator.class,
|
||||
com.rometools.rome.io.impl.RSS20Generator.class,
|
||||
com.rometools.rome.io.impl.Atom10Generator.class,
|
||||
com.rometools.rome.io.impl.Atom03Generator.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForAtom10.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForAtom03.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS090.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS091Netscape.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS091Userland.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS092.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS093.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS094.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS10.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS20.class,
|
||||
com.rometools.modules.mediarss.io.RSS20YahooParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.sle.io.ModuleParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.sle.io.ModuleParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS1.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.sle.io.ItemParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS1.class,
|
||||
com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.thr.io.ThreadingModuleParser.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterParser.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.itunes.io.ITunesGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.atom.io.AtomModuleGenerator.class,
|
||||
com.rometools.modules.sle.io.ModuleGenerator.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleGenerator.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressGenerator.class,
|
||||
com.rometools.modules.fyyd.io.FyydGenerator.class,
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressGenerator.class,
|
||||
com.rometools.modules.fyyd.io.FyydGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class,
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.itunes.io.ITunesGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.atom.io.AtomModuleGenerator.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleGenerator.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterGenerator.class,
|
||||
com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.thr.io.ThreadingModuleGenerator.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterGenerator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.opml.io.impl.OPML10Generator.class,
|
||||
com.rometools.opml.io.impl.OPML20Generator.class,
|
||||
com.rometools.opml.io.impl.OPML10Parser.class,
|
||||
com.rometools.opml.io.impl.OPML20Parser.class,
|
||||
com.rometools.opml.feed.synd.impl.ConverterForOPML10.class,
|
||||
com.rometools.opml.feed.synd.impl.ConverterForOPML20.class,
|
||||
})
|
||||
public class NativeImageClasses {}
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import com.google.common.hash.HashFunction;
|
||||
import com.google.common.hash.Hashing;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
@SuppressWarnings("deprecation")
|
||||
public class Digests {
|
||||
|
||||
public static String sha1Hex(byte[] input) {
|
||||
return hashBytesToHex(Hashing.sha1(), input);
|
||||
}
|
||||
public static String sha1Hex(byte[] input) {
|
||||
return hashBytesToHex(Hashing.sha1(), input);
|
||||
}
|
||||
|
||||
public static String sha1Hex(String input) {
|
||||
return hashBytesToHex(Hashing.sha1(), input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
public static String sha1Hex(String input) {
|
||||
return hashBytesToHex(Hashing.sha1(), input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String md5Hex(String input) {
|
||||
return hashBytesToHex(Hashing.md5(), input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
public static String md5Hex(String input) {
|
||||
return hashBytesToHex(Hashing.md5(), input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static String hashBytesToHex(HashFunction function, byte[] input) {
|
||||
return function.hashBytes(input).toString();
|
||||
}
|
||||
private static String hashBytesToHex(HashFunction function, byte[] input) {
|
||||
return function.hashBytes(input).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedVersion;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import inet.ipaddr.IPAddress;
|
||||
import inet.ipaddr.IPAddressNetwork;
|
||||
import inet.ipaddr.IPAddressString;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -8,9 +15,9 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import nl.altindag.ssl.SSLFactory;
|
||||
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
|
||||
import org.apache.hc.client5.http.DnsResolver;
|
||||
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||
import org.apache.hc.client5.http.config.ConnectionConfig;
|
||||
@@ -35,132 +42,148 @@ import org.apache.hc.core5.util.TimeValue;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.brotli.dec.BrotliInputStream;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedVersion;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import nl.altindag.ssl.SSLFactory;
|
||||
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
|
||||
|
||||
@Singleton
|
||||
@RequiredArgsConstructor
|
||||
public class HttpClientFactory {
|
||||
|
||||
private static final DnsResolver DNS_RESOLVER = SystemDefaultDnsResolver.INSTANCE;
|
||||
private static final DnsResolver DNS_RESOLVER = SystemDefaultDnsResolver.INSTANCE;
|
||||
private static final IPAddress CGNAT_RANGE = new IPAddressString("100.64.0.0/10").getAddress();
|
||||
|
||||
private final CommaFeedConfiguration config;
|
||||
private final CommaFeedVersion version;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final CommaFeedVersion version;
|
||||
|
||||
public CloseableHttpClient newClient(int poolSize) {
|
||||
PoolingHttpClientConnectionManager connectionManager = newConnectionManager(config, poolSize);
|
||||
String userAgent = config.httpClient()
|
||||
.userAgent()
|
||||
.orElseGet(() -> String.format("CommaFeed/%s (https://github.com/Athou/commafeed)", version.getVersion()));
|
||||
return newClient(config, connectionManager, userAgent);
|
||||
}
|
||||
public CloseableHttpClient newClient(int poolSize) {
|
||||
PoolingHttpClientConnectionManager connectionManager =
|
||||
newConnectionManager(config, poolSize);
|
||||
String userAgent =
|
||||
config.httpClient()
|
||||
.userAgent()
|
||||
.orElseGet(
|
||||
() ->
|
||||
String.format(
|
||||
"CommaFeed/%s (https://github.com/Athou/commafeed)",
|
||||
version.getVersion()));
|
||||
return newClient(config, connectionManager, userAgent);
|
||||
}
|
||||
|
||||
private CloseableHttpClient newClient(CommaFeedConfiguration config, HttpClientConnectionManager connectionManager, String userAgent) {
|
||||
List<Header> headers = new ArrayList<>();
|
||||
headers.add(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en"));
|
||||
headers.add(new BasicHeader(HttpHeaders.PRAGMA, "No-cache"));
|
||||
headers.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"));
|
||||
private CloseableHttpClient newClient(
|
||||
CommaFeedConfiguration config,
|
||||
HttpClientConnectionManager connectionManager,
|
||||
String userAgent) {
|
||||
List<Header> headers = new ArrayList<>();
|
||||
headers.add(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en"));
|
||||
headers.add(new BasicHeader(HttpHeaders.PRAGMA, "No-cache"));
|
||||
headers.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"));
|
||||
|
||||
SequencedMap<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);
|
||||
SequencedMap<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);
|
||||
|
||||
RedirectStrategy redirectStrategy = config.httpClient().blockLocalAddresses()
|
||||
? new BlockLocalAddressesRedirectStrategy(DNS_RESOLVER)
|
||||
: new DefaultRedirectStrategy();
|
||||
RedirectStrategy redirectStrategy =
|
||||
config.httpClient().blockLocalAddresses()
|
||||
? new BlockLocalAddressesRedirectStrategy(DNS_RESOLVER)
|
||||
: new DefaultRedirectStrategy();
|
||||
|
||||
return HttpClientBuilder.create()
|
||||
.useSystemProperties()
|
||||
.disableAutomaticRetries()
|
||||
.disableCookieManagement()
|
||||
.setUserAgent(userAgent)
|
||||
.setDefaultHeaders(headers)
|
||||
.setConnectionManager(connectionManager)
|
||||
.evictExpiredConnections()
|
||||
.evictIdleConnections(TimeValue.of(config.httpClient().idleConnectionsEvictionInterval()))
|
||||
.setContentDecoderRegistry(new LinkedHashMap<>(contentDecoderMap))
|
||||
.setRedirectStrategy(redirectStrategy)
|
||||
.build();
|
||||
}
|
||||
return HttpClientBuilder.create()
|
||||
.disableConnectionState()
|
||||
.useSystemProperties()
|
||||
.disableAutomaticRetries()
|
||||
.disableCookieManagement()
|
||||
.setUserAgent(userAgent)
|
||||
.setDefaultHeaders(headers)
|
||||
.setConnectionManager(connectionManager)
|
||||
.evictExpiredConnections()
|
||||
.evictIdleConnections(
|
||||
TimeValue.of(config.httpClient().idleConnectionsEvictionInterval()))
|
||||
.setContentDecoderRegistry(new LinkedHashMap<>(contentDecoderMap))
|
||||
.setRedirectStrategy(redirectStrategy)
|
||||
.build();
|
||||
}
|
||||
|
||||
private PoolingHttpClientConnectionManager newConnectionManager(CommaFeedConfiguration config, int poolSize) {
|
||||
SSLFactory sslFactory = SSLFactory.builder().withUnsafeTrustMaterial().withUnsafeHostnameVerifier().build();
|
||||
DnsResolver dnsResolver = config.httpClient().blockLocalAddresses() ? new BlockLocalAddressesDnsResolver(DNS_RESOLVER)
|
||||
: DNS_RESOLVER;
|
||||
private PoolingHttpClientConnectionManager newConnectionManager(
|
||||
CommaFeedConfiguration config, int poolSize) {
|
||||
SSLFactory sslFactory =
|
||||
SSLFactory.builder().withUnsafeTrustMaterial().withUnsafeHostnameVerifier().build();
|
||||
DnsResolver dnsResolver =
|
||||
config.httpClient().blockLocalAddresses()
|
||||
? new BlockLocalAddressesDnsResolver(DNS_RESOLVER)
|
||||
: DNS_RESOLVER;
|
||||
|
||||
return PoolingHttpClientConnectionManagerBuilder.create()
|
||||
.setTlsSocketStrategy(Apache5SslUtils.toTlsSocketStrategy(sslFactory))
|
||||
.setDefaultConnectionConfig(ConnectionConfig.custom()
|
||||
.setConnectTimeout(Timeout.of(config.httpClient().connectTimeout()))
|
||||
.setSocketTimeout(Timeout.of(config.httpClient().socketTimeout()))
|
||||
.setTimeToLive(Timeout.of(config.httpClient().connectionTimeToLive()))
|
||||
.build())
|
||||
.setDefaultTlsConfig(TlsConfig.custom().setHandshakeTimeout(Timeout.of(config.httpClient().sslHandshakeTimeout())).build())
|
||||
.setMaxConnPerRoute(poolSize)
|
||||
.setMaxConnTotal(poolSize)
|
||||
.setDnsResolver(dnsResolver)
|
||||
.build();
|
||||
return PoolingHttpClientConnectionManagerBuilder.create()
|
||||
.setTlsSocketStrategy(Apache5SslUtils.toTlsSocketStrategy(sslFactory))
|
||||
.setDefaultConnectionConfig(
|
||||
ConnectionConfig.custom()
|
||||
.setConnectTimeout(Timeout.of(config.httpClient().connectTimeout()))
|
||||
.setSocketTimeout(Timeout.of(config.httpClient().socketTimeout()))
|
||||
.setTimeToLive(
|
||||
Timeout.of(config.httpClient().connectionTimeToLive()))
|
||||
.build())
|
||||
.setDefaultTlsConfig(
|
||||
TlsConfig.custom()
|
||||
.setHandshakeTimeout(
|
||||
Timeout.of(config.httpClient().sslHandshakeTimeout()))
|
||||
.build())
|
||||
.setMaxConnPerRoute(poolSize)
|
||||
.setMaxConnTotal(poolSize)
|
||||
.setDnsResolver(dnsResolver)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
private static boolean isLocalAddress(InetAddress address) {
|
||||
IPAddress ip = new IPAddressNetwork.IPAddressGenerator().from(address);
|
||||
return ip.isLocal() || ip.isLoopback() || ip.isMulticast() || CGNAT_RANGE.contains(ip);
|
||||
}
|
||||
|
||||
private static boolean isLocalAddress(InetAddress address) {
|
||||
return address.isSiteLocalAddress() || address.isAnyLocalAddress() || address.isLinkLocalAddress() || address.isLoopbackAddress()
|
||||
|| address.isMulticastAddress();
|
||||
}
|
||||
private record BlockLocalAddressesDnsResolver(DnsResolver delegate) implements DnsResolver {
|
||||
@Override
|
||||
public InetAddress[] resolve(String host) throws UnknownHostException {
|
||||
InetAddress[] addresses = delegate.resolve(host);
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isLocalAddress(addr)) {
|
||||
throw new UnknownHostException(
|
||||
"Access to local address blocked: " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
private record BlockLocalAddressesDnsResolver(DnsResolver delegate) implements DnsResolver {
|
||||
@Override
|
||||
public InetAddress[] resolve(String host) throws UnknownHostException {
|
||||
InetAddress[] addresses = delegate.resolve(host);
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isLocalAddress(addr)) {
|
||||
throw new UnknownHostException("Access to local address blocked: " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
@Override
|
||||
public String resolveCanonicalHostname(String host) throws UnknownHostException {
|
||||
return delegate.resolveCanonicalHostname(host);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveCanonicalHostname(String host) throws UnknownHostException {
|
||||
return delegate.resolveCanonicalHostname(host);
|
||||
}
|
||||
}
|
||||
@RequiredArgsConstructor
|
||||
private static class BlockLocalAddressesRedirectStrategy extends DefaultRedirectStrategy {
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class BlockLocalAddressesRedirectStrategy extends DefaultRedirectStrategy {
|
||||
private final DnsResolver delegate;
|
||||
|
||||
private final DnsResolver delegate;
|
||||
@Override
|
||||
public URI getLocationURI(HttpRequest request, HttpResponse response, HttpContext context)
|
||||
throws HttpException {
|
||||
URI redirectUri = super.getLocationURI(request, response, context);
|
||||
|
||||
@Override
|
||||
public URI getLocationURI(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException {
|
||||
URI redirectUri = super.getLocationURI(request, response, context);
|
||||
String host = redirectUri.getHost();
|
||||
if (host == null) {
|
||||
throw new HttpException("Redirect URI does not have a host: " + redirectUri);
|
||||
}
|
||||
|
||||
String host = redirectUri.getHost();
|
||||
if (host == null) {
|
||||
throw new HttpException("Redirect URI does not have a host: " + redirectUri);
|
||||
}
|
||||
InetAddress[] addresses;
|
||||
try {
|
||||
addresses = delegate.resolve(host);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new HttpException("Unknown host: " + host);
|
||||
}
|
||||
|
||||
InetAddress[] addresses;
|
||||
try {
|
||||
addresses = delegate.resolve(host);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new HttpException("Unknown host: " + host);
|
||||
}
|
||||
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isLocalAddress(addr)) {
|
||||
throw new HttpException("Access to local address blocked: " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
|
||||
return redirectUri;
|
||||
}
|
||||
}
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isLocalAddress(addr)) {
|
||||
throw new HttpException(
|
||||
"Access to local address blocked: " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
|
||||
return redirectUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedConfiguration.HttpClientCache;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.ws.rs.core.CacheControl;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
@@ -8,10 +18,12 @@ import java.time.Instant;
|
||||
import java.time.InstantSource;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.ws.rs.core.CacheControl;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Lombok;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
@@ -27,284 +39,326 @@ import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.jboss.resteasy.reactive.common.headers.CacheControlDelegate;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedConfiguration.HttpClientCache;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Lombok;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
|
||||
*/
|
||||
/** Smart HTTP getter: handles gzip, ssl, last modified and etag headers */
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class HttpGetter {
|
||||
private final CommaFeedConfiguration config;
|
||||
private final InstantSource instantSource;
|
||||
private final CloseableHttpClient client;
|
||||
private final Cache<HttpRequest, HttpResponse> cache;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final InstantSource instantSource;
|
||||
private final CloseableHttpClient client;
|
||||
private final Cache<HttpRequest, HttpResponse> cache;
|
||||
|
||||
public HttpGetter(CommaFeedConfiguration config, InstantSource instantSource, HttpClientFactory httpClientFactory,
|
||||
MetricRegistry metrics) {
|
||||
this.config = config;
|
||||
this.instantSource = instantSource;
|
||||
this.client = httpClientFactory.newClient(config.feedRefresh().httpThreads());
|
||||
this.cache = newCache(config);
|
||||
public HttpGetter(
|
||||
CommaFeedConfiguration config,
|
||||
InstantSource instantSource,
|
||||
HttpClientFactory httpClientFactory,
|
||||
MetricRegistry metrics) {
|
||||
this.config = config;
|
||||
this.instantSource = instantSource;
|
||||
this.client = httpClientFactory.newClient(config.feedRefresh().httpThreads());
|
||||
this.cache = newCache(config);
|
||||
|
||||
metrics.registerGauge(MetricRegistry.name(getClass(), "cache", "size"), () -> cache == null ? 0 : cache.size());
|
||||
metrics.registerGauge(MetricRegistry.name(getClass(), "cache", "memoryUsage"),
|
||||
() -> cache == null ? 0 : cache.asMap().values().stream().mapToInt(e -> ArrayUtils.getLength(e.content)).sum());
|
||||
}
|
||||
metrics.registerGauge(
|
||||
MetricRegistry.name(getClass(), "cache", "size"),
|
||||
() -> cache == null ? 0 : cache.size());
|
||||
metrics.registerGauge(
|
||||
MetricRegistry.name(getClass(), "cache", "memoryUsage"),
|
||||
() ->
|
||||
cache == null
|
||||
? 0
|
||||
: cache.asMap().values().stream()
|
||||
.mapToInt(e -> ArrayUtils.getLength(e.content))
|
||||
.sum());
|
||||
}
|
||||
|
||||
public HttpResult get(String url) throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||
return get(HttpRequest.builder(url).build());
|
||||
}
|
||||
public HttpResult get(String url)
|
||||
throws IOException,
|
||||
NotModifiedException,
|
||||
TooManyRequestsException,
|
||||
SchemeNotAllowedException {
|
||||
return get(HttpRequest.builder(url).build());
|
||||
}
|
||||
|
||||
public HttpResult get(HttpRequest request)
|
||||
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||
URI uri = URI.create(request.getUrl());
|
||||
ensureHttpScheme(uri.getScheme());
|
||||
public HttpResult get(HttpRequest request)
|
||||
throws IOException,
|
||||
NotModifiedException,
|
||||
TooManyRequestsException,
|
||||
SchemeNotAllowedException {
|
||||
URI uri = URI.create(request.getUrl());
|
||||
ensureHttpScheme(uri.getScheme());
|
||||
|
||||
final HttpResponse response;
|
||||
if (cache == null) {
|
||||
response = invoke(request);
|
||||
} else {
|
||||
try {
|
||||
response = cache.get(request, () -> invoke(request));
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof IOException ioe) {
|
||||
throw ioe;
|
||||
} else {
|
||||
throw Lombok.sneakyThrow(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
final HttpResponse response;
|
||||
if (cache == null) {
|
||||
response = invoke(request);
|
||||
} else {
|
||||
try {
|
||||
response = cache.get(request, () -> invoke(request));
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof IOException ioe) {
|
||||
throw ioe;
|
||||
} else {
|
||||
throw Lombok.sneakyThrow(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int code = response.code();
|
||||
if (code == HttpStatus.SC_TOO_MANY_REQUESTS || code == HttpStatus.SC_SERVICE_UNAVAILABLE && response.retryAfter() != null) {
|
||||
throw new TooManyRequestsException(response.retryAfter());
|
||||
}
|
||||
int code = response.code();
|
||||
if (code == HttpStatus.SC_TOO_MANY_REQUESTS
|
||||
|| code == HttpStatus.SC_SERVICE_UNAVAILABLE && response.retryAfter() != null) {
|
||||
throw new TooManyRequestsException(response.retryAfter());
|
||||
}
|
||||
|
||||
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
||||
throw new NotModifiedException("'304 - not modified' http code received");
|
||||
}
|
||||
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
||||
throw new NotModifiedException("'304 - not modified' http code received");
|
||||
}
|
||||
|
||||
if (code >= 300) {
|
||||
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
|
||||
}
|
||||
if (code >= 300) {
|
||||
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
|
||||
}
|
||||
|
||||
String lastModifiedHeader = response.lastModifiedHeader();
|
||||
String eTagHeader = response.eTagHeader();
|
||||
String lastModifiedHeader = response.lastModifiedHeader();
|
||||
String eTagHeader = response.eTagHeader();
|
||||
|
||||
Duration validFor = Optional.ofNullable(response.cacheControl())
|
||||
.filter(cc -> cc.getMaxAge() >= 0)
|
||||
.map(cc -> Duration.ofSeconds(cc.getMaxAge()))
|
||||
.orElse(Duration.ZERO);
|
||||
Duration validFor =
|
||||
Optional.ofNullable(response.cacheControl())
|
||||
.filter(cc -> cc.getMaxAge() >= 0)
|
||||
.map(cc -> Duration.ofSeconds(cc.getMaxAge()))
|
||||
.orElse(Duration.ZERO);
|
||||
|
||||
return new HttpResult(response.content(), response.contentType(), lastModifiedHeader, eTagHeader, response.urlAfterRedirect(),
|
||||
validFor);
|
||||
}
|
||||
return new HttpResult(
|
||||
response.content(),
|
||||
response.contentType(),
|
||||
lastModifiedHeader,
|
||||
eTagHeader,
|
||||
response.urlAfterRedirect(),
|
||||
validFor);
|
||||
}
|
||||
|
||||
private void ensureHttpScheme(String scheme) throws SchemeNotAllowedException {
|
||||
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
|
||||
throw new SchemeNotAllowedException(scheme);
|
||||
}
|
||||
}
|
||||
private void ensureHttpScheme(String scheme) throws SchemeNotAllowedException {
|
||||
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
|
||||
throw new SchemeNotAllowedException(scheme);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpResponse invoke(HttpRequest request) throws IOException {
|
||||
log.debug("fetching {}", request.getUrl());
|
||||
private HttpResponse invoke(HttpRequest request) throws IOException {
|
||||
log.debug("fetching {}", request.getUrl());
|
||||
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(RequestConfig.custom()
|
||||
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
|
||||
// causes issues with some feeds
|
||||
// see https://github.com/Athou/commafeed/issues/1572
|
||||
// and https://issues.apache.org/jira/browse/HTTPCLIENT-2344
|
||||
.setProtocolUpgradeEnabled(false)
|
||||
.build());
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(
|
||||
RequestConfig.custom()
|
||||
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
|
||||
// causes issues with some feeds
|
||||
// see https://github.com/Athou/commafeed/issues/1572
|
||||
// and https://issues.apache.org/jira/browse/HTTPCLIENT-2344
|
||||
.setProtocolUpgradeEnabled(false)
|
||||
.build());
|
||||
|
||||
return client.execute(request.toClassicHttpRequest(), context, resp -> {
|
||||
byte[] content = resp.getEntity() == null ? null
|
||||
: toByteArray(resp.getEntity(), config.httpClient().maxResponseSize().asLongValue());
|
||||
int code = resp.getCode();
|
||||
String lastModifiedHeader = Optional.ofNullable(resp.getFirstHeader(HttpHeaders.LAST_MODIFIED))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.orElse(null);
|
||||
String eTagHeader = Optional.ofNullable(resp.getFirstHeader(HttpHeaders.ETAG))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.orElse(null);
|
||||
return client.execute(
|
||||
request.toClassicHttpRequest(),
|
||||
context,
|
||||
resp -> {
|
||||
byte[] content =
|
||||
resp.getEntity() == null
|
||||
? null
|
||||
: toByteArray(
|
||||
resp.getEntity(),
|
||||
config.httpClient().maxResponseSize().asLongValue());
|
||||
int code = resp.getCode();
|
||||
String lastModifiedHeader =
|
||||
Optional.ofNullable(resp.getFirstHeader(HttpHeaders.LAST_MODIFIED))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.orElse(null);
|
||||
String eTagHeader =
|
||||
Optional.ofNullable(resp.getFirstHeader(HttpHeaders.ETAG))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.orElse(null);
|
||||
|
||||
CacheControl cacheControl = Optional.ofNullable(resp.getFirstHeader(HttpHeaders.CACHE_CONTROL))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.map(HttpGetter::toCacheControl)
|
||||
.orElse(null);
|
||||
CacheControl cacheControl =
|
||||
Optional.ofNullable(resp.getFirstHeader(HttpHeaders.CACHE_CONTROL))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.map(HttpGetter::toCacheControl)
|
||||
.orElse(null);
|
||||
|
||||
Instant retryAfter = Optional.ofNullable(resp.getFirstHeader(HttpHeaders.RETRY_AFTER))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.map(this::toInstant)
|
||||
.orElse(null);
|
||||
Instant retryAfter =
|
||||
Optional.ofNullable(resp.getFirstHeader(HttpHeaders.RETRY_AFTER))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.map(this::toInstant)
|
||||
.orElse(null);
|
||||
|
||||
String contentType = Optional.ofNullable(resp.getEntity()).map(HttpEntity::getContentType).orElse(null);
|
||||
String urlAfterRedirect = Optional.ofNullable(context.getRedirectLocations())
|
||||
.map(RedirectLocations::getAll)
|
||||
.map(l -> Iterables.getLast(l, null))
|
||||
.map(URI::toString)
|
||||
.orElse(request.getUrl());
|
||||
String contentType =
|
||||
Optional.ofNullable(resp.getEntity())
|
||||
.map(HttpEntity::getContentType)
|
||||
.orElse(null);
|
||||
String urlAfterRedirect =
|
||||
Optional.ofNullable(context.getRedirectLocations())
|
||||
.map(RedirectLocations::getAll)
|
||||
.map(l -> Iterables.getLast(l, null))
|
||||
.map(URI::toString)
|
||||
.orElse(request.getUrl());
|
||||
|
||||
return new HttpResponse(code, lastModifiedHeader, eTagHeader, cacheControl, retryAfter, content, contentType, urlAfterRedirect);
|
||||
});
|
||||
}
|
||||
return new HttpResponse(
|
||||
code,
|
||||
lastModifiedHeader,
|
||||
eTagHeader,
|
||||
cacheControl,
|
||||
retryAfter,
|
||||
content,
|
||||
contentType,
|
||||
urlAfterRedirect);
|
||||
});
|
||||
}
|
||||
|
||||
private static CacheControl toCacheControl(String headerValue) {
|
||||
try {
|
||||
return CacheControlDelegate.INSTANCE.fromString(headerValue);
|
||||
} catch (Exception e) {
|
||||
log.debug("Invalid Cache-Control header: {}", headerValue);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private static CacheControl toCacheControl(String headerValue) {
|
||||
try {
|
||||
return CacheControlDelegate.INSTANCE.fromString(headerValue);
|
||||
} catch (Exception e) {
|
||||
log.debug("Invalid Cache-Control header: {}", headerValue);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Instant toInstant(String headerValue) {
|
||||
if (headerValue == null) {
|
||||
return null;
|
||||
}
|
||||
private Instant toInstant(String headerValue) {
|
||||
if (headerValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (StringUtils.isNumeric(headerValue)) {
|
||||
return instantSource.instant().plusSeconds(Long.parseLong(headerValue));
|
||||
}
|
||||
if (StringUtils.isNumeric(headerValue)) {
|
||||
return instantSource.instant().plusSeconds(Long.parseLong(headerValue));
|
||||
}
|
||||
|
||||
return DateUtils.parseStandardDate(headerValue);
|
||||
}
|
||||
return DateUtils.parseStandardDate(headerValue);
|
||||
}
|
||||
|
||||
private static byte[] toByteArray(HttpEntity entity, long maxBytes) throws IOException {
|
||||
if (entity.getContentLength() > maxBytes) {
|
||||
throw new IOException(
|
||||
"Response size (%s bytes) exceeds the maximum allowed size (%s bytes)".formatted(entity.getContentLength(), maxBytes));
|
||||
}
|
||||
private static byte[] toByteArray(HttpEntity entity, long maxBytes) throws IOException {
|
||||
if (entity.getContentLength() > maxBytes) {
|
||||
throw new IOException(
|
||||
"Response size (%s bytes) exceeds the maximum allowed size (%s bytes)"
|
||||
.formatted(entity.getContentLength(), maxBytes));
|
||||
}
|
||||
|
||||
try (InputStream input = entity.getContent()) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream input = entity.getContent()) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] bytes = ByteStreams.limit(input, maxBytes + 1).readAllBytes();
|
||||
if (bytes.length > maxBytes) {
|
||||
throw new IOException("Response size exceeds the maximum allowed size (%s bytes)".formatted(maxBytes));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
byte[] bytes = ByteStreams.limit(input, maxBytes + 1).readAllBytes();
|
||||
if (bytes.length > maxBytes) {
|
||||
throw new IOException(
|
||||
"Response size exceeds the maximum allowed size (%s bytes)"
|
||||
.formatted(maxBytes));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
private static Cache<HttpRequest, HttpResponse> newCache(CommaFeedConfiguration config) {
|
||||
HttpClientCache cacheConfig = config.httpClient().cache();
|
||||
if (!cacheConfig.enabled()) {
|
||||
return null;
|
||||
}
|
||||
private static Cache<HttpRequest, HttpResponse> newCache(CommaFeedConfiguration config) {
|
||||
HttpClientCache cacheConfig = config.httpClient().cache();
|
||||
if (!cacheConfig.enabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CacheBuilder.newBuilder()
|
||||
.weigher((HttpRequest key, HttpResponse value) -> value.content() != null ? value.content().length : 0)
|
||||
.maximumWeight(cacheConfig.maximumMemorySize().asLongValue())
|
||||
.expireAfterWrite(cacheConfig.expiration())
|
||||
.build();
|
||||
}
|
||||
return CacheBuilder.newBuilder()
|
||||
.weigher(
|
||||
(HttpRequest key, HttpResponse value) ->
|
||||
value.content() != null ? value.content().length : 0)
|
||||
.maximumWeight(cacheConfig.maximumMemorySize().asLongValue())
|
||||
.expireAfterWrite(cacheConfig.expiration())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static class SchemeNotAllowedException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public static class SchemeNotAllowedException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SchemeNotAllowedException(String scheme) {
|
||||
super("Scheme not allowed: " + scheme);
|
||||
}
|
||||
}
|
||||
public SchemeNotAllowedException(String scheme) {
|
||||
super("Scheme not allowed: " + scheme);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class NotModifiedException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Getter
|
||||
public static class NotModifiedException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* if the value of this header changed, this is its new value
|
||||
*/
|
||||
private final String newLastModifiedHeader;
|
||||
/** if the value of this header changed, this is its new value */
|
||||
private final String newLastModifiedHeader;
|
||||
|
||||
/**
|
||||
* if the value of this header changed, this is its new value
|
||||
*/
|
||||
private final String newEtagHeader;
|
||||
/** if the value of this header changed, this is its new value */
|
||||
private final String newEtagHeader;
|
||||
|
||||
public NotModifiedException(String message) {
|
||||
this(message, null, null);
|
||||
}
|
||||
public NotModifiedException(String message) {
|
||||
this(message, null, null);
|
||||
}
|
||||
|
||||
public NotModifiedException(String message, String newLastModifiedHeader, String newEtagHeader) {
|
||||
super(message);
|
||||
this.newLastModifiedHeader = newLastModifiedHeader;
|
||||
this.newEtagHeader = newEtagHeader;
|
||||
}
|
||||
}
|
||||
public NotModifiedException(
|
||||
String message, String newLastModifiedHeader, String newEtagHeader) {
|
||||
super(message);
|
||||
this.newLastModifiedHeader = newLastModifiedHeader;
|
||||
this.newEtagHeader = newEtagHeader;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class TooManyRequestsException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class TooManyRequestsException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Instant retryAfter;
|
||||
}
|
||||
private final Instant retryAfter;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class HttpResponseException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Getter
|
||||
public static class HttpResponseException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int code;
|
||||
private final int code;
|
||||
|
||||
public HttpResponseException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
public HttpResponseException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
@Builder(builderMethodName = "")
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
public static class HttpRequest {
|
||||
private String url;
|
||||
private String lastModified;
|
||||
private String eTag;
|
||||
@Builder(builderMethodName = "")
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
public static class HttpRequest {
|
||||
private String url;
|
||||
private String lastModified;
|
||||
private String eTag;
|
||||
|
||||
public static HttpRequestBuilder builder(String url) {
|
||||
return new HttpRequestBuilder().url(url);
|
||||
}
|
||||
public static HttpRequestBuilder builder(String url) {
|
||||
return new HttpRequestBuilder().url(url);
|
||||
}
|
||||
|
||||
public ClassicHttpRequest toClassicHttpRequest() {
|
||||
ClassicHttpRequest req = ClassicRequestBuilder.get(url).build();
|
||||
if (lastModified != null) {
|
||||
req.addHeader(HttpHeaders.IF_MODIFIED_SINCE, lastModified);
|
||||
}
|
||||
if (eTag != null) {
|
||||
req.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||
}
|
||||
return req;
|
||||
}
|
||||
}
|
||||
public ClassicHttpRequest toClassicHttpRequest() {
|
||||
ClassicHttpRequest req = ClassicRequestBuilder.get(url).build();
|
||||
if (lastModified != null) {
|
||||
req.addHeader(HttpHeaders.IF_MODIFIED_SINCE, lastModified);
|
||||
}
|
||||
if (eTag != null) {
|
||||
req.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||
}
|
||||
return req;
|
||||
}
|
||||
}
|
||||
|
||||
private record HttpResponse(int code, String lastModifiedHeader, String eTagHeader, CacheControl cacheControl, Instant retryAfter,
|
||||
byte[] content, String contentType, String urlAfterRedirect) {
|
||||
}
|
||||
|
||||
public record HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, String urlAfterRedirect,
|
||||
Duration validFor) {
|
||||
}
|
||||
private record HttpResponse(
|
||||
int code,
|
||||
String lastModifiedHeader,
|
||||
String eTagHeader,
|
||||
CacheControl cacheControl,
|
||||
Instant retryAfter,
|
||||
byte[] content,
|
||||
String contentType,
|
||||
String urlAfterRedirect) {}
|
||||
|
||||
public record HttpResult(
|
||||
byte[] content,
|
||||
String contentType,
|
||||
String lastModifiedSince,
|
||||
String eTag,
|
||||
String urlAfterRedirect,
|
||||
Duration validFor) {}
|
||||
}
|
||||
|
||||
@@ -2,105 +2,104 @@ package com.commafeed.backend;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.netpreserve.urlcanon.Canonicalizer;
|
||||
import org.netpreserve.urlcanon.ParsedUrl;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@UtilityClass
|
||||
@Slf4j
|
||||
public class Urls {
|
||||
|
||||
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
|
||||
private static final Pattern QUESTION_MARK = Pattern.compile(Pattern.quote("?"));
|
||||
|
||||
public static boolean isHttp(String url) {
|
||||
return url.startsWith("http://");
|
||||
}
|
||||
public static boolean isHttp(String url) {
|
||||
return url.startsWith("http://");
|
||||
}
|
||||
|
||||
public static boolean isHttps(String url) {
|
||||
return url.startsWith("https://");
|
||||
}
|
||||
public static boolean isHttps(String url) {
|
||||
return url.startsWith("https://");
|
||||
}
|
||||
|
||||
public static boolean isAbsolute(String url) {
|
||||
return isHttp(url) || isHttps(url);
|
||||
}
|
||||
public static boolean isAbsolute(String url) {
|
||||
return isHttp(url) || isHttps(url);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param relativeUrl
|
||||
* the url of the entry
|
||||
* @param feedLink
|
||||
* the url of the feed as described in the feed
|
||||
* @param feedUrl
|
||||
* the url of the feed that we used to fetch the feed
|
||||
* @return an absolute url pointing to the entry
|
||||
*/
|
||||
public static String toAbsolute(String relativeUrl, String feedLink, String feedUrl) {
|
||||
String baseUrl = (feedLink != null && isAbsolute(feedLink)) ? feedLink : feedUrl;
|
||||
if (baseUrl == null) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @param relativeUrl the url of the entry
|
||||
* @param feedLink the url of the feed as described in the feed
|
||||
* @param feedUrl the url of the feed that we used to fetch the feed
|
||||
* @return an absolute url pointing to the entry
|
||||
*/
|
||||
public static String toAbsolute(String relativeUrl, String feedLink, String feedUrl) {
|
||||
String baseUrl = (feedLink != null && isAbsolute(feedLink)) ? feedLink : feedUrl;
|
||||
if (baseUrl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
public static String removeTrailingSlash(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (url.endsWith("/")) {
|
||||
url = url.substring(0, url.length() - 1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
if (url.endsWith("/")) {
|
||||
url = url.substring(0, url.length() - 1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the url. The resulting url is not meant to be fetched but rather used as a mean to identify a feed and avoid duplicates
|
||||
*/
|
||||
public static String normalize(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Normalize the url. The resulting url is not meant to be fetched but rather used as a mean to
|
||||
* identify a feed and avoid duplicates
|
||||
*/
|
||||
public static String normalize(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ParsedUrl parsedUrl = ParsedUrl.parseUrl(url);
|
||||
Canonicalizer.AGGRESSIVE.canonicalize(parsedUrl);
|
||||
String normalized = parsedUrl.toString();
|
||||
if (normalized == null) {
|
||||
normalized = url;
|
||||
}
|
||||
ParsedUrl parsedUrl = ParsedUrl.parseUrl(url);
|
||||
Canonicalizer.AGGRESSIVE.canonicalize(parsedUrl);
|
||||
String normalized = parsedUrl.toString();
|
||||
if (normalized == null) {
|
||||
normalized = url;
|
||||
}
|
||||
|
||||
// convert to lower case, the url probably won't work in some cases
|
||||
// after that but we don't care we just want to compare urls to avoid
|
||||
// duplicates
|
||||
normalized = normalized.toLowerCase();
|
||||
// convert to lower case, the url probably won't work in some cases
|
||||
// after that but we don't care we just want to compare urls to avoid
|
||||
// duplicates
|
||||
normalized = normalized.toLowerCase();
|
||||
|
||||
// store all urls as http
|
||||
if (normalized.startsWith("https")) {
|
||||
normalized = "http" + normalized.substring(5);
|
||||
}
|
||||
// store all urls as http
|
||||
if (normalized.startsWith("https")) {
|
||||
normalized = "http" + normalized.substring(5);
|
||||
}
|
||||
|
||||
// remove the www. part
|
||||
normalized = normalized.replace("//www.", "//");
|
||||
// remove the www. part
|
||||
normalized = normalized.replace("//www.", "//");
|
||||
|
||||
// feedproxy redirects to feedburner
|
||||
normalized = normalized.replace("feedproxy.google.com", "feeds.feedburner.com");
|
||||
// feedproxy redirects to feedburner
|
||||
normalized = normalized.replace("feedproxy.google.com", "feeds.feedburner.com");
|
||||
|
||||
// feedburner feeds have a special treatment
|
||||
if (normalized.split(ESCAPED_QUESTION_MARK)[0].contains("feedburner.com")) {
|
||||
normalized = normalized.replace("feeds2.feedburner.com", "feeds.feedburner.com");
|
||||
normalized = normalized.split(ESCAPED_QUESTION_MARK)[0];
|
||||
normalized = Strings.CS.removeEnd(normalized, "/");
|
||||
}
|
||||
// feedburner feeds have a special treatment
|
||||
if (QUESTION_MARK.split(normalized)[0].contains("feedburner.com")) {
|
||||
normalized = normalized.replace("feeds2.feedburner.com", "feeds.feedburner.com");
|
||||
normalized = QUESTION_MARK.split(normalized)[0];
|
||||
normalized = Strings.CS.removeEnd(normalized, "/");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,76 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.QFeedCategory;
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Singleton
|
||||
public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
||||
|
||||
private static final QFeedCategory CATEGORY = QFeedCategory.feedCategory;
|
||||
private static final QFeedCategory CATEGORY = QFeedCategory.feedCategory;
|
||||
|
||||
public FeedCategoryDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedCategory.class);
|
||||
}
|
||||
public FeedCategoryDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedCategory.class);
|
||||
}
|
||||
|
||||
public List<FeedCategory> findAll(User user) {
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user)).join(CATEGORY.user, QUser.user).fetchJoin().fetch();
|
||||
}
|
||||
public List<FeedCategory> findAll(User user) {
|
||||
return query().selectFrom(CATEGORY)
|
||||
.where(CATEGORY.user.eq(user))
|
||||
.join(CATEGORY.user, QUser.user)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
}
|
||||
|
||||
public FeedCategory findById(User user, Long id) {
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user), CATEGORY.id.eq(id)).fetchOne();
|
||||
}
|
||||
public FeedCategory findById(User user, Long id) {
|
||||
return query().selectFrom(CATEGORY)
|
||||
.where(CATEGORY.user.eq(user), CATEGORY.id.eq(id))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public FeedCategory findByName(User user, String name, FeedCategory parent) {
|
||||
Predicate parentPredicate;
|
||||
if (parent == null) {
|
||||
parentPredicate = CATEGORY.parent.isNull();
|
||||
} else {
|
||||
parentPredicate = CATEGORY.parent.eq(parent);
|
||||
}
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user), CATEGORY.name.eq(name), parentPredicate).fetchOne();
|
||||
}
|
||||
public FeedCategory findByName(User user, String name, FeedCategory parent) {
|
||||
Predicate parentPredicate;
|
||||
if (parent == null) {
|
||||
parentPredicate = CATEGORY.parent.isNull();
|
||||
} else {
|
||||
parentPredicate = CATEGORY.parent.eq(parent);
|
||||
}
|
||||
return query().selectFrom(CATEGORY)
|
||||
.where(CATEGORY.user.eq(user), CATEGORY.name.eq(name), parentPredicate)
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public List<FeedCategory> findByParent(User user, FeedCategory parent) {
|
||||
Predicate parentPredicate;
|
||||
if (parent == null) {
|
||||
parentPredicate = CATEGORY.parent.isNull();
|
||||
} else {
|
||||
parentPredicate = CATEGORY.parent.eq(parent);
|
||||
}
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user), parentPredicate).fetch();
|
||||
}
|
||||
public List<FeedCategory> findByParent(User user, FeedCategory parent) {
|
||||
Predicate parentPredicate;
|
||||
if (parent == null) {
|
||||
parentPredicate = CATEGORY.parent.isNull();
|
||||
} else {
|
||||
parentPredicate = CATEGORY.parent.eq(parent);
|
||||
}
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user), parentPredicate).fetch();
|
||||
}
|
||||
|
||||
public List<FeedCategory> findAllChildrenCategories(User user, FeedCategory parent) {
|
||||
return findAll(user).stream().filter(c -> isChild(c, parent)).toList();
|
||||
}
|
||||
|
||||
private boolean isChild(FeedCategory child, FeedCategory parent) {
|
||||
if (parent == null) {
|
||||
return true;
|
||||
}
|
||||
boolean isChild = false;
|
||||
while (child != null) {
|
||||
if (Objects.equals(child.getId(), parent.getId())) {
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
child = child.getParent();
|
||||
}
|
||||
return isChild;
|
||||
}
|
||||
public List<FeedCategory> findAllChildrenCategories(User user, FeedCategory parent) {
|
||||
return findAll(user).stream().filter(c -> isChild(c, parent)).toList();
|
||||
}
|
||||
|
||||
private boolean isChild(FeedCategory child, FeedCategory parent) {
|
||||
if (parent == null) {
|
||||
return true;
|
||||
}
|
||||
boolean isChild = false;
|
||||
while (child != null) {
|
||||
if (Objects.equals(child.getId(), parent.getId())) {
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
child = child.getParent();
|
||||
}
|
||||
return isChild;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,69 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.QFeed;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
@Singleton
|
||||
public class FeedDAO extends GenericDAO<Feed> {
|
||||
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
|
||||
public FeedDAO(EntityManager entityManager) {
|
||||
super(entityManager, Feed.class);
|
||||
}
|
||||
public FeedDAO(EntityManager entityManager) {
|
||||
super(entityManager, Feed.class);
|
||||
}
|
||||
|
||||
public List<Feed> findByIds(List<Long> id) {
|
||||
return query().selectFrom(FEED).where(FEED.id.in(id)).fetch();
|
||||
}
|
||||
public List<Feed> findByIds(List<Long> id) {
|
||||
return query().selectFrom(FEED).where(FEED.id.in(id)).fetch();
|
||||
}
|
||||
|
||||
public List<Feed> findNextUpdatable(int count, Instant lastLoginThreshold) {
|
||||
JPAQuery<Feed> query = query().selectFrom(FEED)
|
||||
.distinct()
|
||||
// join on subscriptions to only refresh feeds that have subscribers
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.where(FEED.disabledUntil.isNull().or(FEED.disabledUntil.lt(Instant.now())));
|
||||
public List<Feed> findNextUpdatable(int count, Instant lastLoginThreshold) {
|
||||
JPAQuery<Feed> query =
|
||||
query().selectFrom(FEED)
|
||||
.distinct()
|
||||
// join on subscriptions to only refresh feeds that have subscribers
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.where(
|
||||
FEED.disabledUntil
|
||||
.isNull()
|
||||
.or(FEED.disabledUntil.lt(Instant.now())));
|
||||
|
||||
if (lastLoginThreshold != null) {
|
||||
query.join(SUBSCRIPTION.user).where(SUBSCRIPTION.user.lastLogin.gt(lastLoginThreshold));
|
||||
}
|
||||
if (lastLoginThreshold != null) {
|
||||
query.join(SUBSCRIPTION.user).where(SUBSCRIPTION.user.lastLogin.gt(lastLoginThreshold));
|
||||
}
|
||||
|
||||
return query.orderBy(FEED.disabledUntil.asc()).limit(count).fetch();
|
||||
}
|
||||
return query.orderBy(FEED.disabledUntil.asc()).limit(count).fetch();
|
||||
}
|
||||
|
||||
public void setDisabledUntil(List<Long> feedIds, Instant date) {
|
||||
updateQuery(FEED).set(FEED.disabledUntil, date).where(FEED.id.in(feedIds)).execute();
|
||||
}
|
||||
public void setDisabledUntil(List<Long> feedIds, Instant date) {
|
||||
updateQuery(FEED).set(FEED.disabledUntil, date).where(FEED.id.in(feedIds)).execute();
|
||||
}
|
||||
|
||||
public Feed findByUrl(String normalizedUrl, String normalizedUrlHash) {
|
||||
return query().selectFrom(FEED)
|
||||
.where(FEED.normalizedUrlHash.eq(normalizedUrlHash))
|
||||
.fetch()
|
||||
.stream()
|
||||
.filter(f -> Strings.CS.equals(normalizedUrl, f.getNormalizedUrl()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
public Feed findByUrl(String normalizedUrl, String normalizedUrlHash) {
|
||||
return query()
|
||||
.selectFrom(FEED)
|
||||
.where(FEED.normalizedUrlHash.eq(normalizedUrlHash))
|
||||
.fetch()
|
||||
.stream()
|
||||
.filter(f -> Strings.CS.equals(normalizedUrl, f.getNormalizedUrl()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public List<Feed> findWithoutSubscriptions(int max) {
|
||||
QFeedSubscription sub = QFeedSubscription.feedSubscription;
|
||||
return query().selectFrom(FEED).where(JPAExpressions.selectOne().from(sub).where(sub.feed.eq(FEED)).notExists()).limit(max).fetch();
|
||||
}
|
||||
public List<Feed> findWithoutSubscriptions(int max) {
|
||||
QFeedSubscription sub = QFeedSubscription.feedSubscription;
|
||||
return query().selectFrom(FEED)
|
||||
.where(JPAExpressions.selectOne().from(sub).where(sub.feed.eq(FEED)).notExists())
|
||||
.limit(max)
|
||||
.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntryContent;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
||||
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
|
||||
public FeedEntryContentDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntryContent.class);
|
||||
}
|
||||
public FeedEntryContentDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntryContent.class);
|
||||
}
|
||||
|
||||
public List<FeedEntryContent> findExisting(String contentHash, String titleHash) {
|
||||
return query().select(CONTENT).from(CONTENT).where(CONTENT.contentHash.eq(contentHash), CONTENT.titleHash.eq(titleHash)).fetch();
|
||||
}
|
||||
public List<FeedEntryContent> findExisting(String contentHash, String titleHash) {
|
||||
return query().select(CONTENT)
|
||||
.from(CONTENT)
|
||||
.where(CONTENT.contentHash.eq(contentHash), CONTENT.titleHash.eq(titleHash))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
public long deleteWithoutEntries(int max) {
|
||||
List<Long> ids = query().select(CONTENT.id)
|
||||
.from(CONTENT)
|
||||
.leftJoin(ENTRY)
|
||||
.on(ENTRY.content.id.eq(CONTENT.id))
|
||||
.where(ENTRY.id.isNull())
|
||||
.limit(max)
|
||||
.fetch();
|
||||
return deleteQuery(CONTENT).where(CONTENT.id.in(ids)).execute();
|
||||
}
|
||||
public long deleteWithoutEntries(int max) {
|
||||
List<Long> ids =
|
||||
query().select(CONTENT.id)
|
||||
.from(CONTENT)
|
||||
.leftJoin(ENTRY)
|
||||
.on(ENTRY.content.id.eq(CONTENT.id))
|
||||
.where(ENTRY.id.isNull())
|
||||
.limit(max)
|
||||
.fetch();
|
||||
return deleteQuery(CONTENT).where(CONTENT.id.in(ids)).execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,104 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.querydsl.core.Tuple;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
||||
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final int IN_CLAUSE_BATCH_SIZE = 1000;
|
||||
|
||||
public FeedEntryDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntry.class);
|
||||
}
|
||||
public FeedEntryDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntry.class);
|
||||
}
|
||||
|
||||
public FeedEntry findExisting(String guidHash, Feed feed) {
|
||||
return query().select(ENTRY).from(ENTRY).where(ENTRY.guidHash.eq(guidHash), ENTRY.feed.eq(feed)).limit(1).fetchOne();
|
||||
}
|
||||
public FeedEntry findExisting(String guidHash, Feed feed) {
|
||||
return query().select(ENTRY)
|
||||
.from(ENTRY)
|
||||
.where(ENTRY.guidHash.eq(guidHash), ENTRY.feed.eq(feed))
|
||||
.limit(1)
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public List<FeedCapacity> findFeedsExceedingCapacity(long maxCapacity, long max, boolean keepStarredEntries) {
|
||||
NumberExpression<Long> count = ENTRY.id.count();
|
||||
JPAQuery<Tuple> query = query().select(ENTRY.feed.id, count).from(ENTRY);
|
||||
public Set<String> findExistingGuidHashes(Set<String> guidHashes, Feed feed) {
|
||||
if (guidHashes.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
Set<String> result = new HashSet<>();
|
||||
for (List<String> batch :
|
||||
Lists.partition(new ArrayList<>(guidHashes), IN_CLAUSE_BATCH_SIZE)) {
|
||||
result.addAll(
|
||||
query().select(ENTRY.guidHash)
|
||||
.from(ENTRY)
|
||||
.where(ENTRY.feed.eq(feed), ENTRY.guidHash.in(batch))
|
||||
.fetch());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return query.groupBy(ENTRY.feed)
|
||||
.having(count.gt(maxCapacity))
|
||||
.limit(max)
|
||||
.fetch()
|
||||
.stream()
|
||||
.map(t -> new FeedCapacity(t.get(ENTRY.feed.id), t.get(count)))
|
||||
.toList();
|
||||
}
|
||||
public List<FeedCapacity> findFeedsExceedingCapacity(
|
||||
long maxCapacity, long max, boolean keepStarredEntries) {
|
||||
NumberExpression<Long> count = ENTRY.id.count();
|
||||
JPAQuery<Tuple> query = query().select(ENTRY.feed.id, count).from(ENTRY);
|
||||
|
||||
public int delete(Long feedId, long max) {
|
||||
List<FeedEntry> list = query().selectFrom(ENTRY).where(ENTRY.feed.id.eq(feedId)).limit(max).fetch();
|
||||
return delete(list);
|
||||
}
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entries older than a certain date
|
||||
*/
|
||||
public int deleteEntriesOlderThan(Instant olderThan, long max, boolean keepStarredEntries) {
|
||||
JPAQuery<FeedEntry> query = query().selectFrom(ENTRY)
|
||||
.where(ENTRY.published.lt(olderThan))
|
||||
.orderBy(ENTRY.published.asc())
|
||||
.limit(max);
|
||||
return query.groupBy(ENTRY.feed).having(count.gt(maxCapacity)).limit(max).fetch().stream()
|
||||
.map(t -> new FeedCapacity(t.get(ENTRY.feed.id), t.get(count)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
public int delete(Long feedId, long max) {
|
||||
List<FeedEntry> list =
|
||||
query().selectFrom(ENTRY).where(ENTRY.feed.id.eq(feedId)).limit(max).fetch();
|
||||
return delete(list);
|
||||
}
|
||||
|
||||
return delete(query.fetch());
|
||||
}
|
||||
/** Delete entries older than a certain date */
|
||||
public int deleteEntriesOlderThan(Instant olderThan, long max, boolean keepStarredEntries) {
|
||||
JPAQuery<FeedEntry> query =
|
||||
query().selectFrom(ENTRY)
|
||||
.where(ENTRY.published.lt(olderThan))
|
||||
.orderBy(ENTRY.published.asc())
|
||||
.limit(max);
|
||||
|
||||
/**
|
||||
* Delete the oldest entries of a feed
|
||||
*/
|
||||
public int deleteOldEntries(Long feedId, long max, boolean keepStarredEntries) {
|
||||
JPAQuery<FeedEntry> query = query().selectFrom(ENTRY).where(ENTRY.feed.id.eq(feedId)).orderBy(ENTRY.published.asc()).limit(max);
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
return delete(query.fetch());
|
||||
}
|
||||
|
||||
return delete(query.fetch());
|
||||
}
|
||||
/** Delete the oldest entries of a feed */
|
||||
public int deleteOldEntries(Long feedId, long max, boolean keepStarredEntries) {
|
||||
JPAQuery<FeedEntry> query =
|
||||
query().selectFrom(ENTRY)
|
||||
.where(ENTRY.feed.id.eq(feedId))
|
||||
.orderBy(ENTRY.published.asc())
|
||||
.limit(max);
|
||||
|
||||
public record FeedCapacity(Long id, Long capacity) {
|
||||
}
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
|
||||
return delete(query.fetch());
|
||||
}
|
||||
|
||||
public record FeedCapacity(Long id, Long capacity) {}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.feed.FeedEntryKeyword;
|
||||
import com.commafeed.backend.feed.FeedEntryKeyword.Mode;
|
||||
@@ -32,264 +21,316 @@ import com.querydsl.core.Tuple;
|
||||
import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
|
||||
private final FeedEntryTagDAO feedEntryTagDAO;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final FeedEntryTagDAO feedEntryTagDAO;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
public FeedEntryStatusDAO(EntityManager entityManager, FeedEntryTagDAO feedEntryTagDAO, CommaFeedConfiguration config) {
|
||||
super(entityManager, FeedEntryStatus.class);
|
||||
this.feedEntryTagDAO = feedEntryTagDAO;
|
||||
this.config = config;
|
||||
}
|
||||
public FeedEntryStatusDAO(
|
||||
EntityManager entityManager,
|
||||
FeedEntryTagDAO feedEntryTagDAO,
|
||||
CommaFeedConfiguration config) {
|
||||
super(entityManager, FeedEntryStatus.class);
|
||||
this.feedEntryTagDAO = feedEntryTagDAO;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) {
|
||||
List<FeedEntryStatus> statuses = query().selectFrom(STATUS).where(STATUS.entry.eq(entry), STATUS.subscription.eq(sub)).fetch();
|
||||
FeedEntryStatus status = statuses.stream().findFirst().orElse(null);
|
||||
return handleStatus(user, status, sub, entry);
|
||||
}
|
||||
public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) {
|
||||
List<FeedEntryStatus> statuses =
|
||||
query().selectFrom(STATUS)
|
||||
.where(STATUS.entry.eq(entry), STATUS.subscription.eq(sub))
|
||||
.fetch();
|
||||
FeedEntryStatus status = statuses.stream().findFirst().orElse(null);
|
||||
return handleStatus(user, status, sub, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates an artificial "unread" status if status is null
|
||||
*/
|
||||
private FeedEntryStatus handleStatus(User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
|
||||
if (status == null) {
|
||||
Instant statusesInstantThreshold = config.database().cleanup().statusesInstantThreshold();
|
||||
boolean read = statusesInstantThreshold != null && entry.getPublished().isBefore(statusesInstantThreshold);
|
||||
status = new FeedEntryStatus(user, sub, entry);
|
||||
status.setRead(read);
|
||||
status.setMarkable(!read);
|
||||
} else {
|
||||
status.setMarkable(true);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
/** creates an artificial "unread" status if status is null */
|
||||
private FeedEntryStatus handleStatus(
|
||||
User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
|
||||
if (status == null) {
|
||||
Instant statusesInstantThreshold =
|
||||
config.database().cleanup().statusesInstantThreshold();
|
||||
boolean read =
|
||||
statusesInstantThreshold != null
|
||||
&& entry.getPublished().isBefore(statusesInstantThreshold);
|
||||
status = new FeedEntryStatus(user, sub, entry);
|
||||
status.setRead(read);
|
||||
status.setMarkable(!read);
|
||||
} else {
|
||||
status.setMarkable(true);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
private void fetchTags(User user, List<FeedEntryStatus> statuses) {
|
||||
Map<Long, List<FeedEntryTag>> tagsByEntryIds = feedEntryTagDAO.findByEntries(user,
|
||||
statuses.stream().map(FeedEntryStatus::getEntry).toList());
|
||||
for (FeedEntryStatus status : statuses) {
|
||||
List<FeedEntryTag> tags = tagsByEntryIds.get(status.getEntry().getId());
|
||||
status.setTags(tags == null ? List.of() : tags);
|
||||
}
|
||||
}
|
||||
private void fetchTags(User user, List<FeedEntryStatus> statuses) {
|
||||
Map<Long, List<FeedEntryTag>> tagsByEntryIds =
|
||||
feedEntryTagDAO.findByEntries(
|
||||
user, statuses.stream().map(FeedEntryStatus::getEntry).toList());
|
||||
for (FeedEntryStatus status : statuses) {
|
||||
List<FeedEntryTag> tags = tagsByEntryIds.get(status.getEntry().getId());
|
||||
status.setTags(tags == null ? List.of() : tags);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FeedEntryStatus> findStarred(User user, Instant newerThan, int offset, int limit, ReadingOrder order,
|
||||
boolean includeContent) {
|
||||
JPAQuery<FeedEntryStatus> query = query().selectFrom(STATUS).where(STATUS.user.eq(user), STATUS.starred.isTrue());
|
||||
if (includeContent) {
|
||||
query.join(STATUS.entry).fetchJoin();
|
||||
query.join(STATUS.entry.content).fetchJoin();
|
||||
}
|
||||
public List<FeedEntryStatus> findStarred(
|
||||
User user,
|
||||
List<FeedEntryKeyword> keywords,
|
||||
Instant newerThan,
|
||||
int offset,
|
||||
int limit,
|
||||
ReadingOrder order,
|
||||
boolean includeContent) {
|
||||
JPAQuery<FeedEntryStatus> query =
|
||||
query().selectFrom(STATUS).where(STATUS.user.eq(user), STATUS.starred.isTrue());
|
||||
if (includeContent || CollectionUtils.isNotEmpty(keywords)) {
|
||||
query.join(STATUS.entry).fetchJoin();
|
||||
query.join(STATUS.entry.content, CONTENT).fetchJoin();
|
||||
}
|
||||
|
||||
if (newerThan != null) {
|
||||
query.where(STATUS.entryInserted.gt(newerThan));
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||
applyKeywordsFilter(query, keywords);
|
||||
}
|
||||
|
||||
if (order == ReadingOrder.ASC) {
|
||||
query.orderBy(STATUS.entryPublished.asc(), STATUS.id.asc());
|
||||
} else {
|
||||
query.orderBy(STATUS.entryPublished.desc(), STATUS.id.desc());
|
||||
}
|
||||
if (newerThan != null) {
|
||||
query.where(STATUS.entryInserted.gt(newerThan));
|
||||
}
|
||||
|
||||
if (offset > -1) {
|
||||
query.offset(offset);
|
||||
}
|
||||
if (order == ReadingOrder.ASC) {
|
||||
query.orderBy(STATUS.entryPublished.asc(), STATUS.id.asc());
|
||||
} else {
|
||||
query.orderBy(STATUS.entryPublished.desc(), STATUS.id.desc());
|
||||
}
|
||||
|
||||
if (limit > -1) {
|
||||
query.limit(limit);
|
||||
}
|
||||
if (offset > -1) {
|
||||
query.offset(offset);
|
||||
}
|
||||
|
||||
setTimeout(query, config.database().queryTimeout());
|
||||
if (limit > -1) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
List<FeedEntryStatus> statuses = query.fetch();
|
||||
statuses.forEach(s -> s.setMarkable(true));
|
||||
if (includeContent) {
|
||||
fetchTags(user, statuses);
|
||||
}
|
||||
setTimeout(query, config.database().queryTimeout());
|
||||
|
||||
return statuses;
|
||||
}
|
||||
List<FeedEntryStatus> statuses = query.fetch();
|
||||
statuses.forEach(s -> s.setMarkable(true));
|
||||
if (includeContent) {
|
||||
fetchTags(user, statuses);
|
||||
}
|
||||
|
||||
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly,
|
||||
List<FeedEntryKeyword> keywords, Instant newerThan, int offset, int limit, ReadingOrder order, boolean includeContent,
|
||||
String tag, Long minEntryId, Long maxEntryId) {
|
||||
Map<Long, List<FeedSubscription>> subsByFeedId = subs.stream().collect(Collectors.groupingBy(s -> s.getFeed().getId()));
|
||||
return statuses;
|
||||
}
|
||||
|
||||
JPAQuery<Tuple> query = query().select(ENTRY, STATUS).from(ENTRY);
|
||||
query.leftJoin(ENTRY.statuses, STATUS).on(STATUS.subscription.in(subs));
|
||||
query.where(ENTRY.feed.id.in(subsByFeedId.keySet()));
|
||||
public List<FeedEntryStatus> findBySubscriptions(
|
||||
User user,
|
||||
List<FeedSubscription> subs,
|
||||
boolean unreadOnly,
|
||||
List<FeedEntryKeyword> keywords,
|
||||
Instant newerThan,
|
||||
int offset,
|
||||
int limit,
|
||||
ReadingOrder order,
|
||||
boolean includeContent,
|
||||
String tag,
|
||||
Long minEntryId,
|
||||
Long maxEntryId) {
|
||||
Map<Long, List<FeedSubscription>> subsByFeedId =
|
||||
subs.stream().collect(Collectors.groupingBy(s -> s.getFeed().getId()));
|
||||
|
||||
if (includeContent || CollectionUtils.isNotEmpty(keywords)) {
|
||||
query.join(ENTRY.content, CONTENT).fetchJoin();
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||
for (FeedEntryKeyword keyword : keywords) {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(CONTENT.content.containsIgnoreCase(keyword.keyword()));
|
||||
or.or(CONTENT.title.containsIgnoreCase(keyword.keyword()));
|
||||
if (keyword.mode() == Mode.EXCLUDE) {
|
||||
or.not();
|
||||
}
|
||||
query.where(or);
|
||||
}
|
||||
}
|
||||
JPAQuery<Tuple> query = query().select(ENTRY, STATUS).from(ENTRY);
|
||||
query.leftJoin(ENTRY.statuses, STATUS).on(STATUS.subscription.in(subs));
|
||||
query.where(ENTRY.feed.id.in(subsByFeedId.keySet()));
|
||||
|
||||
if (unreadOnly && tag == null) {
|
||||
query.where(buildUnreadPredicate());
|
||||
}
|
||||
if (includeContent || CollectionUtils.isNotEmpty(keywords)) {
|
||||
query.join(ENTRY.content, CONTENT).fetchJoin();
|
||||
}
|
||||
|
||||
if (tag != null) {
|
||||
BooleanBuilder and = new BooleanBuilder();
|
||||
and.and(TAG.user.id.eq(user.getId()));
|
||||
and.and(TAG.name.eq(tag));
|
||||
query.join(ENTRY.tags, TAG).on(and);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||
applyKeywordsFilter(query, keywords);
|
||||
}
|
||||
|
||||
if (newerThan != null) {
|
||||
query.where(ENTRY.inserted.goe(newerThan));
|
||||
}
|
||||
if (unreadOnly && tag == null) {
|
||||
query.where(buildUnreadPredicate());
|
||||
}
|
||||
|
||||
if (minEntryId != null) {
|
||||
query.where(ENTRY.id.gt(minEntryId));
|
||||
}
|
||||
if (tag != null) {
|
||||
BooleanBuilder and = new BooleanBuilder();
|
||||
and.and(TAG.user.id.eq(user.getId()));
|
||||
and.and(TAG.name.eq(tag));
|
||||
query.join(ENTRY.tags, TAG).on(and);
|
||||
}
|
||||
|
||||
if (maxEntryId != null) {
|
||||
query.where(ENTRY.id.lt(maxEntryId));
|
||||
}
|
||||
if (newerThan != null) {
|
||||
query.where(ENTRY.inserted.goe(newerThan));
|
||||
}
|
||||
|
||||
if (order != null) {
|
||||
if (order == ReadingOrder.ASC) {
|
||||
query.orderBy(ENTRY.published.asc(), ENTRY.id.asc());
|
||||
} else {
|
||||
query.orderBy(ENTRY.published.desc(), ENTRY.id.desc());
|
||||
}
|
||||
}
|
||||
if (minEntryId != null) {
|
||||
query.where(ENTRY.id.gt(minEntryId));
|
||||
}
|
||||
|
||||
if (offset > -1) {
|
||||
query.offset(offset);
|
||||
}
|
||||
if (maxEntryId != null) {
|
||||
query.where(ENTRY.id.lt(maxEntryId));
|
||||
}
|
||||
|
||||
if (limit > -1) {
|
||||
query.limit(limit);
|
||||
}
|
||||
if (order != null) {
|
||||
if (order == ReadingOrder.ASC) {
|
||||
query.orderBy(ENTRY.published.asc(), ENTRY.id.asc());
|
||||
} else {
|
||||
query.orderBy(ENTRY.published.desc(), ENTRY.id.desc());
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(query, config.database().queryTimeout());
|
||||
if (offset > -1) {
|
||||
query.offset(offset);
|
||||
}
|
||||
|
||||
List<FeedEntryStatus> statuses = new ArrayList<>();
|
||||
List<Tuple> tuples = query.fetch();
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntry e = tuple.get(ENTRY);
|
||||
FeedEntryStatus s = tuple.get(STATUS);
|
||||
for (FeedSubscription sub : subsByFeedId.get(e.getFeed().getId())) {
|
||||
statuses.add(handleStatus(user, s, sub, e));
|
||||
}
|
||||
}
|
||||
if (limit > -1) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
if (includeContent) {
|
||||
fetchTags(user, statuses);
|
||||
}
|
||||
setTimeout(query, config.database().queryTimeout());
|
||||
|
||||
return statuses;
|
||||
}
|
||||
List<FeedEntryStatus> statuses = new ArrayList<>();
|
||||
List<Tuple> tuples = query.fetch();
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntry e = tuple.get(ENTRY);
|
||||
FeedEntryStatus s = tuple.get(STATUS);
|
||||
for (FeedSubscription sub : subsByFeedId.get(e.getFeed().getId())) {
|
||||
statuses.add(handleStatus(user, s, sub, e));
|
||||
}
|
||||
}
|
||||
|
||||
public UnreadCount getUnreadCount(FeedSubscription sub) {
|
||||
JPAQuery<Tuple> query = query().select(ENTRY.count(), ENTRY.published.max())
|
||||
.from(ENTRY)
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(sub))
|
||||
.where(ENTRY.feed.eq(sub.getFeed()))
|
||||
.where(buildUnreadPredicate());
|
||||
if (includeContent) {
|
||||
fetchTags(user, statuses);
|
||||
}
|
||||
|
||||
Tuple tuple = query.fetchOne();
|
||||
Long count = tuple.get(ENTRY.count());
|
||||
Instant published = tuple.get(ENTRY.published.max());
|
||||
return new UnreadCount(sub.getId(), count == null ? 0 : count, published);
|
||||
}
|
||||
return statuses;
|
||||
}
|
||||
|
||||
private BooleanBuilder buildUnreadPredicate() {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(STATUS.read.isNull());
|
||||
or.or(STATUS.read.isFalse());
|
||||
private void applyKeywordsFilter(JPAQuery<?> query, List<FeedEntryKeyword> keywords) {
|
||||
for (FeedEntryKeyword keyword : keywords) {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(CONTENT.content.containsIgnoreCase(keyword.keyword()));
|
||||
or.or(CONTENT.title.containsIgnoreCase(keyword.keyword()));
|
||||
if (keyword.mode() == Mode.EXCLUDE) {
|
||||
or.not();
|
||||
}
|
||||
query.where(or);
|
||||
}
|
||||
}
|
||||
|
||||
Instant statusesInstantThreshold = config.database().cleanup().statusesInstantThreshold();
|
||||
if (statusesInstantThreshold != null) {
|
||||
return or.and(ENTRY.published.goe(statusesInstantThreshold));
|
||||
} else {
|
||||
return or;
|
||||
}
|
||||
}
|
||||
public UnreadCount getUnreadCount(FeedSubscription sub) {
|
||||
JPAQuery<Tuple> query =
|
||||
query().select(ENTRY.count(), ENTRY.published.max())
|
||||
.from(ENTRY)
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(sub))
|
||||
.where(ENTRY.feed.eq(sub.getFeed()))
|
||||
.where(buildUnreadPredicate());
|
||||
|
||||
public long deleteOldStatuses(Instant olderThan, int limit) {
|
||||
List<Long> ids = query().select(STATUS.id)
|
||||
.from(STATUS)
|
||||
.where(STATUS.entryInserted.lt(olderThan), STATUS.starred.isFalse())
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
return deleteQuery(STATUS).where(STATUS.id.in(ids)).execute();
|
||||
}
|
||||
Tuple tuple = query.fetchOne();
|
||||
Long count = tuple.get(ENTRY.count());
|
||||
Instant published = tuple.get(ENTRY.published.max());
|
||||
return new UnreadCount(sub.getId(), count == null ? 0 : count, published);
|
||||
}
|
||||
|
||||
public long autoMarkAsRead(int limit) {
|
||||
Instant now = Instant.now();
|
||||
private BooleanBuilder buildUnreadPredicate() {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(STATUS.read.isNull());
|
||||
or.or(STATUS.read.isFalse());
|
||||
|
||||
BooleanBuilder where = new BooleanBuilder();
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.isNotNull());
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.gt(0));
|
||||
Instant statusesInstantThreshold = config.database().cleanup().statusesInstantThreshold();
|
||||
if (statusesInstantThreshold != null) {
|
||||
return or.and(ENTRY.published.goe(statusesInstantThreshold));
|
||||
} else {
|
||||
return or;
|
||||
}
|
||||
}
|
||||
|
||||
NumberExpression<Integer> daysDiff = Expressions.numberTemplate(Integer.class, "TIMESTAMPDIFF(DAY, {0}, {1})", ENTRY.published,
|
||||
now);
|
||||
where.and(daysDiff.goe(SUBSCRIPTION.autoMarkAsReadAfterDays));
|
||||
public long deleteOldStatuses(Instant olderThan, int limit) {
|
||||
List<Long> ids =
|
||||
query().select(STATUS.id)
|
||||
.from(STATUS)
|
||||
.where(STATUS.entryInserted.lt(olderThan), STATUS.starred.isFalse())
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
return deleteQuery(STATUS).where(STATUS.id.in(ids)).execute();
|
||||
}
|
||||
|
||||
where.and(buildUnreadPredicate());
|
||||
public long autoMarkAsRead(int limit) {
|
||||
Instant now = Instant.now();
|
||||
|
||||
List<Tuple> tuples = query().select(ENTRY, STATUS, SUBSCRIPTION)
|
||||
.from(ENTRY)
|
||||
.join(ENTRY.feed, FEED)
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(SUBSCRIPTION))
|
||||
.where(where)
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
BooleanBuilder where = new BooleanBuilder();
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.isNotNull());
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.gt(0));
|
||||
|
||||
long updated = 0;
|
||||
NumberExpression<Integer> daysDiff =
|
||||
Expressions.numberTemplate(
|
||||
Integer.class, "TIMESTAMPDIFF(DAY, {0}, {1})", ENTRY.published, now);
|
||||
where.and(daysDiff.goe(SUBSCRIPTION.autoMarkAsReadAfterDays));
|
||||
|
||||
// Update existing statuses
|
||||
List<Long> statusIdsToUpdate = tuples.stream()
|
||||
.map(t -> t.get(STATUS))
|
||||
.filter(s -> s != null && s.getId() != null)
|
||||
.map(FeedEntryStatus::getId)
|
||||
.distinct()
|
||||
.toList();
|
||||
where.and(buildUnreadPredicate());
|
||||
|
||||
if (!statusIdsToUpdate.isEmpty()) {
|
||||
updated += updateQuery(STATUS).where(STATUS.id.in(statusIdsToUpdate)).set(STATUS.read, true).execute();
|
||||
}
|
||||
List<Tuple> tuples =
|
||||
query().select(ENTRY, STATUS, SUBSCRIPTION)
|
||||
.from(ENTRY)
|
||||
.join(ENTRY.feed, FEED)
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(SUBSCRIPTION))
|
||||
.where(where)
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
|
||||
// Insert new statuses for entries without existing status
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntryStatus status = tuple.get(STATUS);
|
||||
if (status == null || status.getId() == null) {
|
||||
FeedEntry entry = tuple.get(ENTRY);
|
||||
FeedSubscription sub = tuple.get(SUBSCRIPTION);
|
||||
FeedEntryStatus newStatus = new FeedEntryStatus(sub.getUser(), sub, entry);
|
||||
newStatus.setRead(true);
|
||||
persist(newStatus);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
long updated = 0;
|
||||
|
||||
return updated;
|
||||
}
|
||||
// Update existing statuses
|
||||
List<Long> statusIdsToUpdate =
|
||||
tuples.stream()
|
||||
.map(t -> t.get(STATUS))
|
||||
.filter(s -> s != null && s.getId() != null)
|
||||
.map(FeedEntryStatus::getId)
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
if (!statusIdsToUpdate.isEmpty()) {
|
||||
updated +=
|
||||
updateQuery(STATUS)
|
||||
.where(STATUS.id.in(statusIdsToUpdate))
|
||||
.set(STATUS.read, true)
|
||||
.execute();
|
||||
}
|
||||
|
||||
// Insert new statuses for entries without existing status
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntryStatus status = tuple.get(STATUS);
|
||||
if (status == null || status.getId() == null) {
|
||||
FeedEntry entry = tuple.get(ENTRY);
|
||||
FeedSubscription sub = tuple.get(SUBSCRIPTION);
|
||||
FeedEntryStatus newStatus = new FeedEntryStatus(sub.getUser(), sub, entry);
|
||||
newStatus.setRead(true);
|
||||
persist(newStatus);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryTag;
|
||||
import com.commafeed.backend.model.QFeedEntryTag;
|
||||
import com.commafeed.backend.model.User;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> {
|
||||
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
|
||||
public FeedEntryTagDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntryTag.class);
|
||||
}
|
||||
public FeedEntryTagDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntryTag.class);
|
||||
}
|
||||
|
||||
public List<String> findByUser(User user) {
|
||||
return query().selectDistinct(TAG.name).from(TAG).where(TAG.user.eq(user)).fetch();
|
||||
}
|
||||
public List<String> findByUser(User user) {
|
||||
return query().selectDistinct(TAG.name).from(TAG).where(TAG.user.eq(user)).fetch();
|
||||
}
|
||||
|
||||
public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) {
|
||||
return query().selectFrom(TAG).where(TAG.user.eq(user), TAG.entry.eq(entry)).fetch();
|
||||
}
|
||||
public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) {
|
||||
return query().selectFrom(TAG).where(TAG.user.eq(user), TAG.entry.eq(entry)).fetch();
|
||||
}
|
||||
|
||||
public Map<Long, List<FeedEntryTag>> findByEntries(User user, List<FeedEntry> entries) {
|
||||
return query().selectFrom(TAG)
|
||||
.where(TAG.user.eq(user), TAG.entry.in(entries))
|
||||
.fetch()
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(t -> t.getEntry().getId()));
|
||||
}
|
||||
public Map<Long, List<FeedEntryTag>> findByEntries(User user, List<FeedEntry> entries) {
|
||||
return query()
|
||||
.selectFrom(TAG)
|
||||
.where(TAG.user.eq(user), TAG.entry.in(entries))
|
||||
.fetch()
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(t -> t.getEntry().getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,5 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||
import org.hibernate.event.spi.EventType;
|
||||
import org.hibernate.event.spi.PostCommitInsertEventListener;
|
||||
import org.hibernate.event.spi.PostInsertEvent;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
import com.commafeed.backend.model.AbstractModel;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
@@ -23,109 +8,136 @@ import com.commafeed.backend.model.Models;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.querydsl.jpa.JPQLQuery;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||
import org.hibernate.event.spi.EventType;
|
||||
import org.hibernate.event.spi.PostCommitInsertEventListener;
|
||||
import org.hibernate.event.spi.PostInsertEvent;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
@Singleton
|
||||
public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
||||
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
|
||||
private final EntityManager entityManager;
|
||||
private final EntityManager entityManager;
|
||||
|
||||
public FeedSubscriptionDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedSubscription.class);
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
public FeedSubscriptionDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedSubscription.class);
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
public void onPostCommitInsert(Consumer<FeedSubscription> consumer) {
|
||||
entityManager.unwrap(SharedSessionContractImplementor.class)
|
||||
.getFactory()
|
||||
.getServiceRegistry()
|
||||
.getService(EventListenerRegistry.class)
|
||||
.getEventListenerGroup(EventType.POST_COMMIT_INSERT)
|
||||
.appendListener(new PostCommitInsertEventListener() {
|
||||
@Override
|
||||
public void onPostInsert(PostInsertEvent event) {
|
||||
if (event.getEntity() instanceof FeedSubscription s) {
|
||||
consumer.accept(s);
|
||||
}
|
||||
}
|
||||
public void onPostCommitInsert(Consumer<FeedSubscription> consumer) {
|
||||
entityManager
|
||||
.unwrap(SharedSessionContractImplementor.class)
|
||||
.getFactory()
|
||||
.getServiceRegistry()
|
||||
.getService(EventListenerRegistry.class)
|
||||
.getEventListenerGroup(EventType.POST_COMMIT_INSERT)
|
||||
.appendListener(
|
||||
new PostCommitInsertEventListener() {
|
||||
@Override
|
||||
public void onPostInsert(PostInsertEvent event) {
|
||||
if (event.getEntity() instanceof FeedSubscription s) {
|
||||
consumer.accept(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresPostCommitHandling(EntityPersister persister) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean requiresPostCommitHandling(EntityPersister persister) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostInsertCommitFailed(PostInsertEvent event) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onPostInsertCommitFailed(PostInsertEvent event) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public FeedSubscription findById(User user, Long id) {
|
||||
List<FeedSubscription> subs = query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user), SUBSCRIPTION.id.eq(id))
|
||||
.leftJoin(SUBSCRIPTION.feed)
|
||||
.fetchJoin()
|
||||
.leftJoin(SUBSCRIPTION.category)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
FeedSubscription sub = subs.stream().findFirst().orElse(null);
|
||||
return initRelations(sub);
|
||||
}
|
||||
public FeedSubscription findById(User user, Long id) {
|
||||
List<FeedSubscription> subs =
|
||||
query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user), SUBSCRIPTION.id.eq(id))
|
||||
.leftJoin(SUBSCRIPTION.feed)
|
||||
.fetchJoin()
|
||||
.leftJoin(SUBSCRIPTION.category)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
FeedSubscription sub = subs.stream().findFirst().orElse(null);
|
||||
return initRelations(sub);
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByFeed(Feed feed) {
|
||||
return query().selectFrom(SUBSCRIPTION).where(SUBSCRIPTION.feed.eq(feed)).fetch();
|
||||
}
|
||||
public List<FeedSubscription> findByFeed(Feed feed) {
|
||||
return query().selectFrom(SUBSCRIPTION).where(SUBSCRIPTION.feed.eq(feed)).fetch();
|
||||
}
|
||||
|
||||
public FeedSubscription findByFeed(User user, Feed feed) {
|
||||
List<FeedSubscription> subs = query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user), SUBSCRIPTION.feed.eq(feed))
|
||||
.fetch();
|
||||
FeedSubscription sub = subs.stream().findFirst().orElse(null);
|
||||
return initRelations(sub);
|
||||
}
|
||||
public FeedSubscription findByFeed(User user, Feed feed) {
|
||||
List<FeedSubscription> subs =
|
||||
query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user), SUBSCRIPTION.feed.eq(feed))
|
||||
.fetch();
|
||||
FeedSubscription sub = subs.stream().findFirst().orElse(null);
|
||||
return initRelations(sub);
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findAll(User user) {
|
||||
List<FeedSubscription> subs = query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user))
|
||||
.leftJoin(SUBSCRIPTION.feed)
|
||||
.fetchJoin()
|
||||
.leftJoin(SUBSCRIPTION.category)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
return initRelations(subs);
|
||||
}
|
||||
public List<FeedSubscription> findAll(User user) {
|
||||
List<FeedSubscription> subs =
|
||||
query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user))
|
||||
.leftJoin(SUBSCRIPTION.feed)
|
||||
.fetchJoin()
|
||||
.leftJoin(SUBSCRIPTION.category)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
return initRelations(subs);
|
||||
}
|
||||
|
||||
public Long count(User user) {
|
||||
return query().select(SUBSCRIPTION.count()).from(SUBSCRIPTION).where(SUBSCRIPTION.user.eq(user)).fetchOne();
|
||||
}
|
||||
public Long count(User user) {
|
||||
return query().select(SUBSCRIPTION.count())
|
||||
.from(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByCategory(User user, FeedCategory category) {
|
||||
JPQLQuery<FeedSubscription> query = query().selectFrom(SUBSCRIPTION).where(SUBSCRIPTION.user.eq(user));
|
||||
if (category == null) {
|
||||
query.where(SUBSCRIPTION.category.isNull());
|
||||
} else {
|
||||
query.where(SUBSCRIPTION.category.eq(category));
|
||||
}
|
||||
return initRelations(query.fetch());
|
||||
}
|
||||
public List<FeedSubscription> findByCategory(User user, FeedCategory category) {
|
||||
JPQLQuery<FeedSubscription> query =
|
||||
query().selectFrom(SUBSCRIPTION).where(SUBSCRIPTION.user.eq(user));
|
||||
if (category == null) {
|
||||
query.where(SUBSCRIPTION.category.isNull());
|
||||
} else {
|
||||
query.where(SUBSCRIPTION.category.eq(category));
|
||||
}
|
||||
return initRelations(query.fetch());
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByCategories(User user, List<FeedCategory> categories) {
|
||||
Set<Long> categoryIds = categories.stream().map(AbstractModel::getId).collect(Collectors.toSet());
|
||||
return findAll(user).stream().filter(s -> s.getCategory() != null && categoryIds.contains(s.getCategory().getId())).toList();
|
||||
}
|
||||
public List<FeedSubscription> findByCategories(User user, List<FeedCategory> categories) {
|
||||
Set<Long> categoryIds =
|
||||
categories.stream().map(AbstractModel::getId).collect(Collectors.toSet());
|
||||
return findAll(user).stream()
|
||||
.filter(
|
||||
s ->
|
||||
s.getCategory() != null
|
||||
&& categoryIds.contains(s.getCategory().getId()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<FeedSubscription> initRelations(List<FeedSubscription> list) {
|
||||
list.forEach(this::initRelations);
|
||||
return list;
|
||||
}
|
||||
private List<FeedSubscription> initRelations(List<FeedSubscription> list) {
|
||||
list.forEach(this::initRelations);
|
||||
return list;
|
||||
}
|
||||
|
||||
private FeedSubscription initRelations(FeedSubscription sub) {
|
||||
if (sub != null) {
|
||||
Models.initialize(sub.getFeed());
|
||||
Models.initialize(sub.getCategory());
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
private FeedSubscription initRelations(FeedSubscription sub) {
|
||||
if (sub != null) {
|
||||
Models.initialize(sub.getFeed());
|
||||
Models.initialize(sub.getCategory());
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,61 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.hibernate.jpa.SpecHints;
|
||||
|
||||
import com.commafeed.backend.model.AbstractModel;
|
||||
import com.querydsl.core.types.EntityPath;
|
||||
import com.querydsl.jpa.impl.JPADeleteClause;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import com.querydsl.jpa.impl.JPAUpdateClause;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.hibernate.jpa.SpecHints;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public abstract class GenericDAO<T extends AbstractModel> {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
private final Class<T> entityClass;
|
||||
private final EntityManager entityManager;
|
||||
private final Class<T> entityClass;
|
||||
|
||||
protected JPAQueryFactory query() {
|
||||
return new JPAQueryFactory(entityManager);
|
||||
}
|
||||
protected JPAQueryFactory query() {
|
||||
return new JPAQueryFactory(entityManager);
|
||||
}
|
||||
|
||||
protected JPAUpdateClause updateQuery(EntityPath<T> entityPath) {
|
||||
return new JPAUpdateClause(entityManager, entityPath);
|
||||
}
|
||||
protected JPAUpdateClause updateQuery(EntityPath<T> entityPath) {
|
||||
return new JPAUpdateClause(entityManager, entityPath);
|
||||
}
|
||||
|
||||
protected JPADeleteClause deleteQuery(EntityPath<T> entityPath) {
|
||||
return new JPADeleteClause(entityManager, entityPath);
|
||||
}
|
||||
protected JPADeleteClause deleteQuery(EntityPath<T> entityPath) {
|
||||
return new JPADeleteClause(entityManager, entityPath);
|
||||
}
|
||||
|
||||
public void persist(T model) {
|
||||
entityManager.persist(model);
|
||||
}
|
||||
public void persist(T model) {
|
||||
entityManager.persist(model);
|
||||
}
|
||||
|
||||
public T merge(T model) {
|
||||
return entityManager.merge(model);
|
||||
}
|
||||
public T merge(T model) {
|
||||
return entityManager.merge(model);
|
||||
}
|
||||
|
||||
public T findById(Long id) {
|
||||
return entityManager.find(entityClass, id);
|
||||
}
|
||||
public T findById(Long id) {
|
||||
return entityManager.find(entityClass, id);
|
||||
}
|
||||
|
||||
public void delete(T object) {
|
||||
if (object != null) {
|
||||
entityManager.remove(object);
|
||||
}
|
||||
}
|
||||
public void delete(T object) {
|
||||
if (object != null) {
|
||||
entityManager.remove(object);
|
||||
}
|
||||
}
|
||||
|
||||
public int delete(Collection<T> objects) {
|
||||
objects.forEach(this::delete);
|
||||
return objects.size();
|
||||
}
|
||||
|
||||
protected void setTimeout(JPAQuery<?> query, Duration timeout) {
|
||||
if (!timeout.isZero()) {
|
||||
query.setHint(SpecHints.HINT_SPEC_QUERY_TIMEOUT, Math.toIntExact(timeout.toMillis()));
|
||||
}
|
||||
}
|
||||
public int delete(Collection<T> objects) {
|
||||
objects.forEach(this::delete);
|
||||
return objects.size();
|
||||
}
|
||||
|
||||
protected void setTimeout(JPAQuery<?> query, Duration timeout) {
|
||||
if (!timeout.isZero()) {
|
||||
query.setHint(SpecHints.HINT_SPEC_QUERY_TIMEOUT, Math.toIntExact(timeout.toMillis()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,17 @@ import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntryStatus;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class Predicates {
|
||||
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
|
||||
public static BooleanExpression isNotStarred(QFeedEntry entry) {
|
||||
return JPAExpressions.selectOne().from(STATUS).where(STATUS.entry.eq(entry).and(STATUS.starred.isTrue())).notExists();
|
||||
}
|
||||
public static BooleanExpression isNotStarred(QFeedEntry entry) {
|
||||
return JPAExpressions.selectOne()
|
||||
.from(STATUS)
|
||||
.where(STATUS.entry.eq(entry).and(STATUS.starred.isTrue()))
|
||||
.notExists();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import io.quarkus.narayana.jta.QuarkusTransaction;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@Singleton
|
||||
public class UnitOfWork {
|
||||
|
||||
public void run(Runnable runnable) {
|
||||
QuarkusTransaction.joiningExisting().run(runnable);
|
||||
}
|
||||
public void run(Runnable runnable) {
|
||||
QuarkusTransaction.joiningExisting().run(runnable);
|
||||
}
|
||||
|
||||
public <T> T call(Callable<T> callable) {
|
||||
return QuarkusTransaction.joiningExisting().call(callable);
|
||||
}
|
||||
public <T> T call(Callable<T> callable) {
|
||||
return QuarkusTransaction.joiningExisting().call(callable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.commafeed.backend.model.User;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
@Singleton
|
||||
public class UserDAO extends GenericDAO<User> {
|
||||
|
||||
private static final QUser USER = QUser.user;
|
||||
private static final QUser USER = QUser.user;
|
||||
|
||||
public UserDAO(EntityManager entityManager) {
|
||||
super(entityManager, User.class);
|
||||
}
|
||||
public UserDAO(EntityManager entityManager) {
|
||||
super(entityManager, User.class);
|
||||
}
|
||||
|
||||
public User findByName(String name) {
|
||||
return query().selectFrom(USER).where(USER.name.equalsIgnoreCase(name)).fetchOne();
|
||||
}
|
||||
public User findByName(String name) {
|
||||
return query().selectFrom(USER).where(USER.name.equalsIgnoreCase(name)).fetchOne();
|
||||
}
|
||||
|
||||
public User findByApiKey(String key) {
|
||||
return query().selectFrom(USER).where(USER.apiKey.equalsIgnoreCase(key)).fetchOne();
|
||||
}
|
||||
public User findByApiKey(String key) {
|
||||
return query().selectFrom(USER).where(USER.apiKey.equalsIgnoreCase(key)).fetchOne();
|
||||
}
|
||||
|
||||
public User findByEmail(String email) {
|
||||
return query().selectFrom(USER).where(USER.email.equalsIgnoreCase(email)).fetchOne();
|
||||
}
|
||||
public User findByEmail(String email) {
|
||||
return query().selectFrom(USER).where(USER.email.equalsIgnoreCase(email)).fetchOne();
|
||||
}
|
||||
|
||||
public long count() {
|
||||
return query().select(USER.count()).from(USER).fetchOne();
|
||||
}
|
||||
public long count() {
|
||||
return query().select(USER.count()).from(USER).fetchOne();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.QUserRole;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserRole;
|
||||
import com.commafeed.backend.model.UserRole.Role;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class UserRoleDAO extends GenericDAO<UserRole> {
|
||||
|
||||
private static final QUserRole ROLE = QUserRole.userRole;
|
||||
private static final QUserRole ROLE = QUserRole.userRole;
|
||||
|
||||
public UserRoleDAO(EntityManager entityManager) {
|
||||
super(entityManager, UserRole.class);
|
||||
}
|
||||
public UserRoleDAO(EntityManager entityManager) {
|
||||
super(entityManager, UserRole.class);
|
||||
}
|
||||
|
||||
public List<UserRole> findAll() {
|
||||
return query().selectFrom(ROLE).leftJoin(ROLE.user).fetchJoin().distinct().fetch();
|
||||
}
|
||||
public List<UserRole> findAll() {
|
||||
return query().selectFrom(ROLE).leftJoin(ROLE.user).fetchJoin().distinct().fetch();
|
||||
}
|
||||
|
||||
public List<UserRole> findAll(User user) {
|
||||
return query().selectFrom(ROLE).where(ROLE.user.eq(user)).distinct().fetch();
|
||||
}
|
||||
public List<UserRole> findAll(User user) {
|
||||
return query().selectFrom(ROLE).where(ROLE.user.eq(user)).distinct().fetch();
|
||||
}
|
||||
|
||||
public Set<Role> findRoles(User user) {
|
||||
return findAll(user).stream().map(UserRole::getRole).collect(Collectors.toSet());
|
||||
}
|
||||
public Set<Role> findRoles(User user) {
|
||||
return findAll(user).stream().map(UserRole::getRole).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public long countAdmins() {
|
||||
return query().select(ROLE.count()).from(ROLE).where(ROLE.role.eq(Role.ADMIN)).fetchOne();
|
||||
}
|
||||
public long countAdmins() {
|
||||
return query().select(ROLE.count()).from(ROLE).where(ROLE.role.eq(Role.ADMIN)).fetchOne();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.QUserSettings;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
@Singleton
|
||||
public class UserSettingsDAO extends GenericDAO<UserSettings> {
|
||||
|
||||
private static final QUserSettings SETTINGS = QUserSettings.userSettings;
|
||||
private static final QUserSettings SETTINGS = QUserSettings.userSettings;
|
||||
|
||||
public UserSettingsDAO(EntityManager entityManager) {
|
||||
super(entityManager, UserSettings.class);
|
||||
}
|
||||
public UserSettingsDAO(EntityManager entityManager) {
|
||||
super(entityManager, UserSettings.class);
|
||||
}
|
||||
|
||||
public UserSettings findByUser(User user) {
|
||||
return query().selectFrom(SETTINGS).where(SETTINGS.user.eq(user)).fetchFirst();
|
||||
}
|
||||
public UserSettings findByUser(User user) {
|
||||
return query().selectFrom(SETTINGS).where(SETTINGS.user.eq(user)).fetchFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractFaviconFetcher {
|
||||
|
||||
private static final List<String> ICON_MIMETYPE_BLACKLIST = Arrays.asList("application/xml", "text/html");
|
||||
private static final long MIN_ICON_LENGTH = 100;
|
||||
private static final long MAX_ICON_LENGTH = 100000;
|
||||
|
||||
public abstract Favicon fetch(Feed feed);
|
||||
|
||||
protected boolean isValidIconResponse(byte[] content, String contentType) {
|
||||
if (content == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long length = content.length;
|
||||
|
||||
if (StringUtils.isNotBlank(contentType)) {
|
||||
contentType = contentType.split(";")[0];
|
||||
}
|
||||
|
||||
if (ICON_MIMETYPE_BLACKLIST.contains(contentType)) {
|
||||
log.debug("Content-Type {} is blacklisted", contentType);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length < MIN_ICON_LENGTH) {
|
||||
log.debug("Length {} below MIN_ICON_LENGTH {}", length, MIN_ICON_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length > MAX_ICON_LENGTH) {
|
||||
log.debug("Length {} greater than MAX_ICON_LENGTH {}", length, MAX_ICON_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.Urls;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Inspired/Ported from https://github.com/potatolondon/getfavicon
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
@Priority(Integer.MIN_VALUE)
|
||||
public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
Favicon icon = fetch(feed.getLink());
|
||||
if (icon == null) {
|
||||
icon = fetch(feed.getUrl());
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Favicon fetch(String url) {
|
||||
if (url == null) {
|
||||
log.debug("url is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
int doubleSlash = url.indexOf("//");
|
||||
if (doubleSlash == -1) {
|
||||
doubleSlash = 0;
|
||||
} else {
|
||||
doubleSlash += 2;
|
||||
}
|
||||
int firstSlash = url.indexOf('/', doubleSlash);
|
||||
if (firstSlash != -1) {
|
||||
url = url.substring(0, firstSlash);
|
||||
}
|
||||
|
||||
Favicon icon = getIconAtRoot(url);
|
||||
|
||||
if (icon == null) {
|
||||
icon = getIconInPage(url);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Favicon getIconAtRoot(String url) {
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
|
||||
try {
|
||||
url = Urls.removeTrailingSlash(url) + "/favicon.ico";
|
||||
log.debug("getting root icon at {}", url);
|
||||
HttpResult result = getter.get(url);
|
||||
bytes = result.content();
|
||||
contentType = result.contentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
return null;
|
||||
}
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
private Favicon getIconInPage(String url) {
|
||||
|
||||
Document doc;
|
||||
try {
|
||||
HttpResult result = getter.get(url);
|
||||
doc = Jsoup.parse(new String(result.content()), url);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve page to find icon", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
Elements icons = doc.select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]");
|
||||
|
||||
if (icons.isEmpty()) {
|
||||
log.debug("No icon found in page {}", url);
|
||||
return null;
|
||||
}
|
||||
|
||||
String href = icons.getFirst().attr("abs:href");
|
||||
if (StringUtils.isBlank(href)) {
|
||||
log.debug("No icon found in page");
|
||||
return null;
|
||||
}
|
||||
|
||||
log.debug("Found unconfirmed iconInPage at {}", href);
|
||||
|
||||
byte[] bytes;
|
||||
String contentType;
|
||||
try {
|
||||
HttpResult result = getter.get(href);
|
||||
bytes = result.content();
|
||||
contentType = result.contentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve icon found in page {}", href, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
log.debug("Invalid icon found for {}", href);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,67 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import org.apache.hc.core5.http.NameValuePair;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.core5.http.NameValuePair;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
public class FacebookFaviconFetcher extends AbstractFaviconFetcher {
|
||||
@Priority(3)
|
||||
public class FacebookFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
if (!url.toLowerCase().contains("www.facebook.com")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!url.toLowerCase().contains("www.facebook.com")) {
|
||||
return null;
|
||||
}
|
||||
String userName = extractUserName(url);
|
||||
if (userName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String userName = extractUserName(url);
|
||||
if (userName == null) {
|
||||
return null;
|
||||
}
|
||||
String iconUrl =
|
||||
String.format(
|
||||
"https://graph.facebook.com/%s/picture?type=square&height=16", userName);
|
||||
|
||||
String iconUrl = String.format("https://graph.facebook.com/%s/picture?type=square&height=16", userName);
|
||||
try {
|
||||
log.debug("Getting Facebook user's icon, {}", url);
|
||||
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
HttpResult iconResult = getter.get(iconUrl);
|
||||
return new Favicon(iconResult.content(), iconResult.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve Facebook icon", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
log.debug("Getting Facebook user's icon, {}", url);
|
||||
|
||||
HttpResult iconResult = getter.get(iconUrl);
|
||||
bytes = iconResult.content();
|
||||
contentType = iconResult.contentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve Facebook icon", e);
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
return null;
|
||||
}
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
private String extractUserName(String url) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
log.debug("could not parse url", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NameValuePair> params = new URIBuilder(uri).getQueryParams();
|
||||
return params.stream().filter(p -> "id".equals(p.getName())).map(NameValuePair::getValue).findFirst().orElse(null);
|
||||
}
|
||||
private String extractUserName(String url) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
log.debug("could not parse url", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NameValuePair> params = new URIBuilder(uri).getQueryParams();
|
||||
return params.stream()
|
||||
.filter(p -> "id".equals(p.getName()))
|
||||
.map(NameValuePair::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
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");
|
||||
|
||||
public Favicon(byte[] icon, String contentType) {
|
||||
this(icon, parseMediaType(contentType));
|
||||
}
|
||||
public Favicon(byte[] icon, String contentType) {
|
||||
this(icon, parseMediaType(contentType));
|
||||
}
|
||||
|
||||
private static MediaType parseMediaType(String contentType) {
|
||||
try {
|
||||
return MediaType.valueOf(contentType);
|
||||
} catch (Exception e) {
|
||||
log.debug("invalid content type '{}' received, returning default value", contentType, e);
|
||||
return DEFAULT_MEDIA_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static MediaType parseMediaType(String contentType) {
|
||||
try {
|
||||
return MediaType.valueOf(contentType);
|
||||
} catch (Exception e) {
|
||||
log.debug(
|
||||
"invalid content type '{}' received, returning default value", contentType, e);
|
||||
return DEFAULT_MEDIA_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
public interface FaviconFetcher {
|
||||
|
||||
Favicon fetch(Feed feed);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/** Fetch favicon from the url declared in the feed. */
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
@Priority(2)
|
||||
public class FeedFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getIconUrl();
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpResult result = getter.get(url);
|
||||
return new Favicon(result.content(), result.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve icon declared in the feed {}", url, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
/** Extracts favicon url from html page. */
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
@Priority(1)
|
||||
public class HtmlFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getLink();
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Document doc;
|
||||
try {
|
||||
HttpResult result = getter.get(url);
|
||||
doc = Jsoup.parse(new String(result.content()), url);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve page to find icon", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
Elements icons = doc.select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]");
|
||||
if (icons.isEmpty()) {
|
||||
log.debug("No icon found in page {}", url);
|
||||
return null;
|
||||
}
|
||||
|
||||
String href = icons.getFirst().attr("abs:href");
|
||||
if (StringUtils.isBlank(href)) {
|
||||
log.debug("No icon found in page");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpResult result = getter.get(href);
|
||||
return new Favicon(result.content(), result.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve icon found in page {}", href, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.net.URI;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/** Fetches favicon from root of the domain (e.g. https://example.com/favicon.ico) */
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
@Priority(0)
|
||||
public class RootFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getLink();
|
||||
if (url == null) {
|
||||
url = feed.getUrl();
|
||||
}
|
||||
|
||||
try {
|
||||
URI uri = URI.create(url.trim());
|
||||
String faviconUrl = "%s://%s/favicon.ico".formatted(uri.getScheme(), uri.getHost());
|
||||
|
||||
log.debug("getting root icon at {}", faviconUrl);
|
||||
HttpResult result = getter.get(faviconUrl);
|
||||
return new Favicon(result.content(), result.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user