Compare commits

...

232 Commits
4.0.0 ... 4.3.2

Author SHA1 Message Date
Athou
ff81749559 release 4.3.2 2024-03-04 22:28:03 +01:00
Athou
34db9baa7b add support for unix sockets (#1278) 2024-03-04 21:12:24 +01:00
Jérémie Panzer
541b5ef085 Merge pull request #1274 from Athou/dependabot/npm_and_yarn/commafeed-client/emotion/react-11.11.4
Bump @emotion/react from 11.11.3 to 11.11.4 in /commafeed-client
2024-03-04 10:34:00 +01:00
Jérémie Panzer
a974164ac8 Merge pull request #1275 from Athou/dependabot/npm_and_yarn/commafeed-client/typescript-eslint/eslint-plugin-7.1.0
Bump @typescript-eslint/eslint-plugin from 7.0.2 to 7.1.0 in /commafeed-client
2024-03-04 10:21:47 +01:00
Jérémie Panzer
9ca8358900 Merge pull request #1277 from Athou/dependabot/npm_and_yarn/commafeed-client/eslint-plugin-react-7.34.0
Bump eslint-plugin-react from 7.33.2 to 7.34.0 in /commafeed-client
2024-03-04 10:21:35 +01:00
dependabot[bot]
2bd7b46f11 Bump @emotion/react from 11.11.3 to 11.11.4 in /commafeed-client
Bumps [@emotion/react](https://github.com/emotion-js/emotion) from 11.11.3 to 11.11.4.
- [Release notes](https://github.com/emotion-js/emotion/releases)
- [Changelog](https://github.com/emotion-js/emotion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emotion-js/emotion/compare/@emotion/react@11.11.3...@emotion/react@11.11.4)

---
updated-dependencies:
- dependency-name: "@emotion/react"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 09:21:22 +00:00
Jérémie Panzer
eafe56a967 Merge pull request #1276 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.61
Bump @types/react from 18.2.58 to 18.2.61 in /commafeed-client
2024-03-04 10:21:20 +01:00
Jérémie Panzer
c18cd62d24 Merge pull request #1273 from Athou/dependabot/npm_and_yarn/commafeed-client/react-router-dom-6.22.2
Bump react-router-dom from 6.22.1 to 6.22.2 in /commafeed-client
2024-03-04 10:20:49 +01:00
Jérémie Panzer
180385d6ab Merge pull request #1272 from Athou/dependabot/npm_and_yarn/commafeed-client/fontsource/open-sans-5.0.25
Bump @fontsource/open-sans from 5.0.24 to 5.0.25 in /commafeed-client
2024-03-04 10:20:40 +01:00
dependabot[bot]
838eb8b725 Bump @fontsource/open-sans from 5.0.24 to 5.0.25 in /commafeed-client
Bumps [@fontsource/open-sans](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/open-sans) from 5.0.24 to 5.0.25.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/google/open-sans/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/open-sans)

---
updated-dependencies:
- dependency-name: "@fontsource/open-sans"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 09:15:32 +00:00
Jérémie Panzer
b7cb3ee3f7 Merge pull request #1271 from Athou/dependabot/npm_and_yarn/commafeed-client/mantine-f7798005fd
Bump the mantine group in /commafeed-client with 6 updates
2024-03-04 10:13:29 +01:00
dependabot[bot]
0a97e3f8f0 Bump eslint-plugin-react from 7.33.2 to 7.34.0 in /commafeed-client
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.33.2 to 7.34.0.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.33.2...v7.34.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 09:11:54 +00:00
dependabot[bot]
0229292b48 Bump @types/react from 18.2.58 to 18.2.61 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.58 to 18.2.61.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 09:11:22 +00:00
dependabot[bot]
c87a965ae1 Bump @typescript-eslint/eslint-plugin in /commafeed-client
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.0.2 to 7.1.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.1.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 09:10:58 +00:00
dependabot[bot]
baa4122793 Bump react-router-dom from 6.22.1 to 6.22.2 in /commafeed-client
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.1 to 6.22.2.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.2/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 09:09:20 +00:00
dependabot[bot]
e9a4cb3432 Bump the mantine group in /commafeed-client with 6 updates
Bumps the mantine group in /commafeed-client with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@mantine/core](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/core) | `7.5.3` | `7.6.1` |
| [@mantine/form](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/form) | `7.5.3` | `7.6.1` |
| [@mantine/hooks](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/hooks) | `7.5.3` | `7.6.1` |
| [@mantine/modals](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/modals) | `7.5.3` | `7.6.1` |
| [@mantine/notifications](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/notifications) | `7.5.3` | `7.6.1` |
| [@mantine/spotlight](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/spotlight) | `7.5.3` | `7.6.1` |


Updates `@mantine/core` from 7.5.3 to 7.6.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.6.1/packages/@mantine/core)

Updates `@mantine/form` from 7.5.3 to 7.6.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.6.1/packages/@mantine/form)

Updates `@mantine/hooks` from 7.5.3 to 7.6.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.6.1/packages/@mantine/hooks)

Updates `@mantine/modals` from 7.5.3 to 7.6.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.6.1/packages/@mantine/modals)

Updates `@mantine/notifications` from 7.5.3 to 7.6.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.6.1/packages/@mantine/notifications)

Updates `@mantine/spotlight` from 7.5.3 to 7.6.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.6.1/packages/@mantine/spotlight)

---
updated-dependencies:
- dependency-name: "@mantine/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/form"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/hooks"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/modals"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/notifications"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/spotlight"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 09:08:06 +00:00
Athou
30fc2cb8a4 apply standard js 2024-02-29 13:51:26 +01:00
Jérémie Panzer
25ccece76c Merge pull request #1269 from Athou/dependabot/maven/com.google.apis-google-api-services-youtube-v3-rev20240225-2.0.0
Bump com.google.apis:google-api-services-youtube from v3-rev20240213-2.0.0 to v3-rev20240225-2.0.0
2024-02-28 15:46:04 +01:00
dependabot[bot]
2cb9d2285a Bump com.google.apis:google-api-services-youtube
Bumps com.google.apis:google-api-services-youtube from v3-rev20240213-2.0.0 to v3-rev20240225-2.0.0.

---
updated-dependencies:
- dependency-name: com.google.apis:google-api-services-youtube
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-28 06:27:00 +00:00
Jérémie Panzer
0bb922fb99 Merge pull request #1265 from Athou/dependabot/maven/redis.clients-jedis-5.1.1
Bump redis.clients:jedis from 5.1.0 to 5.1.1
2024-02-26 15:03:41 +01:00
Jérémie Panzer
1c59ec5857 Merge pull request #1266 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.58
Bump @types/react from 18.2.57 to 18.2.58 in /commafeed-client
2024-02-26 14:56:37 +01:00
Jérémie Panzer
f4e97f6350 Merge pull request #1264 from Athou/dependabot/maven/org.apache.maven.plugins-maven-shade-plugin-3.5.2
Bump org.apache.maven.plugins:maven-shade-plugin from 3.5.1 to 3.5.2
2024-02-26 14:56:20 +01:00
Jérémie Panzer
fb355187ee Merge pull request #1263 from Athou/dependabot/maven/io.github.hakky54-sslcontext-kickstart-for-apache5-8.3.2
Bump io.github.hakky54:sslcontext-kickstart-for-apache5 from 8.3.1 to 8.3.2
2024-02-26 14:56:08 +01:00
Jérémie Panzer
89eb4d0535 Merge pull request #1262 from Athou/dependabot/maven/org.mariadb.jdbc-mariadb-java-client-3.3.3
Bump org.mariadb.jdbc:mariadb-java-client from 3.3.2 to 3.3.3
2024-02-26 14:55:56 +01:00
Jérémie Panzer
11fe2f9db8 Merge pull request #1268 from Athou/dependabot/npm_and_yarn/commafeed-client/eslint-8.57.0
Bump eslint from 8.56.0 to 8.57.0 in /commafeed-client
2024-02-26 14:55:37 +01:00
Jérémie Panzer
86acc3850a Merge pull request #1267 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.1.4
Bump vite from 5.1.3 to 5.1.4 in /commafeed-client
2024-02-26 14:55:29 +01:00
dependabot[bot]
77bf97c6d6 Bump eslint from 8.56.0 to 8.57.0 in /commafeed-client
Bumps [eslint](https://github.com/eslint/eslint) from 8.56.0 to 8.57.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.56.0...v8.57.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 09:58:06 +00:00
dependabot[bot]
1a96579292 Bump vite from 5.1.3 to 5.1.4 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.3 to 5.1.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 09:57:23 +00:00
dependabot[bot]
caccd3802c Bump @types/react from 18.2.57 to 18.2.58 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.57 to 18.2.58.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 09:56:45 +00:00
dependabot[bot]
dce899186b Bump redis.clients:jedis from 5.1.0 to 5.1.1
Bumps [redis.clients:jedis](https://github.com/redis/jedis) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/redis/jedis/releases)
- [Commits](https://github.com/redis/jedis/compare/v5.1.0...v5.1.1)

---
updated-dependencies:
- dependency-name: redis.clients:jedis
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 09:21:28 +00:00
dependabot[bot]
5626b39ffa Bump org.apache.maven.plugins:maven-shade-plugin from 3.5.1 to 3.5.2
Bumps [org.apache.maven.plugins:maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.5.1...maven-shade-plugin-3.5.2)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 09:21:22 +00:00
dependabot[bot]
5386c99c6b Bump io.github.hakky54:sslcontext-kickstart-for-apache5
Bumps [io.github.hakky54:sslcontext-kickstart-for-apache5](https://github.com/Hakky54/sslcontext-kickstart) from 8.3.1 to 8.3.2.
- [Changelog](https://github.com/Hakky54/sslcontext-kickstart/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Hakky54/sslcontext-kickstart/compare/v8.3.1...v8.3.2)

---
updated-dependencies:
- dependency-name: io.github.hakky54:sslcontext-kickstart-for-apache5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 09:21:17 +00:00
dependabot[bot]
c27fae140f Bump org.mariadb.jdbc:mariadb-java-client from 3.3.2 to 3.3.3
Bumps [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases)
- [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.3.2...3.3.3)

---
updated-dependencies:
- dependency-name: org.mariadb.jdbc:mariadb-java-client
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 09:21:07 +00:00
Jérémie Panzer
b8b67132f4 Merge pull request #1259 from Athou/dependabot/maven/commafeed-server/org.postgresql-postgresql-42.7.2
Bump org.postgresql:postgresql from 42.7.1 to 42.7.2 in /commafeed-server
2024-02-21 07:24:26 +01:00
dependabot[bot]
5623039084 Bump org.postgresql:postgresql in /commafeed-server
Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.1 to 42.7.2.
- [Release notes](https://github.com/pgjdbc/pgjdbc/releases)
- [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pgjdbc/pgjdbc/commits)

---
updated-dependencies:
- dependency-name: org.postgresql:postgresql
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 00:07:10 +00:00
Jérémie Panzer
39ddb256de Merge pull request #1256 from Athou/dependabot/npm_and_yarn/commafeed-client/lingui-e99279c299
Bump the lingui group in /commafeed-client with 5 updates
2024-02-20 21:01:27 +01:00
Jérémie Panzer
71801718dc Merge pull request #1257 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.57
Bump @types/react from 18.2.56 to 18.2.57 in /commafeed-client
2024-02-20 21:01:16 +01:00
Jérémie Panzer
b728e28081 Merge pull request #1258 from Athou/dependabot/npm_and_yarn/commafeed-client/vitest-1.3.1
Bump vitest from 1.3.0 to 1.3.1 in /commafeed-client
2024-02-20 21:00:59 +01:00
dependabot[bot]
bf2de7aecd Bump vitest from 1.3.0 to 1.3.1 in /commafeed-client
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.3.1/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-20 15:35:46 +00:00
dependabot[bot]
010fb2dccb Bump @types/react from 18.2.56 to 18.2.57 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.56 to 18.2.57.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-20 15:35:28 +00:00
dependabot[bot]
8517c0f4eb Bump the lingui group in /commafeed-client with 5 updates
Bumps the lingui group in /commafeed-client with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@lingui/core](https://github.com/lingui/js-lingui) | `4.7.0` | `4.7.1` |
| [@lingui/macro](https://github.com/lingui/js-lingui) | `4.7.0` | `4.7.1` |
| [@lingui/react](https://github.com/lingui/js-lingui) | `4.7.0` | `4.7.1` |
| [@lingui/cli](https://github.com/lingui/js-lingui) | `4.7.0` | `4.7.1` |
| [@lingui/vite-plugin](https://github.com/lingui/js-lingui) | `4.7.0` | `4.7.1` |


Updates `@lingui/core` from 4.7.0 to 4.7.1
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v4.7.0...v4.7.1)

Updates `@lingui/macro` from 4.7.0 to 4.7.1
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v4.7.0...v4.7.1)

Updates `@lingui/react` from 4.7.0 to 4.7.1
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v4.7.0...v4.7.1)

Updates `@lingui/cli` from 4.7.0 to 4.7.1
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v4.7.0...v4.7.1)

Updates `@lingui/vite-plugin` from 4.7.0 to 4.7.1
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v4.7.0...v4.7.1)

---
updated-dependencies:
- dependency-name: "@lingui/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lingui
- dependency-name: "@lingui/macro"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lingui
- dependency-name: "@lingui/react"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lingui
- dependency-name: "@lingui/cli"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: lingui
- dependency-name: "@lingui/vite-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: lingui
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-20 15:35:12 +00:00
Athou
09737b4d4c logout now works in dev mode 2024-02-20 10:42:56 +01:00
Athou
88e9a2c2e1 keep more eslint rules enabled 2024-02-20 10:42:44 +01:00
Athou
0d7300c192 use stricter eslint rules 2024-02-19 20:58:47 +01:00
Jérémie Panzer
cb1a00c5cd Merge pull request #1254 from Athou/dependabot/npm_and_yarn/commafeed-client/vitest-1.3.0
Bump vitest from 1.2.2 to 1.3.0 in /commafeed-client
2024-02-19 09:46:07 +01:00
Jérémie Panzer
07a07006cc Merge pull request #1252 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.56
Bump @types/react from 18.2.55 to 18.2.56 in /commafeed-client
2024-02-19 09:45:55 +01:00
Jérémie Panzer
bae7f94f8c Merge pull request #1251 from Athou/dependabot/npm_and_yarn/commafeed-client/fontsource/open-sans-5.0.24
Bump @fontsource/open-sans from 5.0.23 to 5.0.24 in /commafeed-client
2024-02-19 09:45:46 +01:00
Jérémie Panzer
b0832c5917 Merge pull request #1253 from Athou/dependabot/npm_and_yarn/commafeed-client/react-router-dom-6.22.1
Bump react-router-dom from 6.22.0 to 6.22.1 in /commafeed-client
2024-02-19 09:45:36 +01:00
Jérémie Panzer
f72e70cb56 Merge pull request #1250 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.1.3
Bump vite from 5.1.2 to 5.1.3 in /commafeed-client
2024-02-19 09:45:06 +01:00
Jérémie Panzer
8cc24e054f Merge pull request #1249 from Athou/dependabot/npm_and_yarn/commafeed-client/mantine-285eda16db
Bump the mantine group in /commafeed-client with 6 updates
2024-02-19 09:44:49 +01:00
dependabot[bot]
48e42228b1 Bump vitest from 1.2.2 to 1.3.0 in /commafeed-client
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.3.0/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 07:19:20 +00:00
dependabot[bot]
46c1af65f0 Bump react-router-dom from 6.22.0 to 6.22.1 in /commafeed-client
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.0 to 6.22.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.22.1/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.1/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 07:19:01 +00:00
dependabot[bot]
2989407d16 Bump @types/react from 18.2.55 to 18.2.56 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.55 to 18.2.56.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 07:18:44 +00:00
dependabot[bot]
2401e36486 Bump @fontsource/open-sans from 5.0.23 to 5.0.24 in /commafeed-client
Bumps [@fontsource/open-sans](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/open-sans) from 5.0.23 to 5.0.24.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/google/open-sans/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/open-sans)

---
updated-dependencies:
- dependency-name: "@fontsource/open-sans"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 07:18:27 +00:00
dependabot[bot]
4ee396e667 Bump vite from 5.1.2 to 5.1.3 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.2 to 5.1.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.3/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 07:18:15 +00:00
dependabot[bot]
08180dd373 Bump the mantine group in /commafeed-client with 6 updates
Bumps the mantine group in /commafeed-client with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@mantine/core](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/core) | `7.5.2` | `7.5.3` |
| [@mantine/form](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/form) | `7.5.2` | `7.5.3` |
| [@mantine/hooks](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/hooks) | `7.5.2` | `7.5.3` |
| [@mantine/modals](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/modals) | `7.5.2` | `7.5.3` |
| [@mantine/notifications](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/notifications) | `7.5.2` | `7.5.3` |
| [@mantine/spotlight](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/spotlight) | `7.5.2` | `7.5.3` |


Updates `@mantine/core` from 7.5.2 to 7.5.3
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.3/packages/@mantine/core)

Updates `@mantine/form` from 7.5.2 to 7.5.3
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.3/packages/@mantine/form)

Updates `@mantine/hooks` from 7.5.2 to 7.5.3
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.3/packages/@mantine/hooks)

Updates `@mantine/modals` from 7.5.2 to 7.5.3
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.3/packages/@mantine/modals)

Updates `@mantine/notifications` from 7.5.2 to 7.5.3
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.3/packages/@mantine/notifications)

Updates `@mantine/spotlight` from 7.5.2 to 7.5.3
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.3/packages/@mantine/spotlight)

---
updated-dependencies:
- dependency-name: "@mantine/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/form"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/hooks"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/modals"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/notifications"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/spotlight"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 07:17:41 +00:00
Jérémie Panzer
561513b7ed Merge pull request #1248 from Athou/dependabot/maven/com.google.apis-google-api-services-youtube-v3-rev20240213-2.0.0
Bump com.google.apis:google-api-services-youtube from v3-rev20240211-2.0.0 to v3-rev20240213-2.0.0
2024-02-18 18:32:10 +01:00
dependabot[bot]
9cd7053a90 Bump com.google.apis:google-api-services-youtube
Bumps com.google.apis:google-api-services-youtube from v3-rev20240211-2.0.0 to v3-rev20240213-2.0.0.

---
updated-dependencies:
- dependency-name: com.google.apis:google-api-services-youtube
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-18 08:12:41 +00:00
Jérémie Panzer
72d510bd47 Merge pull request #1245 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.1.2
Bump vite from 5.1.1 to 5.1.2 in /commafeed-client
2024-02-15 14:06:25 +01:00
Jérémie Panzer
1085d6aa7a Merge pull request #1244 from Athou/dependabot/npm_and_yarn/commafeed-client/reduxjs/toolkit-2.2.1
Bump @reduxjs/toolkit from 2.1.0 to 2.2.1 in /commafeed-client
2024-02-15 14:06:18 +01:00
Jérémie Panzer
9e0ef9461f Merge pull request #1246 from Athou/dependabot/maven/com.google.apis-google-api-services-youtube-v3-rev20240211-2.0.0
Bump com.google.apis:google-api-services-youtube from v3-rev20240123-2.0.0 to v3-rev20240211-2.0.0
2024-02-15 14:06:09 +01:00
dependabot[bot]
650acb62d5 Bump com.google.apis:google-api-services-youtube
Bumps com.google.apis:google-api-services-youtube from v3-rev20240123-2.0.0 to v3-rev20240211-2.0.0.

---
updated-dependencies:
- dependency-name: com.google.apis:google-api-services-youtube
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 09:42:32 +00:00
dependabot[bot]
ff1c8a1eff Bump vite from 5.1.1 to 5.1.2 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 09:42:21 +00:00
dependabot[bot]
62a4ac46a0 Bump @reduxjs/toolkit from 2.1.0 to 2.2.1 in /commafeed-client
Bumps [@reduxjs/toolkit](https://github.com/reduxjs/redux-toolkit) from 2.1.0 to 2.2.1.
- [Release notes](https://github.com/reduxjs/redux-toolkit/releases)
- [Commits](https://github.com/reduxjs/redux-toolkit/compare/v2.1.0...v2.2.1)

---
updated-dependencies:
- dependency-name: "@reduxjs/toolkit"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 09:42:03 +00:00
Athou
fafd4c9d54 release 4.3.1 2024-02-12 16:13:34 +01:00
Jérémie Panzer
73b472bc8a Merge pull request #1242 from Athou/dependabot/npm_and_yarn/commafeed-client/mantine-42bf712b6b
Bump the mantine group in /commafeed-client with 6 updates
2024-02-12 11:00:20 +01:00
Jérémie Panzer
1c3be67f76 Merge pull request #1243 from Athou/dependabot/npm_and_yarn/commafeed-client/fontsource/open-sans-5.0.23
Bump @fontsource/open-sans from 5.0.22 to 5.0.23 in /commafeed-client
2024-02-12 10:59:21 +01:00
dependabot[bot]
2a5988b3e7 Bump @fontsource/open-sans from 5.0.22 to 5.0.23 in /commafeed-client
Bumps [@fontsource/open-sans](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/open-sans) from 5.0.22 to 5.0.23.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/google/open-sans/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/open-sans)

---
updated-dependencies:
- dependency-name: "@fontsource/open-sans"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 09:30:55 +00:00
dependabot[bot]
c5757849f3 Bump the mantine group in /commafeed-client with 6 updates
Bumps the mantine group in /commafeed-client with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@mantine/core](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/core) | `7.5.1` | `7.5.2` |
| [@mantine/form](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/form) | `7.5.1` | `7.5.2` |
| [@mantine/hooks](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/hooks) | `7.5.1` | `7.5.2` |
| [@mantine/modals](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/modals) | `7.5.1` | `7.5.2` |
| [@mantine/notifications](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/notifications) | `7.5.1` | `7.5.2` |
| [@mantine/spotlight](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/spotlight) | `7.5.1` | `7.5.2` |


Updates `@mantine/core` from 7.5.1 to 7.5.2
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.2/packages/@mantine/core)

Updates `@mantine/form` from 7.5.1 to 7.5.2
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.2/packages/@mantine/form)

Updates `@mantine/hooks` from 7.5.1 to 7.5.2
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.2/packages/@mantine/hooks)

Updates `@mantine/modals` from 7.5.1 to 7.5.2
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.2/packages/@mantine/modals)

Updates `@mantine/notifications` from 7.5.1 to 7.5.2
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.2/packages/@mantine/notifications)

Updates `@mantine/spotlight` from 7.5.1 to 7.5.2
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.2/packages/@mantine/spotlight)

---
updated-dependencies:
- dependency-name: "@mantine/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/form"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/hooks"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/modals"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/notifications"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
- dependency-name: "@mantine/spotlight"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mantine
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 09:30:22 +00:00
Athou
b6107c3330 pass theme and colorscheme in tss context to avoid repetitions (#1241) 2024-02-12 07:38:57 +01:00
Athou
3efeed6c85 make sure videos don't overflow parent (#1240) 2024-02-10 21:50:17 +01:00
Athou
be44b0aad1 make sure timestamps are stored in UTC (#1239) 2024-02-10 12:42:22 +01:00
Athou
36152dc47f add an additional day to make sure the timestamp fits in all timezones (#1239) 2024-02-10 12:41:32 +01:00
Athou
32e9cd3e35 release 4.3.0 2024-02-09 20:02:17 +01:00
Athou
4bf8b5696d fix metrics page 2024-02-09 18:41:43 +01:00
Athou
0bf44dbc7b make sure we clean any existing file before starting 2024-02-09 18:32:43 +01:00
Athou
bda3ba4b5c mysql/mariadb lowest timestamp is actually 1970-01-01 00:00:01 (#1239) 2024-02-09 17:31:22 +01:00
Athou
23cff9c1e9 columnDataType is required for addNotNullConstraint on mysql 2024-02-09 17:19:47 +01:00
Athou
9691517335 add null check to userSessions 2024-02-09 17:18:18 +01:00
Jérémie Panzer
b38bd8c312 Merge pull request #1234 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.55
Bump @types/react from 18.2.53 to 18.2.55 in /commafeed-client
2024-02-09 11:10:17 +01:00
Jérémie Panzer
d8ca58389d Merge pull request #1238 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.1.1
Bump vite from 5.0.12 to 5.1.1 in /commafeed-client
2024-02-09 11:09:44 +01:00
dependabot[bot]
20a0cd7192 Bump @types/react from 18.2.53 to 18.2.55 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.53 to 18.2.55.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-09 10:06:24 +00:00
dependabot[bot]
9b895328be Bump vite from 5.0.12 to 5.1.1 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.12 to 5.1.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-09 10:05:37 +00:00
Jérémie Panzer
cd39ab5f95 Merge pull request #1235 from Athou/dependabot/npm_and_yarn/commafeed-client/monaco-editor-0.46.0
Bump monaco-editor from 0.45.0 to 0.46.0 in /commafeed-client
2024-02-09 11:05:33 +01:00
Jérémie Panzer
a7152a97a6 Merge pull request #1237 from Athou/dependabot/npm_and_yarn/commafeed-client/typescript-eslint/eslint-plugin-6.21.0
Bump @typescript-eslint/eslint-plugin from 6.20.0 to 6.21.0 in /commafeed-client
2024-02-09 11:05:15 +01:00
Jérémie Panzer
3e6e0a0f00 Merge pull request #1233 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-dom-18.2.19
Bump @types/react-dom from 18.2.18 to 18.2.19 in /commafeed-client
2024-02-09 11:04:49 +01:00
dependabot[bot]
2936dd0d32 Bump @typescript-eslint/eslint-plugin in /commafeed-client
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 6.20.0 to 6.21.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.21.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-09 09:54:35 +00:00
dependabot[bot]
38a838d210 Bump monaco-editor from 0.45.0 to 0.46.0 in /commafeed-client
Bumps [monaco-editor](https://github.com/microsoft/monaco-editor) from 0.45.0 to 0.46.0.
- [Changelog](https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/microsoft/monaco-editor/compare/v0.45.0...v0.46.0)

---
updated-dependencies:
- dependency-name: monaco-editor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-09 09:53:12 +00:00
dependabot[bot]
0136fa883d Bump @types/react-dom from 18.2.18 to 18.2.19 in /commafeed-client
Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.2.18 to 18.2.19.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-09 09:52:12 +00:00
Athou
b0890df2f3 add a css class for view mode (#1232) 2024-02-09 10:31:27 +01:00
Athou
91acad0dbf don't try to migrate h2 if database does not exist yet 2024-02-09 10:30:15 +01:00
Athou
14e7d70106 simplify websocket session retrieval 2024-02-05 20:27:26 +01:00
Jérémie Panzer
1cc76ba3ee Merge pull request #1230 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.53
Bump @types/react from 18.2.52 to 18.2.53 in /commafeed-client
2024-02-05 09:46:37 +01:00
dependabot[bot]
206800c091 Bump @types/react from 18.2.52 to 18.2.53 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.52 to 18.2.53.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 08:42:50 +00:00
Jérémie Panzer
33749c94e3 Merge pull request #1229 from Athou/dependabot/npm_and_yarn/commafeed-client/prettier-3.2.5
Bump prettier from 3.2.4 to 3.2.5 in /commafeed-client
2024-02-04 19:55:52 +01:00
Jérémie Panzer
8bce887e4c Merge pull request #1228 from Athou/dependabot/maven/io.github.hakky54-sslcontext-kickstart-for-apache5-8.3.1
Bump io.github.hakky54:sslcontext-kickstart-for-apache5 from 8.3.0 to 8.3.1
2024-02-04 19:55:46 +01:00
dependabot[bot]
ca4f73fff6 Bump prettier from 3.2.4 to 3.2.5 in /commafeed-client
Bumps [prettier](https://github.com/prettier/prettier) from 3.2.4 to 3.2.5.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.2.4...3.2.5)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-04 18:47:35 +00:00
dependabot[bot]
26443310c9 Bump io.github.hakky54:sslcontext-kickstart-for-apache5
Bumps [io.github.hakky54:sslcontext-kickstart-for-apache5](https://github.com/Hakky54/sslcontext-kickstart) from 8.3.0 to 8.3.1.
- [Changelog](https://github.com/Hakky54/sslcontext-kickstart/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Hakky54/sslcontext-kickstart/compare/v8.3.0...v8.3.1)

---
updated-dependencies:
- dependency-name: io.github.hakky54:sslcontext-kickstart-for-apache5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-04 18:47:02 +00:00
Athou
870593bae8 add H2 migration tool 2024-02-04 18:40:59 +01:00
Jérémie Panzer
cfd5d0faab Merge pull request #1226 from aniol/patch-1
Update messages.po
2024-02-04 08:11:14 +01:00
Aniol
9391c05968 Update messages.po
updated catalan translation
2024-02-04 07:33:44 +01:00
Jérémie Panzer
a13c75981b Merge pull request #1221 from Athou/dependabot/npm_and_yarn/commafeed-client/react-router-dom-6.22.0
Bump react-router-dom from 6.21.1 to 6.22.0 in /commafeed-client
2024-02-03 17:21:28 +01:00
dependabot[bot]
a05baf63c1 Bump react-router-dom from 6.21.1 to 6.22.0 in /commafeed-client
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.21.1 to 6.22.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 16:16:38 +00:00
Jérémie Panzer
32ce265cff Merge pull request #1222 from Athou/dependabot/npm_and_yarn/commafeed-client/prettier-3.2.4
Bump prettier from 3.1.1 to 3.2.4 in /commafeed-client
2024-02-03 17:16:23 +01:00
Jérémie Panzer
b2ad24e7f6 Merge pull request #1223 from Athou/dependabot/npm_and_yarn/commafeed-client/react-icons-5.0.1
Bump react-icons from 4.12.0 to 5.0.1 in /commafeed-client
2024-02-03 17:16:17 +01:00
Jérémie Panzer
fe626ebbe3 Merge pull request #1224 from Athou/dependabot/npm_and_yarn/commafeed-client/react-redux-9.1.0
Bump react-redux from 9.0.4 to 9.1.0 in /commafeed-client
2024-02-03 17:16:10 +01:00
Jérémie Panzer
4431a898a0 Merge pull request #1225 from Athou/dependabot/npm_and_yarn/commafeed-client/vitest-1.2.2
Bump vitest from 1.1.3 to 1.2.2 in /commafeed-client
2024-02-03 17:15:59 +01:00
dependabot[bot]
89bfcfa240 Bump vitest from 1.1.3 to 1.2.2 in /commafeed-client
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.1.3 to 1.2.2.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.2.2/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 16:10:30 +00:00
dependabot[bot]
d046d26f4e Bump react-redux from 9.0.4 to 9.1.0 in /commafeed-client
Bumps [react-redux](https://github.com/reduxjs/react-redux) from 9.0.4 to 9.1.0.
- [Release notes](https://github.com/reduxjs/react-redux/releases)
- [Changelog](https://github.com/reduxjs/react-redux/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reduxjs/react-redux/compare/v9.0.4...v9.1.0)

---
updated-dependencies:
- dependency-name: react-redux
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 16:10:14 +00:00
dependabot[bot]
26b634b1a3 Bump react-icons from 4.12.0 to 5.0.1 in /commafeed-client
Bumps [react-icons](https://github.com/react-icons/react-icons) from 4.12.0 to 5.0.1.
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v4.12.0...v5.0.1)

---
updated-dependencies:
- dependency-name: react-icons
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 16:09:58 +00:00
dependabot[bot]
3ca18bbd36 Bump prettier from 3.1.1 to 3.2.4 in /commafeed-client
Bumps [prettier](https://github.com/prettier/prettier) from 3.1.1 to 3.2.4.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.1.1...3.2.4)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 16:09:46 +00:00
Jérémie Panzer
7645731fff Merge pull request #1211 from Athou/dependabot/npm_and_yarn/commafeed-client/mantine-5b631ac874
Bump the mantine group in /commafeed-client with 6 updates
2024-02-03 11:51:24 +01:00
Athou
3c116dbabe fix build 2024-02-03 11:44:58 +01:00
dependabot[bot]
3026fd116c Bump the mantine group in /commafeed-client with 6 updates
Bumps the mantine group in /commafeed-client with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@mantine/core](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/core) | `7.3.2` | `7.5.1` |
| [@mantine/form](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/form) | `7.3.2` | `7.5.1` |
| [@mantine/hooks](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/hooks) | `7.3.2` | `7.5.1` |
| [@mantine/modals](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/modals) | `7.3.2` | `7.5.1` |
| [@mantine/notifications](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/notifications) | `7.3.2` | `7.5.1` |
| [@mantine/spotlight](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/spotlight) | `7.3.2` | `7.5.1` |


Updates `@mantine/core` from 7.3.2 to 7.5.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.1/packages/@mantine/core)

Updates `@mantine/form` from 7.3.2 to 7.5.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.1/packages/@mantine/form)

Updates `@mantine/hooks` from 7.3.2 to 7.5.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.1/packages/@mantine/hooks)

Updates `@mantine/modals` from 7.3.2 to 7.5.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.1/packages/@mantine/modals)

Updates `@mantine/notifications` from 7.3.2 to 7.5.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.1/packages/@mantine/notifications)

Updates `@mantine/spotlight` from 7.3.2 to 7.5.1
- [Release notes](https://github.com/mantinedev/mantine/releases)
- [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mantinedev/mantine/commits/7.5.1/packages/@mantine/spotlight)

---
updated-dependencies:
- dependency-name: "@mantine/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/form"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/hooks"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/modals"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/notifications"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
- dependency-name: "@mantine/spotlight"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mantine
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 09:20:11 +00:00
Jérémie Panzer
63fa725a13 Merge pull request #1220 from Athou/dependabot/npm_and_yarn/commafeed-client/reduxjs/toolkit-2.1.0
Bump @reduxjs/toolkit from 2.0.1 to 2.1.0 in /commafeed-client
2024-02-03 10:19:05 +01:00
Jérémie Panzer
ede4e07ff3 Merge pull request #1218 from Athou/dependabot/npm_and_yarn/commafeed-client/eslint-config-standard-with-typescript-43.0.1
Bump eslint-config-standard-with-typescript from 43.0.0 to 43.0.1 in /commafeed-client
2024-02-03 10:18:56 +01:00
dependabot[bot]
de6dfbe8b2 Bump @reduxjs/toolkit from 2.0.1 to 2.1.0 in /commafeed-client
Bumps [@reduxjs/toolkit](https://github.com/reduxjs/redux-toolkit) from 2.0.1 to 2.1.0.
- [Release notes](https://github.com/reduxjs/redux-toolkit/releases)
- [Commits](https://github.com/reduxjs/redux-toolkit/compare/v2.0.1...v2.1.0)

---
updated-dependencies:
- dependency-name: "@reduxjs/toolkit"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 09:13:40 +00:00
Jérémie Panzer
164a57bef5 Merge pull request #1217 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.52
Bump @types/react from 18.2.46 to 18.2.52 in /commafeed-client
2024-02-03 10:13:03 +01:00
Jérémie Panzer
fd82b8aaee Merge pull request #1216 from Athou/dependabot/npm_and_yarn/commafeed-client/fontsource/open-sans-5.0.22
Bump @fontsource/open-sans from 5.0.20 to 5.0.22 in /commafeed-client
2024-02-03 10:12:53 +01:00
Jérémie Panzer
facf8b43f2 Merge pull request #1214 from Athou/dependabot/npm_and_yarn/commafeed-client/axios-1.6.7
Bump axios from 1.6.3 to 1.6.7 in /commafeed-client
2024-02-03 10:12:38 +01:00
Jérémie Panzer
3184dfe178 Merge pull request #1213 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-tsconfig-paths-4.3.1
Bump vite-tsconfig-paths from 4.2.3 to 4.3.1 in /commafeed-client
2024-02-03 10:12:27 +01:00
Jérémie Panzer
cc584fd8c8 Merge pull request #1212 from Athou/dependabot/npm_and_yarn/commafeed-client/tss-react-4.9.4
Bump tss-react from 4.9.3 to 4.9.4 in /commafeed-client
2024-02-03 10:12:10 +01:00
Jérémie Panzer
0f8fa1f2e1 Merge pull request #1210 from Athou/dependabot/maven/com.microsoft.playwright-playwright-1.41.2
Bump com.microsoft.playwright:playwright from 1.41.1 to 1.41.2
2024-02-03 10:11:47 +01:00
Jérémie Panzer
d93f7bd20e Merge pull request #1219 from Athou/dependabot/npm_and_yarn/commafeed-client/typescript-eslint/eslint-plugin-6.20.0
Bump @typescript-eslint/eslint-plugin from 6.16.0 to 6.20.0 in /commafeed-client
2024-02-03 10:11:17 +01:00
dependabot[bot]
96aa06d2dd Bump eslint-config-standard-with-typescript in /commafeed-client
Bumps [eslint-config-standard-with-typescript](https://github.com/mightyiam/eslint-config-standard-with-typescript) from 43.0.0 to 43.0.1.
- [Release notes](https://github.com/mightyiam/eslint-config-standard-with-typescript/releases)
- [Changelog](https://github.com/mightyiam/eslint-config-standard-with-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mightyiam/eslint-config-standard-with-typescript/compare/v43.0.0...v43.0.1)

---
updated-dependencies:
- dependency-name: eslint-config-standard-with-typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 09:10:57 +00:00
Jérémie Panzer
fdaff46008 Merge pull request #1215 from Athou/dependabot/npm_and_yarn/commafeed-client/eslint-plugin-prettier-5.1.3
Bump eslint-plugin-prettier from 5.1.2 to 5.1.3 in /commafeed-client
2024-02-03 10:10:12 +01:00
dependabot[bot]
71066cd768 Bump @typescript-eslint/eslint-plugin in /commafeed-client
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 6.16.0 to 6.20.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.20.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 08:15:57 +00:00
dependabot[bot]
b9610a9058 Bump @types/react from 18.2.46 to 18.2.52 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.46 to 18.2.52.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 08:15:29 +00:00
dependabot[bot]
ca027d5a4d Bump @fontsource/open-sans from 5.0.20 to 5.0.22 in /commafeed-client
Bumps [@fontsource/open-sans](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/open-sans) from 5.0.20 to 5.0.22.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/google/open-sans/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/google/open-sans)

---
updated-dependencies:
- dependency-name: "@fontsource/open-sans"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 08:15:21 +00:00
dependabot[bot]
1dea51c705 Bump eslint-plugin-prettier from 5.1.2 to 5.1.3 in /commafeed-client
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 5.1.2 to 5.1.3.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v5.1.2...v5.1.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 08:15:11 +00:00
dependabot[bot]
8edc89f3cc Bump axios from 1.6.3 to 1.6.7 in /commafeed-client
Bumps [axios](https://github.com/axios/axios) from 1.6.3 to 1.6.7.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.3...v1.6.7)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 08:15:02 +00:00
dependabot[bot]
4cbf32cbb8 Bump vite-tsconfig-paths from 4.2.3 to 4.3.1 in /commafeed-client
Bumps [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) from 4.2.3 to 4.3.1.
- [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases)
- [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v4.2.3...v4.3.1)

---
updated-dependencies:
- dependency-name: vite-tsconfig-paths
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 08:14:53 +00:00
dependabot[bot]
a5dc551b6b Bump tss-react from 4.9.3 to 4.9.4 in /commafeed-client
Bumps [tss-react](https://github.com/garronej/tss-react) from 4.9.3 to 4.9.4.
- [Release notes](https://github.com/garronej/tss-react/releases)
- [Commits](https://github.com/garronej/tss-react/compare/v4.9.3...v4.9.4)

---
updated-dependencies:
- dependency-name: tss-react
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 08:14:43 +00:00
dependabot[bot]
b1e0dbd0b3 Bump com.microsoft.playwright:playwright from 1.41.1 to 1.41.2
Bumps [com.microsoft.playwright:playwright](https://github.com/microsoft/playwright-java) from 1.41.1 to 1.41.2.
- [Release notes](https://github.com/microsoft/playwright-java/releases)
- [Commits](https://github.com/microsoft/playwright-java/compare/v1.41.1...v1.41.2)

---
updated-dependencies:
- dependency-name: com.microsoft.playwright:playwright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 08:13:26 +00:00
Athou
58789b15a3 configure dependabot for client dependencies 2024-02-03 09:13:00 +01:00
Athou
c5f58a2fe9 add test to make sure the session has been invalidated 2024-02-02 20:25:27 +01:00
Athou
253ba5f18b remove unused eslint plugins 2024-01-31 20:13:52 +01:00
Athou
ae859178c0 increase pull request limit 2024-01-30 22:42:55 +01:00
Jérémie Panzer
942dc0befe Merge pull request #1206 from Athou/dependabot/maven/com.microsoft.playwright-playwright-1.41.1
Bump com.microsoft.playwright:playwright from 1.40.0 to 1.41.1
2024-01-30 22:42:02 +01:00
Jérémie Panzer
66c4510fd3 Merge pull request #1205 from Athou/dependabot/maven/org.jsoup-jsoup-1.17.2
Bump org.jsoup:jsoup from 1.17.1 to 1.17.2
2024-01-30 22:41:48 +01:00
Jérémie Panzer
feb7de504c Merge pull request #1204 from Athou/dependabot/maven/io.dropwizard-dropwizard-dependencies-4.0.6
Bump io.dropwizard:dropwizard-dependencies from 4.0.5 to 4.0.6
2024-01-30 22:41:38 +01:00
Jérémie Panzer
ec4b809ff9 Merge pull request #1207 from Athou/dependabot/maven/org.gwtproject-gwt-servlet-2.11.0
Bump org.gwtproject:gwt-servlet from 2.10.0 to 2.11.0
2024-01-30 22:41:28 +01:00
Jérémie Panzer
b8d6a5742b Merge pull request #1203 from Athou/dependabot/maven/com.mysql-mysql-connector-j-8.3.0
Bump com.mysql:mysql-connector-j from 8.2.0 to 8.3.0
2024-01-30 22:41:17 +01:00
dependabot[bot]
31c42403a1 Bump org.gwtproject:gwt-servlet from 2.10.0 to 2.11.0
Bumps org.gwtproject:gwt-servlet from 2.10.0 to 2.11.0.

---
updated-dependencies:
- dependency-name: org.gwtproject:gwt-servlet
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:36:11 +00:00
dependabot[bot]
ead97be3cf Bump com.microsoft.playwright:playwright from 1.40.0 to 1.41.1
Bumps [com.microsoft.playwright:playwright](https://github.com/microsoft/playwright-java) from 1.40.0 to 1.41.1.
- [Release notes](https://github.com/microsoft/playwright-java/releases)
- [Commits](https://github.com/microsoft/playwright-java/compare/v1.40.0...v1.41.1)

---
updated-dependencies:
- dependency-name: com.microsoft.playwright:playwright
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:36:05 +00:00
dependabot[bot]
3ee43f75d6 Bump org.jsoup:jsoup from 1.17.1 to 1.17.2
Bumps [org.jsoup:jsoup](https://github.com/jhy/jsoup) from 1.17.1 to 1.17.2.
- [Release notes](https://github.com/jhy/jsoup/releases)
- [Changelog](https://github.com/jhy/jsoup/blob/master/CHANGES.md)
- [Commits](https://github.com/jhy/jsoup/compare/jsoup-1.17.1...jsoup-1.17.2)

---
updated-dependencies:
- dependency-name: org.jsoup:jsoup
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:35:53 +00:00
dependabot[bot]
f77b91540d Bump io.dropwizard:dropwizard-dependencies from 4.0.5 to 4.0.6
Bumps io.dropwizard:dropwizard-dependencies from 4.0.5 to 4.0.6.

---
updated-dependencies:
- dependency-name: io.dropwizard:dropwizard-dependencies
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:35:34 +00:00
dependabot[bot]
34915c93b8 Bump com.mysql:mysql-connector-j from 8.2.0 to 8.3.0
Bumps [com.mysql:mysql-connector-j](https://github.com/mysql/mysql-connector-j) from 8.2.0 to 8.3.0.
- [Changelog](https://github.com/mysql/mysql-connector-j/blob/release/8.x/CHANGES)
- [Commits](https://github.com/mysql/mysql-connector-j/compare/8.2.0...8.3.0)

---
updated-dependencies:
- dependency-name: com.mysql:mysql-connector-j
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:35:26 +00:00
Jérémie Panzer
365044d205 Merge pull request #1202 from Athou/dependabot/maven/io.github.hakky54-sslcontext-kickstart-for-apache5-8.3.0
Bump io.github.hakky54:sslcontext-kickstart-for-apache5 from 8.2.0 to 8.3.0
2024-01-30 22:33:57 +01:00
Jérémie Panzer
46f84ab29e Merge pull request #1198 from Athou/dependabot/maven/org.apache.maven.plugins-maven-surefire-plugin-3.2.5
Bump org.apache.maven.plugins:maven-surefire-plugin from 3.2.3 to 3.2.5
2024-01-30 22:33:50 +01:00
Jérémie Panzer
2041823f0d Merge pull request #1199 from Athou/dependabot/maven/org.mariadb.jdbc-mariadb-java-client-3.3.2
Bump org.mariadb.jdbc:mariadb-java-client from 3.3.1 to 3.3.2
2024-01-30 22:33:42 +01:00
Jérémie Panzer
ff01d7a87c Merge pull request #1200 from Athou/dependabot/maven/querydsl.version-5.1.0
Bump querydsl.version from 5.0.0 to 5.1.0
2024-01-30 22:33:35 +01:00
Jérémie Panzer
4d905b118a Merge pull request #1201 from Athou/dependabot/maven/com.diffplug.spotless-spotless-maven-plugin-2.43.0
Bump com.diffplug.spotless:spotless-maven-plugin from 2.41.1 to 2.43.0
2024-01-30 22:33:21 +01:00
Athou
6d74b50751 login to docker hub only if we need to be logged in 2024-01-30 22:28:05 +01:00
dependabot[bot]
f12bdf5841 Bump io.github.hakky54:sslcontext-kickstart-for-apache5
Bumps [io.github.hakky54:sslcontext-kickstart-for-apache5](https://github.com/Hakky54/sslcontext-kickstart) from 8.2.0 to 8.3.0.
- [Changelog](https://github.com/Hakky54/sslcontext-kickstart/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Hakky54/sslcontext-kickstart/compare/v8.2.0...v8.3.0)

---
updated-dependencies:
- dependency-name: io.github.hakky54:sslcontext-kickstart-for-apache5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:16:26 +00:00
dependabot[bot]
6b89e211d8 Bump com.diffplug.spotless:spotless-maven-plugin from 2.41.1 to 2.43.0
Bumps [com.diffplug.spotless:spotless-maven-plugin](https://github.com/diffplug/spotless) from 2.41.1 to 2.43.0.
- [Changelog](https://github.com/diffplug/spotless/blob/main/CHANGES.md)
- [Commits](https://github.com/diffplug/spotless/compare/maven/2.41.1...lib/2.43.0)

---
updated-dependencies:
- dependency-name: com.diffplug.spotless:spotless-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:16:21 +00:00
dependabot[bot]
1b00e5613a Bump querydsl.version from 5.0.0 to 5.1.0
Bumps `querydsl.version` from 5.0.0 to 5.1.0.

Updates `com.querydsl:querydsl-apt` from 5.0.0 to 5.1.0

Updates `com.querydsl:querydsl-jpa` from 5.0.0 to 5.1.0

---
updated-dependencies:
- dependency-name: com.querydsl:querydsl-apt
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.querydsl:querydsl-jpa
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:16:16 +00:00
dependabot[bot]
050a8b24fc Bump org.mariadb.jdbc:mariadb-java-client from 3.3.1 to 3.3.2
Bumps [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases)
- [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.3.1...3.3.2)

---
updated-dependencies:
- dependency-name: org.mariadb.jdbc:mariadb-java-client
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:16:12 +00:00
dependabot[bot]
c5b1ea486c Bump org.apache.maven.plugins:maven-surefire-plugin from 3.2.3 to 3.2.5
Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.2.3 to 3.2.5.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.3...surefire-3.2.5)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:16:04 +00:00
Jérémie Panzer
7b8c0ac6ff Merge pull request #1193 from Athou/dependabot/maven/com.google.apis-google-api-services-youtube-v3-rev20240123-2.0.0
Bump com.google.apis:google-api-services-youtube from v3-rev20231011-2.0.0 to v3-rev20240123-2.0.0
2024-01-30 22:12:22 +01:00
Jérémie Panzer
d43039cf9f Merge pull request #1197 from Athou/dependabot/maven/org.apache.maven.plugins-maven-compiler-plugin-3.12.1
Bump org.apache.maven.plugins:maven-compiler-plugin from 3.11.0 to 3.12.1
2024-01-30 22:12:14 +01:00
Jérémie Panzer
3adc043740 Merge pull request #1196 from Athou/dependabot/maven/org.apache.maven.plugins-maven-failsafe-plugin-3.2.5
Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.2.3 to 3.2.5
2024-01-30 22:11:36 +01:00
Jérémie Panzer
08b95ff3dd Merge pull request #1195 from Athou/dependabot/maven/io.swagger.core.v3-swagger-maven-plugin-jakarta-2.2.20
Bump io.swagger.core.v3:swagger-maven-plugin-jakarta from 2.2.19 to 2.2.20
2024-01-30 22:11:28 +01:00
Jérémie Panzer
e043ce71c3 Merge pull request #1194 from Athou/dependabot/maven/io.swagger.core.v3-swagger-annotations-2.2.20
Bump io.swagger.core.v3:swagger-annotations from 2.2.19 to 2.2.20
2024-01-30 22:11:17 +01:00
Athou
b345319f68 don't try to login to docker hub for pull requests 2024-01-30 22:09:29 +01:00
dependabot[bot]
4c298df9c9 Bump org.apache.maven.plugins:maven-compiler-plugin
Bumps [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.11.0 to 3.12.1.
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.11.0...maven-compiler-plugin-3.12.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-compiler-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:03:03 +00:00
dependabot[bot]
067e01660a Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.2.3 to 3.2.5
Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.2.3 to 3.2.5.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.3...surefire-3.2.5)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-failsafe-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:02:57 +00:00
dependabot[bot]
c649a04891 Bump io.swagger.core.v3:swagger-maven-plugin-jakarta
Bumps io.swagger.core.v3:swagger-maven-plugin-jakarta from 2.2.19 to 2.2.20.

---
updated-dependencies:
- dependency-name: io.swagger.core.v3:swagger-maven-plugin-jakarta
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:02:51 +00:00
dependabot[bot]
9b9a4f98f4 Bump io.swagger.core.v3:swagger-annotations from 2.2.19 to 2.2.20
Bumps io.swagger.core.v3:swagger-annotations from 2.2.19 to 2.2.20.

---
updated-dependencies:
- dependency-name: io.swagger.core.v3:swagger-annotations
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:02:48 +00:00
dependabot[bot]
f8b6f2f237 Bump com.google.apis:google-api-services-youtube
Bumps com.google.apis:google-api-services-youtube from v3-rev20231011-2.0.0 to v3-rev20240123-2.0.0.

---
updated-dependencies:
- dependency-name: com.google.apis:google-api-services-youtube
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 21:02:44 +00:00
Jérémie Panzer
371ce0d160 Create dependabot.yml 2024-01-30 22:01:04 +01:00
Athou
a92a7217ff add a setting to completely disable scrolling to selected entry (#1157) 2024-01-29 20:30:49 +01:00
Athou
e69c230678 bump github action versions to remove warnings 2024-01-26 10:11:41 +01:00
Athou
b82077d3ca release 4.2.1 2024-01-26 09:54:07 +01:00
Athou
c624955ea4 websocket notification now takes entry filtering into account (#1191) 2024-01-24 15:47:37 +01:00
Athou
9354fb8e18 release 4.2.0 2024-01-22 15:07:17 +01:00
Jérémie Panzer
664ed317a0 Merge pull request #1189 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.0.12
Bump vite from 5.0.11 to 5.0.12 in /commafeed-client
2024-01-20 08:33:52 +01:00
dependabot[bot]
5bf121782b Bump vite from 5.0.11 to 5.0.12 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.11 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-20 07:29:09 +00:00
Athou
66c361e6a6 no need to render the header twice 2024-01-18 09:39:53 +01:00
Athou
0946c0248e show footer on the bottom of the page on mobile (#1121) 2024-01-18 09:29:12 +01:00
Athou
a8be8f2edf remove unnecessary usage of headerHeight 2024-01-18 09:05:32 +01:00
Athou
99db85328b scroll to the correct position regardless of the position or height of the header 2024-01-18 09:05:32 +01:00
Athou
5f29838bd2 clarify some descriptions in the profile settings 2024-01-16 07:07:49 +01:00
Athou
7d2c0e7576 remove the license from the README because there's already a LICENSE file 2024-01-15 11:10:01 +01:00
Athou
b8211e69e9 remove websocket bundle because it doesn't add much, use jetty directly 2024-01-15 09:53:52 +01:00
Athou
d7b2c5a6e3 add fix for fever api for Unread (#1188) 2024-01-14 18:34:50 +01:00
Athou
18358d5991 don't return last_refreshed_on_time when not set 2024-01-14 18:21:52 +01:00
Athou
e9b4895b0f move timezone logic to a reusable timestamp_type property 2024-01-13 17:23:16 +01:00
Athou
c4fbf98200 convert datetime fields to timestamp fields since we want to store UTC timestamps (#1187) 2024-01-13 14:58:03 +01:00
Athou
b0aa6ae524 the "new-feed-entries" websocket event no longer needs to reload the entire tree 2024-01-13 09:20:56 +01:00
Athou
11dd151a3b fix typo 2024-01-12 08:22:36 +01:00
Athou
874e7dcee6 release 4.1.0 2024-01-12 08:15:40 +01:00
Athou
8297edaf71 redirect to login page instead of welcome page if allowRegistrations is false (#1185) 2024-01-11 16:44:40 +01:00
Athou
9e4e629a1a prevent caching openapi files, so that the documentation is always up to date 2024-01-11 08:01:51 +01:00
Athou
8b86617f18 marking an entry as read/unread now requires to swipe to the left since swiping to the right now opens the mobile menu 2024-01-10 19:57:56 +01:00
Athou
bbda35f868 open sidebar on swipe (#1098) 2024-01-10 19:57:38 +01:00
Athou
df68405fef allow users without email to change their profile (#1184) 2024-01-10 19:28:03 +01:00
Athou
65194d948f ignore vscode files 2024-01-10 11:21:37 +01:00
Athou
d49297216c cleanup 2024-01-10 11:19:47 +01:00
Athou
e3e50f8456 improve artifact upload speed (https://github.com/actions/upload-artifact/issues/199) 2024-01-09 22:08:04 +01:00
Athou
e90b3730ef add JavaTimeModule to RedisCacheService object mapper to be able to serialize java.time.Instant 2024-01-09 22:01:34 +01:00
Athou
7675a24eb6 store only user id in session in order to avoid invalidating all sessions when user model changes 2024-01-09 21:22:20 +01:00
Athou
2bf9186135 only show sidebar resizer when sidebar is actually shown 2024-01-09 16:20:46 +01:00
Athou
d4ea51c145 fix vulnerability 2024-01-09 14:59:33 +01:00
Athou
6e0e99694e use properties file of git-commit-id-maven-plugin so we don't need to filter resources 2024-01-09 14:56:59 +01:00
Athou
9ede8d1c46 remove the Managed interface for classes that are not managed by dropwizard 2024-01-09 14:09:24 +01:00
Athou
fd0425a2be clear all sessions because the session model changed 2024-01-09 11:26:54 +01:00
Athou
2b976cadeb add a memory management section to the readme 2024-01-09 09:39:56 +01:00
Athou
023c27a565 setupListeners is only used for rtk query and we don't use it anymore 2024-01-09 07:24:24 +01:00
Athou
69c9988404 migrate from java.util.Date to java.time 2024-01-08 21:58:40 +01:00
Athou
b1a4debb95 replace toSorted usage with sort (#1183) 2024-01-08 13:48:27 +01:00
Athou
5663d619aa show category hierarchy (#1045) 2024-01-08 13:26:20 +01:00
Athou
2ef9e8d274 add null check 2024-01-07 22:14:00 +01:00
Athou
1292018de0 add setting to delete old entries 2024-01-07 20:49:02 +01:00
Athou
039e91414e prevent demo account from registering custom js code 2024-01-07 17:51:22 +01:00
Athou
662d0f754f avoid flash of light theme when using system color scheme 2024-01-07 17:51:22 +01:00
Athou
7fb7efbdf7 add missing truncate lost in refactoring 2024-01-07 17:51:22 +01:00
Athou
a841c80261 simplify trie building 2024-01-07 17:51:22 +01:00
Athou
da4143fa13 multiple feeds may have the same url hash 2024-01-07 17:51:22 +01:00
Athou
789857b09f compare feed entry content after cleanup because that's what saved in the database 2024-01-07 17:51:22 +01:00
Athou
ed45746f52 extract html cleaning code to its own service 2024-01-07 17:51:22 +01:00
Athou
deb51f2ccc rename FixedSizeSortedSet to FixedSizeSortedList because it's actually a list 2024-01-07 17:51:22 +01:00
Athou
5fec4a4c5f improve lookup by using a set because we only use contains() 2024-01-07 17:51:22 +01:00
Athou
7b335e2fd4 feed refresh engine now uses its own immutable model 2024-01-07 17:51:22 +01:00
Athou
60b6c69020 close the HTTP client after each test to close idle connections (https://github.com/dropwizard/dropwizard/issues/8174) 2024-01-06 08:37:12 +01:00
Athou
08ab32c4c2 we don't need the admin connector for tests 2024-01-05 21:20:56 +01:00
Athou
ff24fe4c7c eslint is already run by vite-plugin-eslint during build 2024-01-05 20:51:41 +01:00
Athou
50c62fb468 remove warning: 'typeParameters' property is deprecated 2024-01-05 20:48:25 +01:00
Athou
201331afc3 update vite to 5.x 2024-01-05 20:38:01 +01:00
Athou
cf3100081e add test for unauthorized websocket usage 2024-01-03 21:08:25 +01:00
Athou
860aab7495 fix typo 2024-01-02 11:11:45 +01:00
Athou
b084c8d108 remove line break 2024-01-02 11:10:33 +01:00
171 changed files with 4788 additions and 2648 deletions

24
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
version: 2
updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "npm"
directory: "/commafeed-client"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
groups:
mantine:
patterns:
- "@mantine/*"
lingui:
patterns:
- "@lingui/*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10

View File

@@ -11,19 +11,19 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
# Setup # Setup
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Set up Java - name: Set up Java
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
distribution: "temurin" distribution: "temurin"
@@ -34,7 +34,7 @@ jobs:
run: mvn --batch-mode --update-snapshots verify run: mvn --batch-mode --update-snapshots verify
- name: Upload JAR - name: Upload JAR
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
if: ${{ matrix.java == '17' }} if: ${{ matrix.java == '17' }}
with: with:
name: commafeed.jar name: commafeed.jar
@@ -42,14 +42,14 @@ jobs:
# Docker # Docker
- name: Login to Container Registry - name: Login to Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3
if: ${{ matrix.java == '17' }} if: ${{ matrix.java == '17' && (github.ref_type == 'tag' || github.ref_name == 'master') }}
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker build and push tag - name: Docker build and push tag
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
if: ${{ matrix.java == '17' && github.ref_type == 'tag' }} if: ${{ matrix.java == '17' && github.ref_type == 'tag' }}
with: with:
context: . context: .
@@ -60,7 +60,7 @@ jobs:
athou/commafeed:${{ github.ref_name }} athou/commafeed:${{ github.ref_name }}
- name: Docker build and push master - name: Docker build and push master
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
if: ${{ matrix.java == '17' && github.ref_name == 'master' }} if: ${{ matrix.java == '17' && github.ref_name == 'master' }}
with: with:
context: . context: .

3
.gitignore vendored
View File

@@ -35,5 +35,8 @@ src/main/app/lib
# Sublime # Sublime
*.sublime* *.sublime*
# VSCode
.vscode
# Macs # Macs
*.DS_Store *.DS_Store

View File

@@ -1,5 +1,57 @@
# Changelog # Changelog
## [4.3.2]
- added support for unix sockets (#1278)
## [4.3.1]
- fix an issue that prevents new feeds from being added when mysql/mariadb is used as the database and the database
timezone is not UTC (#1239)
- videos in enclosures can no longer have a width larger than the page (#1240)
## [4.3.0]
- h2 (the embedded database) has been upgraded to 2.2.224
- this version uses a different file format than 2.1.x, the first time you start CommaFeed with this version, the
database will be automatically converted to the new format
- add a setting to completely disable scrolling to selected entry (#1157)
- add a css class reflecting the current view mode to ease custom css rules (#1232)
- fix an issue that prevents new feeds from being added when mysql/mariadb is used as the database (#1239)
## [4.2.1]
- fix an issue that caused the tree to show an incorrect unread count after a websocket notification because entries
that were already marked as read by a filtering expression were not ignored (#1191)
## [4.2.0]
- add a setting to display the action buttons in the footer instead of in the header on mobile (#1121)
- the websocket notification now contains everything needed to update the UI, the client no longer needs to make an API
call to get the latest data when receiving the notification
- add a workaround to the Fever API for the Unread iOS app (#1188)
- fix an issue that caused dates to be saved incorrectly if the database server and the application server were in
different timezones (#1187)
## [4.1.0]
- it is now possible to open the sidebar on mobile by swiping to the right (#1098)
- swiping to mark entries as read/unread changed from swiping right to left because swiping right now opens the sidebar
- the full hierarchy of categories are now displayed in the category dropdown (#1045)
- added a setting `maxEntriesAgeDays` to delete old entries based on their age during database cleanup.
The setting is disabled by default for existing installations, except for the docker image where it is enabled and set
to 365 days
- if user registrations are disabled on your instance which is the default behavior, users are redirected on the login
page instead of the welcome page when not logged in (#1185)
- the sidebar resizer is no longer shown in the middle of the screen on mobile
- when using the system color scheme and the system is using a dark theme, feed entries no longer flicker on load
- the demo account (if enabled) cannot register custom javascript code anymore
- removed the usage of `toSorted` in the client because older browsers do not support it (#1183)
- the openapi documentation is no longer cached by the browser so you always have access to the latest version
- added a memory management section to the readme, reading it is recommended if you are running CommaFeed on a server
with limited memory
- fixed an issue that caused users without an email address set to be unable to edit their profile (#1184)
## [4.0.0] ## [4.0.0]
- migrated from dropwizard 2 to dropwizard 4, Java 17+ is now required - migrated from dropwizard 2 to dropwizard 4, Java 17+ is now required
@@ -11,14 +63,13 @@
- custom JS code is now executed when the app is done loading instead of when the page is loaded - custom JS code is now executed when the app is done loading instead of when the page is loaded
- the favicon is now correctly returned for feeds that return an invalid content type - the favicon is now correctly returned for feeds that return an invalid content type
- the feed refresh engine now uses httpclient5 with connection pooling and no longer creates a new client for each - the feed refresh engine now uses httpclient5 with connection pooling and no longer creates a new client for each
request, request, reducing CPU usage
reducing CPU usage
- updated UI library Mantine to 7.0, improving performance - updated UI library Mantine to 7.0, improving performance
- the h2 embedded database is now compacted on shutdown to reclaim unused space - the h2 embedded database is now compacted on shutdown to reclaim unused space
- the admin connector on port 8084 is now disabled in config.yml.example. Disabling it in your config.yml is - the admin connector on port 8084 is now disabled in config.yml.example. Disabling it in your config.yml is
recommended (see https://github.com/Athou/commafeed/commit/929df60f09cce56020b0962ab111cd8349b271b0) recommended (see https://github.com/Athou/commafeed/commit/929df60f09cce56020b0962ab111cd8349b271b0)
- migrated documentation from swagger 2 to openapi 3 - migrated documentation from swagger 2 to openapi 3
- added a GET method to the fever api to indicate that the endpoint is working correctly when accesed from a browser - added a GET method to the fever api to indicate that the endpoint is working correctly when accessed from a browser
- the websocket connection can now be disabled, the websocket ping interval and the tree reload interval can now be - the websocket connection can now be disabled, the websocket ping interval and the tree reload interval can now be
configured (see config.yml.example) configured (see config.yml.example)
- the websocket connection now works correctly when the context root of the application is not "/" - the websocket connection now works correctly when the context root of the application is not "/"

View File

@@ -54,6 +54,29 @@ user is `admin` and the default password is `admin`.
The server will listen on http://localhost:8082. The default The server will listen on http://localhost:8082. The default
user is `admin` and the default password is `admin`. user is `admin` and the default password is `admin`.
### Memory management
The Java Virtual Machine (JVM) is rather greedy by default and will not release unused memory to the
operating system. This is because acquiring memory from the operating system is a relatively expensive operation.
However, this can be problematic on systems with limited memory.
#### Hard limit
The JVM can be configured to use a maximum amount of memory with the `-Xmx` parameter.
For example, to limit the JVM to 256MB of memory, use `-Xmx256m`.
#### Dynamic sizing
The JVM can be configured to release unused memory to the operating system with the following parameters:
-Xms20m -XX:+UseG1GC -XX:-ShrinkHeapInSteps -XX:G1PeriodicGCInterval=10000 -XX:-G1PeriodicGCInvokesConcurrent -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10
This is how the Docker image is configured.
See [here](https://docs.oracle.com/en/java/javase/17/gctuning/garbage-first-g1-garbage-collector1.html)
and [here](https://docs.oracle.com/en/java/javase/17/gctuning/factors-affecting-garbage-collection-performance.html) for
more
information.
## Translation ## Translation
Files for internationalization are Files for internationalization are
@@ -86,19 +109,3 @@ two-letters [ISO-639-1 language code](http://en.wikipedia.org/wiki/List_of_ISO_6
The frontend server is now running at http://localhost:8082 and is proxying REST requests to the backend running on The frontend server is now running at http://localhost:8082 and is proxying REST requests to the backend running on
port 8083 port 8083
## Copyright and license
Copyright 2013-2023 CommaFeed.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,41 +1,47 @@
module.exports = { module.exports = {
env: { env: {
browser: true, browser: true,
es2021: true es2021: true,
}, },
extends: ["standard-with-typescript", "plugin:react/recommended", "plugin:react-hooks/recommended", "plugin:prettier/recommended"], extends: [
"eslint:recommended",
"standard",
"plugin:@typescript-eslint/strict-type-checked",
"plugin:@typescript-eslint/stylistic-type-checked",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended",
],
settings: { settings: {
react: { react: {
version: "detect" version: "detect",
} },
}, },
overrides: [ overrides: [
{ {
env: { env: {
node: true node: true,
}, },
files: [".eslintrc.{js,cjs}"], files: [".eslintrc.{js,cjs}"],
parserOptions: { parserOptions: {
sourceType: "script" sourceType: "script",
} },
} },
], ],
parserOptions: { parserOptions: {
project: true,
ecmaVersion: "latest", ecmaVersion: "latest",
sourceType: "module" sourceType: "module",
}, },
plugins: ["react"], plugins: ["react"],
rules: { rules: {
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as" }], "@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as" }],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-confusing-void-expression": ["error", { ignoreArrowShorthand: true }], "@typescript-eslint/no-confusing-void-expression": ["error", { ignoreArrowShorthand: true }],
"@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-misused-promises": "off", "@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/prefer-nullish-coalescing": ["error", { ignoreConditionalTests: true }], "@typescript-eslint/prefer-nullish-coalescing": ["error", { ignoreConditionalTests: true }],
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/unbound-method": "off",
"react/no-unescaped-entities": "off", "react/no-unescaped-entities": "off",
"react/react-in-jsx-scope": "off", "react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "error" "react-hooks/exhaustive-deps": "error",
} },
} }

File diff suppressed because it is too large Load Diff

View File

@@ -14,24 +14,24 @@
"i18n:extract": "lingui extract --clean" "i18n:extract": "lingui extract --clean"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.3", "@emotion/react": "^11.11.4",
"@fontsource/open-sans": "^5.0.20", "@fontsource/open-sans": "^5.0.25",
"@lingui/core": "^4.6.0", "@lingui/core": "^4.7.1",
"@lingui/macro": "^4.6.0", "@lingui/macro": "^4.7.1",
"@lingui/react": "^4.6.0", "@lingui/react": "^4.7.1",
"@mantine/core": "^7.3.2", "@mantine/core": "^7.6.1",
"@mantine/form": "^7.3.2", "@mantine/form": "^7.6.1",
"@mantine/hooks": "^7.3.2", "@mantine/hooks": "^7.6.1",
"@mantine/modals": "^7.3.2", "@mantine/modals": "^7.6.1",
"@mantine/notifications": "^7.3.2", "@mantine/notifications": "^7.6.1",
"@mantine/spotlight": "^7.3.2", "@mantine/spotlight": "^7.6.1",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@reduxjs/toolkit": "^2.0.1", "@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.3", "axios": "^1.6.7",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"interweave": "^13.1.0", "interweave": "^13.1.0",
"monaco-editor": "^0.45.0", "monaco-editor": "^0.46.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-async-hook": "^4.0.0", "react-async-hook": "^4.0.0",
@@ -39,47 +39,44 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.6", "react-draggable": "^4.4.6",
"react-ga4": "^2.1.0", "react-ga4": "^2.1.0",
"react-icons": "^4.12.0", "react-icons": "^5.0.1",
"react-infinite-scroller": "^1.2.6", "react-infinite-scroller": "^1.2.6",
"react-redux": "^9.0.4", "react-redux": "^9.1.0",
"react-router-dom": "^6.21.1", "react-router-dom": "^6.22.2",
"react-swipeable": "^7.0.1", "react-swipeable": "^7.0.1",
"redoc": "^2.1.3", "redoc": "^2.1.3",
"throttle-debounce": "^5.0.0", "throttle-debounce": "^5.0.0",
"tinycon": "^0.6.8", "tinycon": "^0.6.8",
"tss-react": "^4.9.3", "tss-react": "^4.9.4",
"use-local-storage": "^3.0.0", "use-local-storage": "^3.0.0",
"websocket-heartbeat-js": "^1.1.3" "websocket-heartbeat-js": "^1.1.3"
}, },
"devDependencies": { "devDependencies": {
"@lingui/cli": "^4.6.0", "@lingui/cli": "^4.7.1",
"@lingui/vite-plugin": "^4.6.0", "@lingui/vite-plugin": "^4.7.1",
"@types/mousetrap": "^1.6.15", "@types/mousetrap": "^1.6.15",
"@types/react": "^18.2.46", "@types/react": "^18.2.61",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.19",
"@types/react-infinite-scroller": "^1.2.5", "@types/react-infinite-scroller": "^1.2.5",
"@types/swagger-ui-react": "^4.18.3", "@types/swagger-ui-react": "^4.18.3",
"@types/throttle-debounce": "^5.0.2", "@types/throttle-debounce": "^5.0.2",
"@types/tinycon": "^0.6.5", "@types/tinycon": "^0.6.5",
"@typescript-eslint/eslint-plugin": "^6.16.0", "@typescript-eslint/eslint-plugin": "^7.1.0",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"eslint": "^8.56.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "^43.0.0", "eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-n": "^16.5.0", "eslint-plugin-react": "^7.34.0",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^3.1.1", "prettier": "^3.2.5",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^4.5.1", "vite": "^5.1.4",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.3", "vite-tsconfig-paths": "^4.3.1",
"vitest": "^0.34.6", "vitest": "^1.3.1",
"vitest-mock-extended": "^1.3.1" "vitest-mock-extended": "^1.3.1"
} }
} }

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>4.0.0</version> <version>4.3.2</version>
</parent> </parent>
<artifactId>commafeed-client</artifactId> <artifactId>commafeed-client</artifactId>
<name>CommaFeed Client</name> <name>CommaFeed Client</name>
@@ -39,16 +39,6 @@
<arguments>ci</arguments> <arguments>ci</arguments>
</configuration> </configuration>
</execution> </execution>
<execution>
<id>npm run eslint</id>
<goals>
<goal>npm</goal>
</goals>
<phase>compile</phase>
<configuration>
<arguments>run eslint</arguments>
</configuration>
</execution>
<execution> <execution>
<id>npm run test</id> <id>npm run test</id>
<goals> <goals>

View File

@@ -1,7 +1,8 @@
import axios from "axios" import axios, { AxiosError } from "axios"
import { import {
type AddCategoryRequest, type AddCategoryRequest,
type AdminSaveUserRequest, type AdminSaveUserRequest,
AuthenticationError,
type Category, type Category,
type CategoryModificationRequest, type CategoryModificationRequest,
type CollapseRequest, type CollapseRequest,
@@ -31,16 +32,18 @@ const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
response => response, response => response,
error => { error => {
if ( if (isAuthenticationError(error)) {
(error.response.status === 401 && error.response.data === "Credentials are required to access this resource.") || const data = error.response?.data
(error.response.status === 403 && error.response.data === "You don't have the required role to access this resource.") window.location.hash = data?.allowRegistrations ? "/welcome" : "/login"
) {
window.location.hash = "/welcome"
} }
throw error throw error
} }
) )
function isAuthenticationError(error: unknown): error is AxiosError<AuthenticationError> {
return axios.isAxiosError(error) && !!error.response && [401, 403].includes(error.response.status)
}
export const client = { export const client = {
category: { category: {
getRoot: async () => await axiosInstance.get<Category>("category/get"), getRoot: async () => await axiosInstance.get<Category>("category/get"),
@@ -106,14 +109,19 @@ export const client = {
export const errorToStrings = (err: unknown) => { export const errorToStrings = (err: unknown) => {
let strings: string[] = [] let strings: string[] = []
if (axios.isAxiosError(err)) { if (axios.isAxiosError(err) && err.response) {
if (err.response) { if (typeof err.response.data === "string") strings.push(err.response.data)
const { data } = err.response if (isMessageError(err)) strings.push(err.response.data.message)
if (typeof data === "string") strings.push(data) if (isMessageArrayError(err)) strings = [...strings, ...err.response.data.errors]
if (typeof data === "object" && data.message) strings.push(data.message as string)
if (typeof data === "object" && data.errors) strings = [...strings, ...data.errors]
}
} }
return strings return strings
} }
function isMessageError(err: AxiosError): err is AxiosError<{ message: string }> {
return !!err.response && !!err.response.data && typeof err.response.data === "object" && "message" in err.response.data
}
function isMessageArrayError(err: AxiosError): err is AxiosError<{ errors: string[] }> {
return !!err.response && !!err.response.data && typeof err.response.data === "object" && "errors" in err.response.data
}

View File

@@ -89,10 +89,18 @@ export const Constants = {
mobileBreakpointName: "md", mobileBreakpointName: "md",
headerHeight: 60, headerHeight: 60,
entryMaxWidth: 650, entryMaxWidth: 650,
isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight, isTopVisible: (div: HTMLElement) => {
isBottomVisible: (div: HTMLElement) => div.getBoundingClientRect().bottom <= window.innerHeight, const header = document.getElementById(Constants.dom.headerId)?.getBoundingClientRect()
return div.getBoundingClientRect().top >= (header?.bottom ?? 0)
},
isBottomVisible: (div: HTMLElement) => {
const footer = document.getElementById(Constants.dom.footerId)?.getBoundingClientRect()
return div.getBoundingClientRect().bottom <= (footer?.top ?? window.innerHeight)
},
}, },
dom: { dom: {
headerId: "header",
footerId: "footer",
entryId: (entry: Entry) => `entry-id-${entry.id}`, entryId: (entry: Entry) => `entry-id-${entry.id}`,
entryContextMenuId: (entry: Entry) => entry.id, entryContextMenuId: (entry: Entry) => entry.id,
}, },

View File

@@ -1,4 +1,3 @@
/* eslint-disable import/first */
import { configureStore } from "@reduxjs/toolkit" import { configureStore } from "@reduxjs/toolkit"
import { type client } from "app/client" import { type client } from "app/client"
import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "app/entries/thunks" import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "app/entries/thunks"
@@ -90,7 +89,7 @@ describe("entries", () => {
expect(store.getState().entries.hasMore).toBe(false) expect(store.getState().entries.hasMore).toBe(false)
}) })
it("marks an entry as read", async () => { it("marks an entry as read", () => {
const store = configureStore({ const store = configureStore({
reducer: reducers, reducer: reducers,
preloadedState: { preloadedState: {
@@ -117,7 +116,7 @@ describe("entries", () => {
expect(mockClient.entry.mark).toHaveBeenCalledWith({ id: "3", read: true }) expect(mockClient.entry.mark).toHaveBeenCalledWith({ id: "3", read: true })
}) })
it("marks all entries as read", async () => { it("marks all entries as read", () => {
const store = configureStore({ const store = configureStore({
reducer: reducers, reducer: reducers,
preloadedState: { preloadedState: {

View File

@@ -46,11 +46,11 @@ const buildGetEntriesPaginatedRequest = (state: RootState, source: EntrySource,
tag: source.type === "tag" ? source.id : undefined, tag: source.type === "tag" ? source.id : undefined,
keywords: state.entries.search, keywords: state.entries.search,
}) })
export const reloadEntries = createAppAsyncThunk("entries/reload", async (arg, thunkApi) => { export const reloadEntries = createAppAsyncThunk("entries/reload", (arg, thunkApi) => {
const state = thunkApi.getState() const state = thunkApi.getState()
thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false })) thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false }))
}) })
export const search = createAppAsyncThunk("entries/search", async (arg: string, thunkApi) => { export const search = createAppAsyncThunk("entries/search", (arg: string, thunkApi) => {
const state = thunkApi.getState() const state = thunkApi.getState()
thunkApi.dispatch(setSearch(arg)) thunkApi.dispatch(setSearch(arg))
thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false })) thunkApi.dispatch(loadEntries({ source: state.entries.source, clearSearch: false }))
@@ -84,7 +84,7 @@ export const markMultipleEntries = createAppAsyncThunk(
thunkApi.dispatch(reloadTree()) thunkApi.dispatch(reloadTree())
} }
) )
export const markEntriesUpToEntry = createAppAsyncThunk("entries/entry/upToEntry", async (arg: Entry, thunkApi) => { export const markEntriesUpToEntry = createAppAsyncThunk("entries/entry/upToEntry", (arg: Entry, thunkApi) => {
const state = thunkApi.getState() const state = thunkApi.getState()
const { entries } = state.entries const { entries } = state.entries
@@ -162,9 +162,9 @@ export const selectEntry = createAppAsyncThunk(
if (arg.scrollToEntry) { if (arg.scrollToEntry) {
const entryElement = document.getElementById(Constants.dom.entryId(entry)) const entryElement = document.getElementById(Constants.dom.entryId(entry))
if (entryElement) { if (entryElement) {
const alwaysScrollToEntry = state.user.settings?.alwaysScrollToEntry const scrollMode = state.user.settings?.scrollMode
const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement) const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement)
if (alwaysScrollToEntry || !entryEntirelyVisible) { if (scrollMode === "always" || (scrollMode === "if_needed" && !entryEntirelyVisible)) {
const scrollSpeed = state.user.settings?.scrollSpeed const scrollSpeed = state.user.settings?.scrollSpeed
thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true)) thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true))
scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false))) scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false)))
@@ -174,10 +174,11 @@ export const selectEntry = createAppAsyncThunk(
} }
) )
const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefined, onScrollEnded: () => void) => { const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefined, onScrollEnded: () => void) => {
const header = document.getElementById(Constants.dom.headerId)?.getBoundingClientRect()
const offset = (header?.bottom ?? 0) + 3
scrollToWithCallback({ scrollToWithCallback({
options: { options: {
// add a small gap between the top of the content and the top of the page top: entryElement.offsetTop - offset,
top: entryElement.offsetTop - Constants.layout.headerHeight - 3,
behavior: scrollSpeed && scrollSpeed > 0 ? "smooth" : "auto", behavior: scrollSpeed && scrollSpeed > 0 ? "smooth" : "auto",
}, },
onScrollEnded, onScrollEnded,

View File

@@ -1,5 +1,4 @@
import { configureStore } from "@reduxjs/toolkit" import { configureStore } from "@reduxjs/toolkit"
import { setupListeners } from "@reduxjs/toolkit/query"
import { entriesSlice } from "app/entries/slice" import { entriesSlice } from "app/entries/slice"
import { redirectSlice } from "app/redirect/slice" import { redirectSlice } from "app/redirect/slice"
import { serverSlice } from "app/server/slice" import { serverSlice } from "app/server/slice"
@@ -17,8 +16,6 @@ export const reducers = {
export const store = configureStore({ reducer: reducers }) export const store = configureStore({ reducer: reducers })
setupListeners(store.dispatch)
export type RootState = ReturnType<typeof store.getState> export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch export type AppDispatch = typeof store.dispatch

View File

@@ -26,6 +26,22 @@ export const treeSlice = createSlice({
toggleSidebar: state => { toggleSidebar: state => {
state.sidebarVisible = !state.sidebarVisible state.sidebarVisible = !state.sidebarVisible
}, },
incrementUnreadCount: (
state,
action: PayloadAction<{
feedId: number
amount: number
}>
) => {
if (!state.rootCategory) return
visitCategoryTree(state.rootCategory, c =>
c.feeds
.filter(f => f.id === action.payload.feedId)
.forEach(f => {
f.unread += action.payload.amount
})
)
},
}, },
extraReducers: builder => { extraReducers: builder => {
builder.addCase(reloadTree.fulfilled, (state, action) => { builder.addCase(reloadTree.fulfilled, (state, action) => {
@@ -53,4 +69,4 @@ export const treeSlice = createSlice({
}, },
}) })
export const { setMobileMenuOpen, toggleSidebar } = treeSlice.actions export const { setMobileMenuOpen, toggleSidebar, incrementUnreadCount } = treeSlice.actions

View File

@@ -1,8 +1,33 @@
export type ReadingMode = "all" | "unread"
export type ReadingOrder = "asc" | "desc"
export type ViewMode = "title" | "cozy" | "detailed" | "expanded"
export type ScrollMode = "always" | "never" | "if_needed"
export interface AddCategoryRequest { export interface AddCategoryRequest {
name: string name: string
parentId?: string parentId?: string
} }
export interface Subscription {
id: number
name: string
message?: string
errorCount: number
lastRefresh?: number
nextRefresh?: number
feedUrl: string
feedLink: string
iconUrl: string
unread: number
categoryId?: string
position: number
newestItemTime?: number
filter?: string
}
export interface Category { export interface Category {
id: string id: string
parentId?: string parentId?: string
@@ -26,19 +51,6 @@ export interface CollapseRequest {
collapse: boolean collapse: boolean
} }
export interface Entries {
name: string
message?: string
errorCount: number
feedLink: string
timestamp: number
hasMore: boolean
offset?: number
limit?: number
entries: Entry[]
ignoredReadStatus: boolean
}
export interface Entry { export interface Entry {
id: string id: string
guid: string guid: string
@@ -67,6 +79,19 @@ export interface Entry {
tags: string[] tags: string[]
} }
export interface Entries {
name: string
message?: string
errorCount: number
feedLink: string
timestamp: number
hasMore: boolean
offset?: number
limit?: number
entries: Entry[]
ignoredReadStatus: boolean
}
export interface FeedInfo { export interface FeedInfo {
url: string url: string
title: string title: string
@@ -196,21 +221,6 @@ export interface ServerInfo {
treeReloadInterval: number treeReloadInterval: number
} }
export interface Settings {
language: string
readingMode: ReadingMode
readingOrder: ReadingOrder
showRead: boolean
scrollMarks: boolean
customCss?: string
customJs?: string
scrollSpeed: number
alwaysScrollToEntry: boolean
markAllAsReadConfirmation: boolean
customContextMenu: boolean
sharingSettings: SharingSettings
}
export interface SharingSettings { export interface SharingSettings {
email: boolean email: boolean
gmail: boolean gmail: boolean
@@ -222,6 +232,22 @@ export interface SharingSettings {
buffer: boolean buffer: boolean
} }
export interface Settings {
language: string
readingMode: ReadingMode
readingOrder: ReadingOrder
showRead: boolean
scrollMarks: boolean
customCss?: string
customJs?: string
scrollSpeed: number
scrollMode: ScrollMode
markAllAsReadConfirmation: boolean
customContextMenu: boolean
mobileFooter: boolean
sharingSettings: SharingSettings
}
export interface StarRequest { export interface StarRequest {
id: string id: string
feedId: number feedId: number
@@ -234,34 +260,11 @@ export interface SubscribeRequest {
categoryId?: string categoryId?: string
} }
export interface Subscription {
id: number
name: string
message?: string
errorCount: number
lastRefresh?: number
nextRefresh?: number
feedUrl: string
feedLink: string
iconUrl: string
unread: number
categoryId?: string
position: number
newestItemTime?: number
filter?: string
}
export interface TagRequest { export interface TagRequest {
entryId: number entryId: number
tags: string[] tags: string[]
} }
export interface UnreadCount {
feedId?: number
unreadCount?: number
newestItemTime?: number
}
export interface UserModel { export interface UserModel {
id: number id: number
name: string name: string
@@ -283,8 +286,7 @@ export interface AdminSaveUserRequest {
admin: boolean admin: boolean
} }
export type ReadingMode = "all" | "unread" export interface AuthenticationError {
message: string
export type ReadingOrder = "asc" | "desc" allowRegistrations: boolean
}
export type ViewMode = "title" | "cozy" | "detailed" | "expanded"

View File

@@ -3,13 +3,14 @@ import { showNotification } from "@mantine/notifications"
import { createSlice, isAnyOf } from "@reduxjs/toolkit" import { createSlice, isAnyOf } from "@reduxjs/toolkit"
import { type Settings, type UserModel } from "app/types" import { type Settings, type UserModel } from "app/types"
import { import {
changeAlwaysScrollToEntry,
changeCustomContextMenu, changeCustomContextMenu,
changeLanguage, changeLanguage,
changeMarkAllAsReadConfirmation, changeMarkAllAsReadConfirmation,
changeMobileFooter,
changeReadingMode, changeReadingMode,
changeReadingOrder, changeReadingOrder,
changeScrollMarks, changeScrollMarks,
changeScrollMode,
changeScrollSpeed, changeScrollSpeed,
changeSharingSetting, changeSharingSetting,
changeShowRead, changeShowRead,
@@ -64,9 +65,9 @@ export const userSlice = createSlice({
if (!state.settings) return if (!state.settings) return
state.settings.scrollMarks = action.meta.arg state.settings.scrollMarks = action.meta.arg
}) })
builder.addCase(changeAlwaysScrollToEntry.pending, (state, action) => { builder.addCase(changeScrollMode.pending, (state, action) => {
if (!state.settings) return if (!state.settings) return
state.settings.alwaysScrollToEntry = action.meta.arg state.settings.scrollMode = action.meta.arg
}) })
builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => { builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => {
if (!state.settings) return if (!state.settings) return
@@ -76,6 +77,10 @@ export const userSlice = createSlice({
if (!state.settings) return if (!state.settings) return
state.settings.customContextMenu = action.meta.arg state.settings.customContextMenu = action.meta.arg
}) })
builder.addCase(changeMobileFooter.pending, (state, action) => {
if (!state.settings) return
state.settings.mobileFooter = action.meta.arg
})
builder.addCase(changeSharingSetting.pending, (state, action) => { builder.addCase(changeSharingSetting.pending, (state, action) => {
if (!state.settings) return if (!state.settings) return
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
@@ -86,9 +91,10 @@ export const userSlice = createSlice({
changeScrollSpeed.fulfilled, changeScrollSpeed.fulfilled,
changeShowRead.fulfilled, changeShowRead.fulfilled,
changeScrollMarks.fulfilled, changeScrollMarks.fulfilled,
changeAlwaysScrollToEntry.fulfilled, changeScrollMode.fulfilled,
changeMarkAllAsReadConfirmation.fulfilled, changeMarkAllAsReadConfirmation.fulfilled,
changeCustomContextMenu.fulfilled, changeCustomContextMenu.fulfilled,
changeMobileFooter.fulfilled,
changeSharingSetting.fulfilled changeSharingSetting.fulfilled
), ),
() => { () => {

View File

@@ -1,7 +1,7 @@
import { createAppAsyncThunk } from "app/async-thunk" import { createAppAsyncThunk } from "app/async-thunk"
import { client } from "app/client" import { client } from "app/client"
import { reloadEntries } from "app/entries/thunks" import { reloadEntries } from "app/entries/thunks"
import type { ReadingMode, ReadingOrder, SharingSettings } from "app/types" import type { ReadingMode, ReadingOrder, ScrollMode, SharingSettings } from "app/types"
export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data)) export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data))
export const reloadProfile = createAppAsyncThunk("profile/reload", async () => await client.user.getProfile().then(r => r.data)) export const reloadProfile = createAppAsyncThunk("profile/reload", async () => await client.user.getProfile().then(r => r.data))
@@ -38,10 +38,10 @@ export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (sc
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, scrollMarks }) client.user.saveSettings({ ...settings, scrollMarks })
}) })
export const changeAlwaysScrollToEntry = createAppAsyncThunk("settings/alwaysScrollToEntry", (alwaysScrollToEntry: boolean, thunkApi) => { export const changeScrollMode = createAppAsyncThunk("settings/scrollMode", (scrollMode: ScrollMode, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, alwaysScrollToEntry }) client.user.saveSettings({ ...settings, scrollMode })
}) })
export const changeMarkAllAsReadConfirmation = createAppAsyncThunk( export const changeMarkAllAsReadConfirmation = createAppAsyncThunk(
"settings/markAllAsReadConfirmation", "settings/markAllAsReadConfirmation",
@@ -56,6 +56,11 @@ export const changeCustomContextMenu = createAppAsyncThunk("settings/customConte
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, customContextMenu }) 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 changeSharingSetting = createAppAsyncThunk( export const changeSharingSetting = createAppAsyncThunk(
"settings/sharingSetting", "settings/sharingSetting",
( (

View File

@@ -1,5 +1,4 @@
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core" import { Box, Center } from "@mantine/core"
import { useColorScheme } from "hooks/useColorScheme"
import { useState } from "react" import { useState } from "react"
import { TbPhoto } from "react-icons/tb" import { TbPhoto } from "react-icons/tb"
import { tss } from "tss" import { tss } from "tss"
@@ -18,8 +17,6 @@ interface ImageWithPlaceholderWhileLoadingProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme
colorScheme: "light" | "dark"
placeholderWidth?: number placeholderWidth?: number
placeholderHeight?: number placeholderHeight?: number
placeholderBackgroundColor?: string placeholderBackgroundColor?: string
@@ -46,11 +43,7 @@ export function ImageWithPlaceholderWhileLoading({
title, title,
width, width,
}: ImageWithPlaceholderWhileLoadingProps) { }: ImageWithPlaceholderWhileLoadingProps) {
const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme,
colorScheme,
placeholderWidth, placeholderWidth,
placeholderHeight, placeholderHeight,
placeholderBackgroundColor, placeholderBackgroundColor,

View File

@@ -110,7 +110,7 @@ export function KeyboardShortcutsHelp() {
<Table.Td> <Table.Td>
<Kbd>M</Kbd> <Kbd>M</Kbd>
<span>, </span> <span>, </span>
<Trans>Swipe header to the right</Trans> <Trans>Swipe header to the left</Trans>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
<Table.Tr> <Table.Tr>

View File

@@ -6,7 +6,7 @@ import { type ReactNode } from "react"
interface CodeEditorProps { interface CodeEditorProps {
description?: ReactNode description?: ReactNode
language: "css" | "javascript" language: "css" | "javascript"
value: string value?: string
onChange: (value: string | undefined) => void onChange: (value: string | undefined) => void
} }

View File

@@ -27,7 +27,7 @@ const init = async () => {
interface RichCodeEditorProps { interface RichCodeEditorProps {
height: number | string height: number | string
language: "css" | "javascript" language: "css" | "javascript"
value: string value?: string
onChange: (value: string | undefined) => void onChange: (value: string | undefined) => void
} }

View File

@@ -76,7 +76,7 @@ class HighlightMatcher extends Matcher {
return this.doMatch(string, new RegExp(pattern, "i"), () => ({})) return this.doMatch(string, new RegExp(pattern, "i"), () => ({}))
} }
replaceWith(children: ChildrenNode, props: unknown): Node { replaceWith(children: ChildrenNode): Node {
return <Mark>{children}</Mark> return <Mark>{children}</Mark>
} }

View File

@@ -2,14 +2,14 @@ import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) { export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) {
const hasVideo = props.enclosureType?.startsWith("video") const hasVideo = props.enclosureType.startsWith("video")
const hasAudio = props.enclosureType?.startsWith("audio") const hasAudio = props.enclosureType.startsWith("audio")
const hasImage = props.enclosureType?.startsWith("image") const hasImage = props.enclosureType.startsWith("image")
return ( return (
<BasicHtmlStyles> <BasicHtmlStyles>
{hasVideo && ( {hasVideo && (
<video controls> <video controls width="100%">
<source src={props.enclosureUrl} type={props.enclosureType} /> <source src={props.enclosureUrl} type={props.enclosureType} />
</video> </video>
)} )}

View File

@@ -91,7 +91,7 @@ export function FeedEntries() {
) )
} }
const swipedRight = async (entry: ExpendableEntry) => await dispatch(markEntry({ entry, read: !entry.read })) const swipedLeft = async (entry: ExpendableEntry) => await dispatch(markEntry({ entry, read: !entry.read }))
// close context menu on scroll // close context menu on scroll
useEffect(() => { useEffect(() => {
@@ -295,10 +295,10 @@ export function FeedEntries() {
}) })
) )
if (!entries) return <Loader />
return ( return (
<InfiniteScroll <InfiniteScroll
id="entries" id="entries"
className={`view-mode-${viewMode}`}
initialLoad={false} initialLoad={false}
loadMore={async () => await (!loading && dispatch(loadMoreEntries()))} loadMore={async () => await (!loading && dispatch(loadMoreEntries()))}
hasMore={hasMore} hasMore={hasMore}
@@ -320,7 +320,7 @@ export function FeedEntries() {
onHeaderClick={event => headerClicked(entry, event)} onHeaderClick={event => headerClicked(entry, event)}
onHeaderRightClick={event => headerRightClicked(entry, event)} onHeaderRightClick={event => headerRightClicked(entry, event)}
onBodyClick={() => bodyClicked(entry)} onBodyClick={() => bodyClicked(entry)}
onSwipedRight={async () => await swipedRight(entry)} onSwipedLeft={async () => await swipedLeft(entry)}
/> />
</div> </div>
))} ))}

View File

@@ -1,7 +1,6 @@
import { Box, Divider, type MantineRadius, type MantineSpacing, type MantineTheme, Paper, useMantineTheme } from "@mantine/core" import { Box, Divider, type MantineRadius, type MantineSpacing, Paper } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { type Entry, type ViewMode } from "app/types" import { type Entry, type ViewMode } from "app/types"
import { useColorScheme } from "hooks/useColorScheme"
import { useViewMode } from "hooks/useViewMode" import { useViewMode } from "hooks/useViewMode"
import React from "react" import React from "react"
import { useSwipeable } from "react-swipeable" import { useSwipeable } from "react-swipeable"
@@ -21,13 +20,11 @@ interface FeedEntryProps {
onHeaderClick: (e: React.MouseEvent) => void onHeaderClick: (e: React.MouseEvent) => void
onHeaderRightClick: (e: React.MouseEvent) => void onHeaderRightClick: (e: React.MouseEvent) => void
onBodyClick: (e: React.MouseEvent) => void onBodyClick: (e: React.MouseEvent) => void
onSwipedRight: () => void onSwipedLeft: () => void
} }
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme
colorScheme: "light" | "dark"
read: boolean read: boolean
expanded: boolean expanded: boolean
viewMode: ViewMode viewMode: ViewMode
@@ -96,12 +93,8 @@ const useStyles = tss
}) })
export function FeedEntry(props: FeedEntryProps) { export function FeedEntry(props: FeedEntryProps) {
const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { viewMode } = useViewMode() const { viewMode } = useViewMode()
const { classes, cx } = useStyles({ const { classes, cx } = useStyles({
theme,
colorScheme,
read: props.entry.read, read: props.entry.read,
expanded: props.expanded, expanded: props.expanded,
viewMode, viewMode,
@@ -111,7 +104,7 @@ export function FeedEntry(props: FeedEntryProps) {
}) })
const swipeHandlers = useSwipeable({ const swipeHandlers = useSwipeable({
onSwipedRight: props.onSwipedRight, onSwipedLeft: props.onSwipedLeft,
}) })
let paddingX: MantineSpacing = "xs" let paddingX: MantineSpacing = "xs"

View File

@@ -2,7 +2,6 @@ import { Box, Text } from "@mantine/core"
import { type Entry } from "app/types" import { type Entry } from "app/types"
import { RelativeDate } from "components/RelativeDate" import { RelativeDate } from "components/RelativeDate"
import { OnDesktop } from "components/responsive/OnDesktop" import { OnDesktop } from "components/responsive/OnDesktop"
import { useColorScheme } from "hooks/useColorScheme"
import { tss } from "tss" import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle" import { FeedEntryTitle } from "./FeedEntryTitle"
import { FeedFavicon } from "./FeedFavicon" import { FeedFavicon } from "./FeedFavicon"
@@ -13,7 +12,6 @@ export interface FeedEntryHeaderProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
colorScheme: "light" | "dark"
read: boolean read: boolean
}>() }>()
.create(({ colorScheme, read }) => ({ .create(({ colorScheme, read }) => ({
@@ -42,9 +40,7 @@ const useStyles = tss
})) }))
export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) { export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
colorScheme,
read: props.entry.read, read: props.entry.read,
}) })
return ( return (

View File

@@ -1,5 +1,5 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Group, type MantineTheme, useMantineTheme } from "@mantine/core" import { Group } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { markEntriesUpToEntry, markEntry, starEntry } from "app/entries/thunks" import { markEntriesUpToEntry, markEntry, starEntry } from "app/entries/thunks"
import { redirectToFeed } from "app/redirect/thunks" import { redirectToFeed } from "app/redirect/thunks"
@@ -17,28 +17,19 @@ interface FeedEntryContextMenuProps {
} }
const iconSize = 16 const iconSize = 16
const useStyles = tss const useStyles = tss.create(({ theme, colorScheme }) => ({
.withParams<{ menu: {
theme: MantineTheme // apply mantine theme from MenuItem.styles.ts
colorScheme: "light" | "dark" fontSize: theme.fontSizes.sm,
}>() "--contexify-item-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
.create(({ theme, colorScheme }) => ({ "--contexify-activeItem-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
menu: { "--contexify-activeItem-bgColor": `${colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]} !important`,
// apply mantine theme from MenuItem.styles.ts },
fontSize: theme.fontSizes.sm, }))
"--contexify-item-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
"--contexify-activeItem-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
"--contexify-activeItem-bgColor": `${colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]} !important`,
},
}))
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) { export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
const theme = useMantineTheme()
const colorScheme = useColorScheme() const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles()
theme,
colorScheme,
})
const sourceType = useAppSelector(state => state.entries.source.type) const sourceType = useAppSelector(state => state.entries.source.type)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { openLinkInBackgroundTab } = useBrowserExtension() const { openLinkInBackgroundTab } = useBrowserExtension()

View File

@@ -1,7 +1,6 @@
import { Box, Space, Text } from "@mantine/core" import { Box, Space, Text } from "@mantine/core"
import { type Entry } from "app/types" import { type Entry } from "app/types"
import { RelativeDate } from "components/RelativeDate" import { RelativeDate } from "components/RelativeDate"
import { useColorScheme } from "hooks/useColorScheme"
import { tss } from "tss" import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle" import { FeedEntryTitle } from "./FeedEntryTitle"
import { FeedFavicon } from "./FeedFavicon" import { FeedFavicon } from "./FeedFavicon"
@@ -13,7 +12,6 @@ export interface FeedEntryHeaderProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
colorScheme: "light" | "dark"
read: boolean read: boolean
}>() }>()
.create(({ colorScheme, read }) => ({ .create(({ colorScheme, read }) => ({
@@ -28,9 +26,7 @@ const useStyles = tss
})) }))
export function FeedEntryHeader(props: FeedEntryHeaderProps) { export function FeedEntryHeader(props: FeedEntryHeaderProps) {
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
colorScheme,
read: props.entry.read, read: props.entry.read,
}) })
return ( return (

View File

@@ -1,8 +1,7 @@
import { ActionIcon, Box, type MantineTheme, SimpleGrid, useMantineTheme } from "@mantine/core" import { ActionIcon, Box, SimpleGrid } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
import { type SharingSettings } from "app/types" import { type SharingSettings } from "app/types"
import { useColorScheme } from "hooks/useColorScheme"
import { type IconType } from "react-icons" import { type IconType } from "react-icons"
import { tss } from "tss" import { tss } from "tss"
@@ -10,8 +9,6 @@ type Color = `#${string}`
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme
colorScheme: "light" | "dark"
color: Color color: Color
}>() }>()
.create(({ theme, colorScheme, color }) => ({ .create(({ theme, colorScheme, color }) => ({
@@ -23,11 +20,7 @@ const useStyles = tss
})) }))
function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) { function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) {
const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme,
colorScheme,
color, color,
}) })
@@ -54,7 +47,7 @@ export function ShareButtons(props: { url: string; description: string }) {
return ( return (
<SimpleGrid cols={4}> <SimpleGrid cols={4}>
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>) {(Object.keys(Constants.sharing) as (keyof SharingSettings)[])
.filter(site => sharingSettings?.[site]) .filter(site => sharingSettings?.[site])
.map(site => ( .map(site => (
<ShareButton <ShareButton

View File

@@ -3,6 +3,7 @@ import { Select, type SelectProps } from "@mantine/core"
import { type ComboboxItem } from "@mantine/core/lib/components/Combobox/Combobox.types" import { type ComboboxItem } from "@mantine/core/lib/components/Combobox/Combobox.types"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
import { type Category } from "app/types"
import { flattenCategoryTree } from "app/utils" import { flattenCategoryTree } from "app/utils"
type CategorySelectProps = Partial<SelectProps> & { type CategorySelectProps = Partial<SelectProps> & {
@@ -13,14 +14,32 @@ type CategorySelectProps = Partial<SelectProps> & {
export function CategorySelect(props: CategorySelectProps) { export function CategorySelect(props: CategorySelectProps) {
const rootCategory = useAppSelector(state => state.tree.rootCategory) const rootCategory = useAppSelector(state => state.tree.rootCategory)
const categories = rootCategory && flattenCategoryTree(rootCategory) const categories = rootCategory && flattenCategoryTree(rootCategory)
const categoriesById = categories?.reduce((map, c) => {
map.set(c.id, c)
return map
}, new Map<string, Category>())
const categoryLabel = (cat: Category) => {
let label = cat.name
while (cat.parentId) {
const parent = categoriesById?.get(cat.parentId)
if (!parent) {
break
}
label = `${parent.name}${label}`
cat = parent
}
return label
}
const selectData: ComboboxItem[] | undefined = categories const selectData: ComboboxItem[] | undefined = categories
?.filter(c => c.id !== Constants.categories.all.id) ?.filter(c => c.id !== Constants.categories.all.id)
.filter(c => !props.withoutCategoryIds?.includes(c.id)) .filter(c => !props.withoutCategoryIds?.includes(c.id))
.sort((c1, c2) => c1.name.localeCompare(c2.name))
.map(c => ({ .map(c => ({
label: c.parentName ? t`${c.name} (in ${c.parentName})` : c.name, label: categoryLabel(c),
value: c.id, value: c.id,
})) }))
.sort((c1, c2) => c1.label.localeCompare(c2.label))
if (props.withAll) { if (props.withAll) {
selectData?.unshift({ selectData?.unshift({
label: t`All`, label: t`All`,

View File

@@ -14,7 +14,7 @@ export function ImportOpml() {
const form = useForm<{ file: File }>({ const form = useForm<{ file: File }>({
validate: { validate: {
file: v => (v ? null : t`file is required`), file: () => t`file is required`,
}, },
}) })

View File

@@ -1,31 +1,40 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core" import { Divider, Group, Radio, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { type SharingSettings } from "app/types" import { type ScrollMode, type SharingSettings } from "app/types"
import { import {
changeAlwaysScrollToEntry,
changeCustomContextMenu, changeCustomContextMenu,
changeLanguage, changeLanguage,
changeMarkAllAsReadConfirmation, changeMarkAllAsReadConfirmation,
changeMobileFooter,
changeScrollMarks, changeScrollMarks,
changeScrollMode,
changeScrollSpeed, changeScrollSpeed,
changeSharingSetting, changeSharingSetting,
changeShowRead, changeShowRead,
} from "app/user/thunks" } from "app/user/thunks"
import { locales } from "i18n" import { locales } from "i18n"
import { type ReactNode } from "react"
export function DisplaySettings() { export function DisplaySettings() {
const language = useAppSelector(state => state.user.settings?.language) const language = useAppSelector(state => state.user.settings?.language)
const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed) const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed)
const showRead = useAppSelector(state => state.user.settings?.showRead) const showRead = useAppSelector(state => state.user.settings?.showRead)
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks) const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
const alwaysScrollToEntry = useAppSelector(state => state.user.settings?.alwaysScrollToEntry) const scrollMode = useAppSelector(state => state.user.settings?.scrollMode)
const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation) const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation)
const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu) const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu)
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings) const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const scrollModeOptions: Record<ScrollMode, ReactNode> = {
always: <Trans>Always</Trans>,
never: <Trans>Never</Trans>,
if_needed: <Trans>If the entry doesn't entirely fit on the screen</Trans>,
}
return ( return (
<Stack> <Stack>
<Select <Select
@@ -38,30 +47,12 @@ export function DisplaySettings() {
onChange={async s => await (s && dispatch(changeLanguage(s)))} onChange={async s => await (s && dispatch(changeLanguage(s)))}
/> />
<Switch
label={<Trans>Scroll smoothly when navigating between entries</Trans>}
checked={scrollSpeed ? scrollSpeed > 0 : false}
onChange={async e => await dispatch(changeScrollSpeed(e.currentTarget.checked))}
/>
<Switch
label={<Trans>Always scroll selected entry to the top of the page, even if it fits entirely on screen</Trans>}
checked={alwaysScrollToEntry}
onChange={async e => await dispatch(changeAlwaysScrollToEntry(e.currentTarget.checked))}
/>
<Switch <Switch
label={<Trans>Show feeds and categories with no unread entries</Trans>} label={<Trans>Show feeds and categories with no unread entries</Trans>}
checked={showRead} checked={showRead}
onChange={async e => await dispatch(changeShowRead(e.currentTarget.checked))} onChange={async e => await dispatch(changeShowRead(e.currentTarget.checked))}
/> />
<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))}
/>
<Switch <Switch
label={<Trans>Show confirmation when marking all entries as read</Trans>} label={<Trans>Show confirmation when marking all entries as read</Trans>}
checked={markAllAsReadConfirmation} checked={markAllAsReadConfirmation}
@@ -74,10 +65,42 @@ export function DisplaySettings() {
onChange={async e => await dispatch(changeCustomContextMenu(e.currentTarget.checked))} onChange={async e => await dispatch(changeCustomContextMenu(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))}
/>
<Divider label={<Trans>Scrolling</Trans>} labelPosition="center" />
<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))}
>
<Group mt="xs">
{Object.entries(scrollModeOptions).map(e => (
<Radio key={e[0]} value={e[0]} label={e[1]} />
))}
</Group>
</Radio.Group>
<Switch
label={<Trans>Scroll smoothly when navigating between entries</Trans>}
checked={scrollSpeed ? scrollSpeed > 0 : false}
onChange={async e => await dispatch(changeScrollSpeed(e.currentTarget.checked))}
/>
<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))}
/>
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" /> <Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
<SimpleGrid cols={2}> <SimpleGrid cols={2}>
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>).map(site => ( {(Object.keys(Constants.sharing) as (keyof SharingSettings)[]).map(site => (
<Switch <Switch
key={site} key={site}
label={Constants.sharing[site].label} label={Constants.sharing[site].label}

View File

@@ -78,7 +78,17 @@ export function ProfileSettings() {
<form onSubmit={form.onSubmit(saveProfile.execute)}> <form onSubmit={form.onSubmit(saveProfile.execute)}>
<Stack> <Stack>
<TextInput label={<Trans>User name</Trans>} readOnly value={profile?.name} /> <TextInput label={<Trans>User name</Trans>} readOnly value={profile?.name} />
<TextInput label={<Trans>API key</Trans>} readOnly value={profile?.apiKey} /> <TextInput
label={<Trans>API key</Trans>}
description={
<Trans>
This is your API key. It can be used for some read-only API operations and grants access to the Fever API.
Use the form at the bottom of the page to generate a new API key
</Trans>
}
readOnly
value={profile?.apiKey}
/>
<Input.Wrapper <Input.Wrapper
label={<Trans>OPML export</Trans>} label={<Trans>OPML export</Trans>}
@@ -100,7 +110,7 @@ export function ProfileSettings() {
description={ description={
<Trans> <Trans>
CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client.
The username is your user name and the password is your API key. Login with your username and your <u>API key</u>.
</Trans> </Trans>
} }
> >

View File

@@ -1,6 +1,5 @@
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core" import { Box, Center } from "@mantine/core"
import { FeedFavicon } from "components/content/FeedFavicon" import { FeedFavicon } from "components/content/FeedFavicon"
import { useColorScheme } from "hooks/useColorScheme"
import React, { type ReactNode } from "react" import React, { type ReactNode } from "react"
import { tss } from "tss" import { tss } from "tss"
import { UnreadCount } from "./UnreadCount" import { UnreadCount } from "./UnreadCount"
@@ -20,8 +19,6 @@ interface TreeNodeProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme
colorScheme: "dark" | "light"
selected: boolean selected: boolean
hasError: boolean hasError: boolean
hasUnread: boolean hasUnread: boolean
@@ -60,11 +57,7 @@ const useStyles = tss
}) })
export function TreeNode(props: TreeNodeProps) { export function TreeNode(props: TreeNodeProps) {
const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme,
colorScheme,
selected: props.selected, selected: props.selected,
hasError: props.hasError, hasError: props.hasError,
hasUnread: props.unread > 0, hasUnread: props.unread > 0,

View File

@@ -16,13 +16,13 @@ export function TreeSearch(props: TreeSearchProps) {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const actions: SpotlightActionData[] = props.feeds const actions: SpotlightActionData[] = props.feeds
.toSorted((f1, f2) => f1.name.localeCompare(f2.name))
.map(f => ({ .map(f => ({
id: `${f.id}`, id: `${f.id}`,
label: f.name, label: f.name,
leftSection: <FeedFavicon url={f.iconUrl} />, leftSection: <FeedFavicon url={f.iconUrl} />,
onClick: async () => await dispatch(redirectToFeed(f.id)), onClick: async () => await dispatch(redirectToFeed(f.id)),
})) }))
.sort((f1, f2) => f1.label.localeCompare(f2.label))
const searchIcon = <TbSearch size={18} /> const searchIcon = <TbSearch size={18} />
const rightSection = ( const rightSection = (

View File

@@ -1,4 +1,20 @@
import { useComputedColorScheme } from "@mantine/core"
// the color scheme to use to render components // the color scheme to use to render components
export const useColorScheme = () => useComputedColorScheme("light") import { useMantineColorScheme } from "@mantine/core"
import { useMediaQuery } from "@mantine/hooks"
export const useColorScheme = () => {
const systemColorScheme = useMediaQuery(
"(prefers-color-scheme: dark)",
// passing undefined will use window.matchMedia(query) as default value
undefined,
{
// get initial value synchronously and not in useEffect to avoid flash of light theme
getInitialValueInEffect: false,
}
)
? "dark"
: "light"
const { colorScheme } = useMantineColorScheme()
return colorScheme === "auto" ? systemColorScheme : colorScheme
}

View File

@@ -1,9 +1,22 @@
import { setWebSocketConnected } from "app/server/slice" import { setWebSocketConnected } from "app/server/slice"
import { useAppDispatch, useAppSelector } from "app/store" import { type AppDispatch, useAppDispatch, useAppSelector } from "app/store"
import { reloadTree } from "app/tree/thunks" import { incrementUnreadCount } from "app/tree/slice"
import { useEffect } from "react" import { useEffect } from "react"
import WebsocketHeartbeatJs from "websocket-heartbeat-js" import WebsocketHeartbeatJs from "websocket-heartbeat-js"
const handleMessage = (dispatch: AppDispatch, message: string) => {
const parts = message.split(":")
const type = parts[0]
if (type === "new-feed-entries") {
dispatch(
incrementUnreadCount({
feedId: +parts[1],
amount: +parts[2],
})
)
}
}
export const useWebSocket = () => { export const useWebSocket = () => {
const websocketEnabled = useAppSelector(state => state.server.serverInfos?.websocketEnabled) const websocketEnabled = useAppSelector(state => state.server.serverInfos?.websocketEnabled)
const websocketPingInterval = useAppSelector(state => state.server.serverInfos?.websocketPingInterval) const websocketPingInterval = useAppSelector(state => state.server.serverInfos?.websocketPingInterval)
@@ -25,9 +38,8 @@ export const useWebSocket = () => {
ws.onopen = () => dispatch(setWebSocketConnected(true)) ws.onopen = () => dispatch(setWebSocketConnected(true))
ws.onclose = () => dispatch(setWebSocketConnected(false)) ws.onclose = () => dispatch(setWebSocketConnected(false))
ws.onmessage = event => { ws.onmessage = event => {
const { data } = event if (typeof event.data === "string") {
if (typeof data === "string") { handleMessage(dispatch, event.data)
if (data.startsWith("new-feed-entries:")) dispatch(reloadTree())
} }
} }
} }

View File

@@ -44,8 +44,8 @@ export const locales: Locale[] = [
function activateLocale(locale: string) { function activateLocale(locale: string) {
// lingui // lingui
import(`./locales/${locale}/messages.po`).then(data => { import(`./locales/${locale}/messages.po`).then((data: { messages: Messages }) => {
i18n.load(locale, data.messages as Messages) i18n.load(locale, data.messages)
i18n.activate(locale) i18n.activate(locale)
}) })

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "الكل" msgstr "الكل"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "المرجع نفسه"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "إذا لم يكن فارغًا ، فسيتم تقييم التعبير إلى \"صواب\" أو \"خطأ\". " msgstr "إذا لم يكن فارغًا ، فسيتم تقييم التعبير إلى \"صواب\" أو \"خطأ\". "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "إذا واجهت مشكلة ، فالرجاء الإبلاغ عنها على صفحة مشكلات مشروع GitHub." msgstr "إذا واجهت مشكلة ، فالرجاء الإبلاغ عنها على صفحة مشكلات مشروع GitHub."
@@ -545,6 +545,10 @@ msgstr "الاسم"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "انتقل إلى اشتراك بإدخال اسمه" msgstr "انتقل إلى اشتراك بإدخال اسمه"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "كلمة مرور جديدة" msgstr "كلمة مرور جديدة"
@@ -578,6 +582,10 @@ msgstr "لم يتم العثور على شيء"
msgid "Oldest first" msgid "Oldest first"
msgstr "الأقدم أولا" msgstr "الأقدم أولا"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "اوووه!" msgstr "اوووه!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "حفظ" msgstr "حفظ"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "قم بالتمرير بسلاسة عند التنقل بين الإدخالات" msgstr "قم بالتمرير بسلاسة عند التنقل بين الإدخالات"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "النجاح" msgstr "النجاح"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "عنوان URL للتغذية التي تريد الاشتراك فيه
msgid "Theme" msgid "Theme"
msgstr "الموضوع" msgstr "الموضوع"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "تبديل قراءة حالة الإدخال الحالي" msgstr "تبديل قراءة حالة الإدخال الحالي"

View File

@@ -13,17 +13,13 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr "<0>CommaFeed és un projecte de codi obert. El codi font està allotjat a </0><1>GitHub</1>."
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "<0>Complete syntax is available </0><1>here</1>." msgid "<0>Complete syntax is available </0><1>here</1>."
msgstr "" msgstr "<0>La sintaxi completa està disponible </0><1>aquí</1>."
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
msgid "<0>Have an account?</0><1>Log in!</1>" msgid "<0>Have an account?</0><1>Log in!</1>"
@@ -31,7 +27,7 @@ msgstr "<0>Teniu un compte?</0><1>Inicieu la sessió!</1>"
#: src/pages/app/DonatePage.tsx #: src/pages/app/DonatePage.tsx
msgid "<0>Hey,</0><1>I'm Jérémie from Belgium and I've been working on CommaFeed in my free time for over 10 years now. Thanks for taking an interest in helping me continue supporting CommaFeed.</1>" msgid "<0>Hey,</0><1>I'm Jérémie from Belgium and I've been working on CommaFeed in my free time for over 10 years now. Thanks for taking an interest in helping me continue supporting CommaFeed.</1>"
msgstr "" msgstr "<0>Ei,</0><1> sóc la Jérémie de Bèlgica i fa més de 10 anys que treballo a CommaFeed en el meu temps lliure. Gràcies per interessar-te i ajudar-me a continuar donant suport a CommaFeed.</1>"
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "<0>Need an account?</0><1>Sign up!</1>" msgid "<0>Need an account?</0><1>Sign up!</1>"
@@ -72,8 +68,8 @@ msgid "All"
msgstr "Tot" msgstr "Tot"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr "Sempre"
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
msgid "An email has been sent if this address was registered. Check your inbox." msgid "An email has been sent if this address was registered. Check your inbox."
@@ -89,7 +85,7 @@ msgstr "Analitzar el feed"
#: src/components/AnnouncementDialog.tsx #: src/components/AnnouncementDialog.tsx
msgid "Announcement" msgid "Announcement"
msgstr "" msgstr "Anunci"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "API key" msgid "API key"
@@ -121,7 +117,7 @@ msgstr "Estàs segur que vols cancel·lar la subscripció a <0>{feedName}</0>?"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Asc" msgid "Asc"
msgstr "" msgstr "Asc"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Available variables are 'title', 'content', 'url' 'author' and 'categories' and their content is converted to lower case to ease string comparison." msgid "Available variables are 'title', 'content', 'url' 'author' and 'categories' and their content is converted to lower case to ease string comparison."
@@ -137,11 +133,11 @@ msgstr "Tornar a iniciar sessió"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Browser extension required for Chrome" msgid "Browser extension required for Chrome"
msgstr "" msgstr "Extensió del navegador necessària per a Chrome"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Browser extention" msgid "Browser extention"
msgstr "" msgstr "Extensió del navegador"
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/content/add/AddCategory.tsx #: src/components/content/add/AddCategory.tsx
@@ -177,15 +173,15 @@ msgstr "Comproveu que el canal funciona"
#: src/pages/app/Layout.tsx #: src/pages/app/Layout.tsx
msgid "Close menu" msgid "Close menu"
msgstr "" msgstr "Tanca el menu"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}." msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr "Versió de l'extensió del navegador CommaFeed {browserExtensionVersion}."
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr "CommaFeed és compatible amb l'API Fever. Utilitzeu l'URL següent al vostre client mòbil compatible amb Fever. Inicieu sessió amb el vostre nom d'usuari i la vostra <0>clau API</0>."
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
@@ -193,7 +189,7 @@ msgstr "CommaFeed següent element no llegit"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})." msgid "CommaFeed version {version} ({revision})."
msgstr "" msgstr "CommaFeed versió {version} ({version})."
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -205,7 +201,7 @@ msgstr "Compacte"
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Confirm" msgid "Confirm"
msgstr "Confirmar" msgstr "Confirma"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "Confirm password" msgid "Confirm password"
@@ -217,7 +213,7 @@ msgstr "Acollidor"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Ctrl" msgid "Ctrl"
msgstr "" msgstr "Ctrl"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "Current password" msgid "Current password"
@@ -225,19 +221,19 @@ msgstr "Contrasenya actual"
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Custom code" msgid "Custom code"
msgstr "" msgstr "Codi personalitzat"
#: src/components/settings/CustomCodeSettings.tsx #: src/components/settings/CustomCodeSettings.tsx
msgid "Custom CSS rules that will be applied" msgid "Custom CSS rules that will be applied"
msgstr "" msgstr "Regles CSS personalitzades que s'aplicaran"
#: src/components/settings/CustomCodeSettings.tsx #: src/components/settings/CustomCodeSettings.tsx
msgid "Custom JS code that will be executed on page load" msgid "Custom JS code that will be executed on page load"
msgstr "" msgstr "Codi JS personalitzat que s'executarà en carregar la pàgina"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Dark" msgid "Dark"
msgstr "" msgstr "Fosc"
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
@@ -262,11 +258,11 @@ msgstr "Suprimeix l'usuari"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Desc" msgid "Desc"
msgstr "" msgstr "Desc"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Detailed" msgid "Detailed"
msgstr "" msgstr "Detallat"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
@@ -276,7 +272,7 @@ msgstr "Mostra"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/DonatePage.tsx #: src/pages/app/DonatePage.tsx
msgid "Donate" msgid "Donate"
msgstr "" msgstr "Donar"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "Download" msgid "Download"
@@ -318,7 +314,7 @@ msgstr "introduïu la vostra contrasenya actual per canviar la configuració del
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "" msgstr "Error"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Example: {example}." msgid "Example: {example}."
@@ -335,7 +331,7 @@ msgstr "exporteu les vostres subscripcions i categories com a fitxer OPML que es
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
msgid "Extension options" msgid "Extension options"
msgstr "" msgstr "Opcions de l'extensió"
#: src/components/content/add/Subscribe.tsx #: src/components/content/add/Subscribe.tsx
msgid "Feed name" msgid "Feed name"
@@ -349,7 +345,7 @@ msgstr "URL del canal"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now" msgid "Fetch all my feeds now"
msgstr "" msgstr "Carrega tots els meus feeds ara"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "Fever API" msgid "Fever API"
@@ -389,7 +385,7 @@ msgstr "URL del feed generat"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}" msgid "Go to {0}"
msgstr "" msgstr "Vés a {0}"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Go to the All view" msgid "Go to the All view"
@@ -405,12 +401,16 @@ msgstr "Bones"
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Id" msgid "Id"
msgstr "" msgstr "Id"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Si no està buida, una expressió que s'avalua com a \"vertader\" o \"fals\". " msgstr "Si no està buida, una expressió que s'avalua com a \"vertader\" o \"fals\". "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr "Si l'entrada no encaixa del tot a la pantalla"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Si trobeu un problema, informeu-lo a la pàgina de problemes del projecte GitHub." msgstr "Si trobeu un problema, informeu-lo a la pàgina de problemes del projecte GitHub."
@@ -451,7 +451,7 @@ msgstr "últim missatge d'actualització"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Light" msgid "Light"
msgstr "" msgstr "Clar"
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
@@ -519,7 +519,7 @@ msgstr "mètriques"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click" msgid "Middle click"
msgstr "" msgstr "Clic central"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
@@ -545,6 +545,10 @@ msgstr "Nom"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navegueu a una subscripció introduint-ne el nom" msgstr "Navegueu a una subscripció introduint-ne el nom"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr "Mai"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Contrasenya nova" msgstr "Contrasenya nova"
@@ -578,13 +582,17 @@ msgstr "No s'ha trobat res"
msgid "Oldest first" msgid "Oldest first"
msgstr "el més vell primer" msgstr "el més vell primer"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "Al mòbil, mostra els botons d'acció a la part inferior de la pantalla"
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Vaja!" msgstr "Vaja!"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Open CommaFeed" msgid "Open CommaFeed"
msgstr "" msgstr "Obre CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab" msgid "Open current entry in a new tab"
@@ -596,19 +604,19 @@ msgstr "Obre l'entrada actual en una pestanya nova al fons"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Open link" msgid "Open link"
msgstr "Enllaç obert" msgstr "Obre l'enllaç obert"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab" msgid "Open link in new background tab"
msgstr "" msgstr "Obre l'enllaç a una pestanya de fons nova"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab" msgid "Open link in new tab"
msgstr "" msgstr "Obre l'enllaç en una pestanya nova"
#: src/pages/app/Layout.tsx #: src/pages/app/Layout.tsx
msgid "Open menu" msgid "Open menu"
msgstr "" msgstr "Obre el menú"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
@@ -624,7 +632,7 @@ msgstr "Obrir/tancar l'entrada actual"
#: src/pages/app/AddPage.tsx #: src/pages/app/AddPage.tsx
msgid "OPML" msgid "OPML"
msgstr "" msgstr "OPML"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "OPML export" msgid "OPML export"
@@ -670,7 +678,7 @@ msgstr "Posició"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Previous" msgid "Previous"
msgstr "" msgstr "Anterior"
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Profile" msgid "Profile"
@@ -696,7 +704,7 @@ msgstr "API REST"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click" msgid "Right click"
msgstr "" msgstr "Clic dret"
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/CustomCodeSettings.tsx #: src/components/settings/CustomCodeSettings.tsx
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Desa" msgstr "Desa"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr "Desplaceu-vos per l'entrada seleccionada fins a la part superior de la pàgina"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Desplaceu-vos suaument quan navegueu entre entrades" msgstr "Desplaceu-vos suaument quan navegueu entre entrades"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr "Desplaçament"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -753,19 +769,19 @@ msgstr "canvi"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show CommaFeed's own context menu on right click" msgid "Show CommaFeed's own context menu on right click"
msgstr "" msgstr "Mostra el menú contextual de CommaFeed fent clic amb el botó dret"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show confirmation when marking all entries as read" msgid "Show confirmation when marking all entries as read"
msgstr "" msgstr "Mostra la confirmació en marcar totes les entrades com a llegides"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)" msgid "Show entry menu (desktop)"
msgstr "" msgstr "Mostra el menú d'entrada (escriptori)"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr "Mostra el menú d'entrada (mòbil)"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
@@ -777,13 +793,13 @@ msgstr "Mostra l'ajuda de la drecera del teclat"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr "Mostra el menú natiu (escriptori)"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Inscriu-te" msgstr "Registra't"
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Something bad just happened..." msgid "Something bad just happened..."
@@ -823,8 +839,8 @@ msgid "Success"
msgstr "Éxit" msgstr "Éxit"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr "Feu lliscar la capçalera cap a l'esquerra"
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
@@ -836,7 +852,7 @@ msgstr "Canvia al tema clar"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "System" msgid "System"
msgstr "" msgstr "Sistema"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
@@ -851,17 +867,21 @@ msgstr "l'URL del canal al qual us voleu subscriure. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr "Aquesta és la vostra clau de l'API. Es pot utilitzar per a algunes operacions de l'API de només lectura i permet accedir a l'API Fever. Utilitzeu el formulari de la part inferior de la pàgina per generar una nova clau d'API."
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Canvia l'estat de lectura de l'entrada actual" msgstr "Canvia l'estat de lectura de l'entrada actual"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle sidebar" msgid "Toggle sidebar"
msgstr "" msgstr "Canvia la barra lateral"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle starred status of current entry" msgid "Toggle starred status of current entry"
msgstr "" msgstr "Commuta l'estat destacat de l'entrada actual"
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
@@ -869,7 +889,7 @@ msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo"
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
msgid "Try the demo!" msgid "Try the demo!"
msgstr "" msgstr "Prova la demostració!"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
@@ -908,4 +928,4 @@ msgstr "Encara no teniu cap subscripció. "
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh." msgid "Your feeds have been queued for refresh."
msgstr "" msgstr "Els vostres feeds s'han posat a la cua per actualitzar-los."

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Všechny" msgstr "Všechny"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Pokud není prázdný, výraz vyhodnocený jako 'true' nebo 'false'. " msgstr "Pokud není prázdný, výraz vyhodnocený jako 'true' nebo 'false'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Pokud narazíte na problém, nahlaste jej prosím na stránce problémů projektu GitHub." msgstr "Pokud narazíte na problém, nahlaste jej prosím na stránce problémů projektu GitHub."
@@ -545,6 +545,10 @@ msgstr "Jméno"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Přejděte na předplatné zadáním jeho názvu" msgstr "Přejděte na předplatné zadáním jeho názvu"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nové heslo" msgstr "Nové heslo"
@@ -578,6 +582,10 @@ msgstr "Nic nebylo nalezeno"
msgid "Oldest first" msgid "Oldest first"
msgstr "Nejdříve nejstarší" msgstr "Nejdříve nejstarší"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Jejda!" msgstr "Jejda!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Uložit" msgstr "Uložit"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Posouvejte plynule při navigaci mezi položkami" msgstr "Posouvejte plynule při navigaci mezi položkami"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Úspěch" msgstr "Úspěch"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "Adresa URL kanálu, k jehož odběru se chcete přihlásit. "
msgid "Theme" msgid "Theme"
msgstr "Téma" msgstr "Téma"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Přepne stav čtení aktuálního záznamu" msgstr "Přepne stav čtení aktuálního záznamu"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Pawb" msgstr "Pawb"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Os nad yw'n wag, mynegiad sy'n gwerthuso i 'gwir' neu 'anghywir'. " msgstr "Os nad yw'n wag, mynegiad sy'n gwerthuso i 'gwir' neu 'anghywir'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Os byddwch yn dod ar draws mater, rhowch wybod amdano ar dudalen materion y prosiect GitHub." msgstr "Os byddwch yn dod ar draws mater, rhowch wybod amdano ar dudalen materion y prosiect GitHub."
@@ -545,6 +545,10 @@ msgstr "Enw"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Llywiwch i danysgrifiad trwy nodi ei enw" msgstr "Llywiwch i danysgrifiad trwy nodi ei enw"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Cyfrinair newydd" msgstr "Cyfrinair newydd"
@@ -578,6 +582,10 @@ msgstr "Dim wedi'i ddarganfod"
msgid "Oldest first" msgid "Oldest first"
msgstr "Hynaf yn gyntaf" msgstr "Hynaf yn gyntaf"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Wps!" msgstr "Wps!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Arbed" msgstr "Arbed"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Sgroliwch yn esmwyth wrth lywio rhwng cofnodion" msgstr "Sgroliwch yn esmwyth wrth lywio rhwng cofnodion"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Llwyddiant" msgstr "Llwyddiant"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "Y URL ar gyfer y porthwr rydych chi am danysgrifio iddo. "
msgid "Theme" msgid "Theme"
msgstr "Thema" msgstr "Thema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Toglo statws darllen y cofnod cyfredol" msgstr "Toglo statws darllen y cofnod cyfredol"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Alle" msgstr "Alle"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Hvis det ikke er tomt, et udtryk, der vurderes til 'sand' eller 'falsk'. " msgstr "Hvis det ikke er tomt, et udtryk, der vurderes til 'sand' eller 'falsk'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Hvis du støder på et problem, bedes du rapportere det på problemsiden for GitHub-projektet." msgstr "Hvis du støder på et problem, bedes du rapportere det på problemsiden for GitHub-projektet."
@@ -545,6 +545,10 @@ msgstr "Navn"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Naviger til et abonnement ved at indtaste dets navn" msgstr "Naviger til et abonnement ved at indtaste dets navn"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Ny adgangskode" msgstr "Ny adgangskode"
@@ -578,6 +582,10 @@ msgstr "Intet fundet"
msgid "Oldest first" msgid "Oldest first"
msgstr "Ældst først" msgstr "Ældst først"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Hovsa!" msgstr "Hovsa!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Gem" msgstr "Gem"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Rul jævnt, når du navigerer mellem poster" msgstr "Rul jævnt, når du navigerer mellem poster"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Succes" msgstr "Succes"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL'en til det feed, du vil abonnere på. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Skift læsestatus for den aktuelle post" msgstr "Skift læsestatus for den aktuelle post"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "CommaFeed ist ein Open Source Projekt. Der Quellcode wird auf auf </0><1>GitHub</1> gehostet." msgstr "CommaFeed ist ein Open Source Projekt. Der Quellcode wird auf auf </0><1>GitHub</1> gehostet."
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Alle" msgstr "Alle"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,8 +180,8 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "CommaFeed ist kompatibel zur Fever API. Benutzen Sie folgende URL in Ihrem Fever-kompatiblen Mobilclient. Der Benutzername ist Ihr User Name, das Passwort ist der API-Schlüssel." msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Wenn nicht leer, ein Ausdruck, der als „wahr“ oder „falsch“ ausgewertet wird. " msgstr "Wenn nicht leer, ein Ausdruck, der als „wahr“ oder „falsch“ ausgewertet wird. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Wenn Sie auf ein Problem stoßen, melden Sie es bitte auf der Problemseite des GitHub-Projekts." msgstr "Wenn Sie auf ein Problem stoßen, melden Sie es bitte auf der Problemseite des GitHub-Projekts."
@@ -545,6 +545,10 @@ msgstr ""
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navigieren Sie zu einem Abonnement, indem Sie seinen Namen eingeben" msgstr "Navigieren Sie zu einem Abonnement, indem Sie seinen Namen eingeben"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Neues Passwort" msgstr "Neues Passwort"
@@ -578,6 +582,10 @@ msgstr "Nichts gefunden"
msgid "Oldest first" msgid "Oldest first"
msgstr "Älteste zuerst" msgstr "Älteste zuerst"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Ups!" msgstr "Ups!"
@@ -706,10 +714,18 @@ msgstr "Rechtsklick"
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Schnelles Scrollen beim Navigieren zwischen Einträgen" msgstr "Schnelles Scrollen beim Navigieren zwischen Einträgen"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Erfolg" msgstr "Erfolg"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "Die URL für den Feed, den Sie abonnieren möchten. "
msgid "Theme" msgid "Theme"
msgstr "Thema" msgstr "Thema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Lesestatus des aktuellen Eintrags umschalten" msgstr "Lesestatus des aktuellen Eintrags umschalten"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr "{0} (in {1})"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgstr "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
@@ -72,8 +68,8 @@ msgid "All"
msgstr "All" msgstr "All"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgstr "Always"
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
msgid "An email has been sent if this address was registered. Check your inbox." msgid "An email has been sent if this address was registered. Check your inbox."
@@ -184,8 +180,8 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "CommaFeed browser extension version {browserExtensionVersion}." msgstr "CommaFeed browser extension version {browserExtensionVersion}."
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgstr "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
@@ -411,6 +407,10 @@ msgstr "Id"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgstr "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr "If the entry doesn't entirely fit on the screen"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "If you encounter an issue, please report it on the issues page of the GitHub project." msgstr "If you encounter an issue, please report it on the issues page of the GitHub project."
@@ -545,6 +545,10 @@ msgstr "Name"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navigate to a subscription by entering its name" msgstr "Navigate to a subscription by entering its name"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr "Never"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "New password" msgstr "New password"
@@ -578,6 +582,10 @@ msgstr "Nothing found"
msgid "Oldest first" msgid "Oldest first"
msgstr "Oldest first" msgstr "Oldest first"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "On mobile, show action buttons at the bottom of the screen"
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Oops!" msgstr "Oops!"
@@ -706,10 +714,18 @@ msgstr "Right click"
msgid "Save" msgid "Save"
msgstr "Save" msgstr "Save"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr "Scroll selected entry to the top of the page"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Scroll smoothly when navigating between entries" msgstr "Scroll smoothly when navigating between entries"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr "Scrolling"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,8 +839,8 @@ msgid "Success"
msgstr "Success" msgstr "Success"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "Swipe header to the right" msgstr "Swipe header to the left"
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
@@ -851,6 +867,10 @@ msgstr "The URL for the feed you want to subscribe to. You can also use the webs
msgid "Theme" msgid "Theme"
msgstr "Theme" msgstr "Theme"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Toggle read status of current entry" msgstr "Toggle read status of current entry"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Todo" msgstr "Todo"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "Identificación"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Si no está vacío, una expresión que se evalúa como 'verdadero' o 'falso'. " msgstr "Si no está vacío, una expresión que se evalúa como 'verdadero' o 'falso'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Si encuentra un problema, infórmelo en la página de problemas del proyecto GitHub." msgstr "Si encuentra un problema, infórmelo en la página de problemas del proyecto GitHub."
@@ -545,6 +545,10 @@ msgstr "Nombre"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navegar a una suscripción ingresando su nombre" msgstr "Navegar a una suscripción ingresando su nombre"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nueva contraseña" msgstr "Nueva contraseña"
@@ -578,6 +582,10 @@ msgstr "Nada encontrado"
msgid "Oldest first" msgid "Oldest first"
msgstr "más antigua primero" msgstr "más antigua primero"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "¡Ups!" msgstr "¡Ups!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Guardar" msgstr "Guardar"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Desplazarse suavemente al navegar entre entradas" msgstr "Desplazarse suavemente al navegar entre entradas"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Éxito" msgstr "Éxito"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "La URL de la fuente a la que desea suscribirse. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Alternar estado de lectura de la entrada actual" msgstr "Alternar estado de lectura de la entrada actual"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "همه" msgstr "همه"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "شناسه"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "اگر خالی نباشد، عبارتی به \"درست\" یا \"نادرست\" ارزیابی می شود. " msgstr "اگر خالی نباشد، عبارتی به \"درست\" یا \"نادرست\" ارزیابی می شود. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "اگر با مشکلی مواجه شدید، لطفاً آن را در صفحه مشکلات پروژه GitHub گزارش دهید." msgstr "اگر با مشکلی مواجه شدید، لطفاً آن را در صفحه مشکلات پروژه GitHub گزارش دهید."
@@ -545,6 +545,10 @@ msgstr "نام"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "با وارد کردن نام اشتراک، به آن بروید" msgstr "با وارد کردن نام اشتراک، به آن بروید"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "رمز عبور جدید" msgstr "رمز عبور جدید"
@@ -578,6 +582,10 @@ msgstr "چیزی پیدا نشد"
msgid "Oldest first" msgid "Oldest first"
msgstr "قدیمی ترین اول" msgstr "قدیمی ترین اول"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "اوه!" msgstr "اوه!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "ذخیره کنید" msgstr "ذخیره کنید"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "هنگام پیمایش بین ورودی‌ها به آرامی حرکت کنید" msgstr "هنگام پیمایش بین ورودی‌ها به آرامی حرکت کنید"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "موفقیت" msgstr "موفقیت"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL فیدی که می خواهید در آن مشترک شوید. "
msgid "Theme" msgid "Theme"
msgstr "تم" msgstr "تم"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید" msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Kaikki" msgstr "Kaikki"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Jos ei tyhjä, lauseke, jonka arvo on \"tosi\" tai \"epätosi\". " msgstr "Jos ei tyhjä, lauseke, jonka arvo on \"tosi\" tai \"epätosi\". "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Jos kohtaat ongelman, ilmoita siitä GitHub-projektin ongelmasivulla." msgstr "Jos kohtaat ongelman, ilmoita siitä GitHub-projektin ongelmasivulla."
@@ -545,6 +545,10 @@ msgstr "Nimi"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Siirry tilaukseen kirjoittamalla sen nimi" msgstr "Siirry tilaukseen kirjoittamalla sen nimi"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Uusi salasana" msgstr "Uusi salasana"
@@ -578,6 +582,10 @@ msgstr "Mitään ei löytynyt"
msgid "Oldest first" msgid "Oldest first"
msgstr "Vanhin ensin" msgstr "Vanhin ensin"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Hups!" msgstr "Hups!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Tallenna" msgstr "Tallenna"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Selaa sujuvasti navigoidessasi merkintöjen välillä" msgstr "Selaa sujuvasti navigoidessasi merkintöjen välillä"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Onnistui" msgstr "Onnistui"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "Sen syötteen URL-osoite, jonka haluat tilata. "
msgid "Theme" msgid "Theme"
msgstr "Teema" msgstr "Teema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Vaihda nykyisen merkinnän lukutila" msgstr "Vaihda nykyisen merkinnän lukutila"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr "{0} (sur {1})"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "<0>CommaFeed est un projet open-source. Les sources sont hébergées sur </0><1>GitHub</1>." msgstr "<0>CommaFeed est un projet open-source. Les sources sont hébergées sur </0><1>GitHub</1>."
@@ -72,8 +68,8 @@ msgid "All"
msgstr "Tout" msgstr "Tout"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "Toujours remonter l'entrée sélectionnée en haut de la page, même si elle s'affiche complètement à l'écran" msgstr "Toujours"
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
msgid "An email has been sent if this address was registered. Check your inbox." msgid "An email has been sent if this address was registered. Check your inbox."
@@ -184,8 +180,8 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "Extension CommaFeed pour navigateur version {browserExtensionVersion}." msgstr "Extension CommaFeed pour navigateur version {browserExtensionVersion}."
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "Commafeed est compatible avec l'API Fever, en inscrivant l'URL suivante dans votre client mobile compatible. Entrez votre nom d'utilisateur habituel, et votre clef API comme mot de passe." msgstr "Commafeed est compatible avec l'API Fever, en inscrivant l'URL suivante dans votre client mobile compatible. Entrez votre nom d'utilisateur habituel, et votre <0>clef API</0> comme mot de passe."
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
@@ -411,6 +407,10 @@ msgstr "Identifiant"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Si non vide, une expression évaluant à 'vrai' ou 'faux'. Si faux, les nouvelles entrées de ce flux seront marquées comme lues automatiquement." msgstr "Si non vide, une expression évaluant à 'vrai' ou 'faux'. Si faux, les nouvelles entrées de ce flux seront marquées comme lues automatiquement."
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr "Si l'entrée ne tient pas entièrement sur l'écran"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Si vous rencontrez un problème, merci de le signaler sur la page du projet GitHub." msgstr "Si vous rencontrez un problème, merci de le signaler sur la page du projet GitHub."
@@ -545,6 +545,10 @@ msgstr "Nom"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Naviguer vers un abonnement en entrant son nom" msgstr "Naviguer vers un abonnement en entrant son nom"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr "Jamais"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nouveau mot de passe" msgstr "Nouveau mot de passe"
@@ -578,6 +582,10 @@ msgstr "Aucun résultat"
msgid "Oldest first" msgid "Oldest first"
msgstr "Du plus ancien au plus récent" msgstr "Du plus ancien au plus récent"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "Sur mobile, afficher les boutons d'action en bas de l'écran"
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Oups !" msgstr "Oups !"
@@ -706,10 +714,18 @@ msgstr "Clic droit"
msgid "Save" msgid "Save"
msgstr "Enregistrer" msgstr "Enregistrer"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr "Faire défiler l'entrée sélectionnée vers le haut de la page"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Défilement animé lors de la navigation entre les entrées" msgstr "Défilement animé lors de la navigation entre les entrées"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr "Défilement"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,8 +839,8 @@ msgid "Success"
msgstr "Succès" msgstr "Succès"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "Faire glisser le titre vers la droite" msgstr "Faire glisser le titre vers la gauche"
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
@@ -851,6 +867,10 @@ msgstr "L'URL du flux auquel vous souhaitez vous abonner. Vous pouvez aussi util
msgid "Theme" msgid "Theme"
msgstr "Thème" msgstr "Thème"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr "Ceci est votre clef API. Elle peut être utilisée pour certaines opérations en lecture seule et donne accès à l'API Fever. Utilisez le formulaire en bas de la page pour générer une nouvelle clef API"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Marquer l'entrée actuelle comme lue/non lue" msgstr "Marquer l'entrée actuelle comme lue/non lue"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Todos" msgstr "Todos"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Se non está baleira, unha expresión que se avalía como \"verdadeiro\" ou \"falso\". " msgstr "Se non está baleira, unha expresión que se avalía como \"verdadeiro\" ou \"falso\". "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Se atopas algún problema, infórmao na páxina de problemas do proxecto GitHub." msgstr "Se atopas algún problema, infórmao na páxina de problemas do proxecto GitHub."
@@ -545,6 +545,10 @@ msgstr "Nome"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navega a unha subscrición introducindo o seu nome" msgstr "Navega a unha subscrición introducindo o seu nome"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "novo contrasinal" msgstr "novo contrasinal"
@@ -578,6 +582,10 @@ msgstr "Non se atopou nada"
msgid "Oldest first" msgid "Oldest first"
msgstr "O máis vello primeiro" msgstr "O máis vello primeiro"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Vaia!" msgstr "Vaia!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Gardar" msgstr "Gardar"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Desprácese suavemente ao navegar entre as entradas" msgstr "Desprácese suavemente ao navegar entre as entradas"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Éxito" msgstr "Éxito"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "O URL do feed ao que quere subscribirse. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "alternar o estado de lectura da entrada actual" msgstr "alternar o estado de lectura da entrada actual"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Mind" msgstr "Mind"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Ha nem üres, akkor 'igaz' vagy 'hamis' értékre kiértékelő kifejezés. " msgstr "Ha nem üres, akkor 'igaz' vagy 'hamis' értékre kiértékelő kifejezés. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Ha problémát tapasztal, kérjük, jelentse azt a GitHub projekt problémák oldalán." msgstr "Ha problémát tapasztal, kérjük, jelentse azt a GitHub projekt problémák oldalán."
@@ -545,6 +545,10 @@ msgstr "Név"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navigáljon egy előfizetéshez a nevének megadásával" msgstr "Navigáljon egy előfizetéshez a nevének megadásával"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Új jelszó" msgstr "Új jelszó"
@@ -578,6 +582,10 @@ msgstr "Semmi sem található"
msgid "Oldest first" msgid "Oldest first"
msgstr "A legidősebb első" msgstr "A legidősebb első"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Hoppá!" msgstr "Hoppá!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Mentés" msgstr "Mentés"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Sima görgetés, amikor a bejegyzések között navigál" msgstr "Sima görgetés, amikor a bejegyzések között navigál"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Siker" msgstr "Siker"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "Az előfizetni kívánt hírcsatorna URL-je. "
msgid "Theme" msgid "Theme"
msgstr "Téma" msgstr "Téma"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Az aktuális bejegyzés olvasási állapotának váltása" msgstr "Az aktuális bejegyzés olvasási állapotának váltása"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Semua" msgstr "Semua"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Jika tidak kosong, ekspresi mengevaluasi ke 'benar' atau 'salah'. " msgstr "Jika tidak kosong, ekspresi mengevaluasi ke 'benar' atau 'salah'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Jika Anda mengalami masalah, harap laporkan di halaman masalah proyek GitHub." msgstr "Jika Anda mengalami masalah, harap laporkan di halaman masalah proyek GitHub."
@@ -545,6 +545,10 @@ msgstr "Nama"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navigasikan ke langganan dengan memasukkan namanya" msgstr "Navigasikan ke langganan dengan memasukkan namanya"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Kata sandi baru" msgstr "Kata sandi baru"
@@ -578,6 +582,10 @@ msgstr "Tidak ada yang ditemukan"
msgid "Oldest first" msgid "Oldest first"
msgstr "Tertua dulu" msgstr "Tertua dulu"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Ups!" msgstr "Ups!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Simpan" msgstr "Simpan"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Gulir dengan lancar saat menavigasi antar entri" msgstr "Gulir dengan lancar saat menavigasi antar entri"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Sukses" msgstr "Sukses"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL untuk umpan yang ingin Anda langgani. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Beralih status baca entri saat ini" msgstr "Beralih status baca entri saat ini"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Tutto" msgstr "Tutto"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Se non è vuota, un'espressione valutata come 'vero' o 'falso'. " msgstr "Se non è vuota, un'espressione valutata come 'vero' o 'falso'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Se riscontri un problema, segnalalo nella pagina dei problemi del progetto GitHub." msgstr "Se riscontri un problema, segnalalo nella pagina dei problemi del progetto GitHub."
@@ -545,6 +545,10 @@ msgstr "Nome"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navigare verso un abbonamento inserendo il suo nome" msgstr "Navigare verso un abbonamento inserendo il suo nome"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nuova password" msgstr "Nuova password"
@@ -578,6 +582,10 @@ msgstr "Non è stato trovato nulla"
msgid "Oldest first" msgid "Oldest first"
msgstr "Il più vecchio prima" msgstr "Il più vecchio prima"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Ops!" msgstr "Ops!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Salva" msgstr "Salva"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Scorrere senza problemi durante la navigazione tra le voci" msgstr "Scorrere senza problemi durante la navigazione tra le voci"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Successo" msgstr "Successo"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "L'URL del feed a cui vuoi iscriverti. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Commuta lo stato di lettura della voce corrente" msgstr "Commuta lo stato di lettura della voce corrente"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "全員" msgstr "全員"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "ID"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "空でない場合は、'true' または 'false' に評価される式。 " msgstr "空でない場合は、'true' または 'false' に評価される式。 "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "問題が発生した場合は、GitHub プロジェクトの問題ページで報告してください。" msgstr "問題が発生した場合は、GitHub プロジェクトの問題ページで報告してください。"
@@ -545,6 +545,10 @@ msgstr "名前"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "名前を入力してサブスクリプションに移動します" msgstr "名前を入力してサブスクリプションに移動します"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "新しいパスワード" msgstr "新しいパスワード"
@@ -578,6 +582,10 @@ msgstr "何も見つかりませんでした"
msgid "Oldest first" msgid "Oldest first"
msgstr "古い順" msgstr "古い順"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "おっと!" msgstr "おっと!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "保存" msgstr "保存"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "エントリ間を移動するときにスムーズにスクロールする" msgstr "エントリ間を移動するときにスムーズにスクロールする"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "成功" msgstr "成功"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "購読したいフィードのURL。 "
msgid "Theme" msgid "Theme"
msgstr "テーマ" msgstr "テーマ"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "現在のエントリの読み取りステータスを切り替えます" msgstr "現在のエントリの読み取りステータスを切り替えます"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "전체" msgstr "전체"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "아이디"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "비어 있지 않은 경우 'true' 또는 'false'로 평가되는 표현식입니다. " msgstr "비어 있지 않은 경우 'true' 또는 'false'로 평가되는 표현식입니다. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "문제가 발생하면 GitHub 프로젝트의 문제 페이지에서 보고하세요." msgstr "문제가 발생하면 GitHub 프로젝트의 문제 페이지에서 보고하세요."
@@ -545,6 +545,10 @@ msgstr "이름"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "이름을 입력하여 구독으로 이동" msgstr "이름을 입력하여 구독으로 이동"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "새 비밀번호" msgstr "새 비밀번호"
@@ -578,6 +582,10 @@ msgstr "아무것도 찾을 수 없습니다"
msgid "Oldest first" msgid "Oldest first"
msgstr "가장 오래된 것부터" msgstr "가장 오래된 것부터"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "앗!" msgstr "앗!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "저장" msgstr "저장"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "항목 간 탐색 시 부드럽게 스크롤" msgstr "항목 간 탐색 시 부드럽게 스크롤"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "성공" msgstr "성공"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "구독하려는 피드의 URL입니다. "
msgid "Theme" msgid "Theme"
msgstr "테마" msgstr "테마"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "현재 항목의 읽기 상태 전환" msgstr "현재 항목의 읽기 상태 전환"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Semua" msgstr "Semua"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Jika tidak kosong, ungkapan yang menilai kepada 'benar' atau 'palsu'. " msgstr "Jika tidak kosong, ungkapan yang menilai kepada 'benar' atau 'palsu'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Jika anda menghadapi isu, sila laporkan pada halaman isu projek GitHub." msgstr "Jika anda menghadapi isu, sila laporkan pada halaman isu projek GitHub."
@@ -545,6 +545,10 @@ msgstr "Nama"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navigasi ke langganan dengan memasukkan namanya" msgstr "Navigasi ke langganan dengan memasukkan namanya"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Kata laluan baharu" msgstr "Kata laluan baharu"
@@ -578,6 +582,10 @@ msgstr "Tiada apa-apa dijumpai"
msgid "Oldest first" msgid "Oldest first"
msgstr "Tertua dahulu" msgstr "Tertua dahulu"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Aduh!" msgstr "Aduh!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Jimat" msgstr "Jimat"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Tatal dengan lancar apabila menavigasi antara entri" msgstr "Tatal dengan lancar apabila menavigasi antara entri"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Kejayaan" msgstr "Kejayaan"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL untuk suapan yang anda ingin langgan. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Togol status bacaan entri semasa" msgstr "Togol status bacaan entri semasa"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Alle" msgstr "Alle"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Hvis det ikke er tomt, et uttrykk som vurderes til 'sant' eller 'usant'. " msgstr "Hvis det ikke er tomt, et uttrykk som vurderes til 'sant' eller 'usant'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Hvis du støter på et problem, vennligst rapporter det på problemsiden til GitHub-prosjektet." msgstr "Hvis du støter på et problem, vennligst rapporter det på problemsiden til GitHub-prosjektet."
@@ -545,6 +545,10 @@ msgstr "Navn"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Naviger til et abonnement ved å skrive inn navnet" msgstr "Naviger til et abonnement ved å skrive inn navnet"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nytt passord" msgstr "Nytt passord"
@@ -578,6 +582,10 @@ msgstr "Ingenting funnet"
msgid "Oldest first" msgid "Oldest first"
msgstr "Eldste først" msgstr "Eldste først"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Beklager!" msgstr "Beklager!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Lagre" msgstr "Lagre"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Rull jevnt når du navigerer mellom oppføringer" msgstr "Rull jevnt når du navigerer mellom oppføringer"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Suksess" msgstr "Suksess"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL-en til feeden du vil abonnere på. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Veksle lesestatus for gjeldende oppføring" msgstr "Veksle lesestatus for gjeldende oppføring"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Alles" msgstr "Alles"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Indien niet leeg, een uitdrukking die evalueert naar 'true' of 'false'. " msgstr "Indien niet leeg, een uitdrukking die evalueert naar 'true' of 'false'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Als je een probleem tegenkomt, meld dit dan op de pagina met problemen van het GitHub-project." msgstr "Als je een probleem tegenkomt, meld dit dan op de pagina met problemen van het GitHub-project."
@@ -545,6 +545,10 @@ msgstr "Naam"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navigeer naar een abonnement door de naam in te voeren" msgstr "Navigeer naar een abonnement door de naam in te voeren"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nieuw wachtwoord" msgstr "Nieuw wachtwoord"
@@ -578,6 +582,10 @@ msgstr "Niets gevonden"
msgid "Oldest first" msgid "Oldest first"
msgstr "Oudste eerst" msgstr "Oudste eerst"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Oeps!" msgstr "Oeps!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Opslaan" msgstr "Opslaan"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Vloeiend scrollen bij het navigeren tussen items" msgstr "Vloeiend scrollen bij het navigeren tussen items"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Succes" msgstr "Succes"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "De URL voor de feed waarop u zich wilt abonneren. "
msgid "Theme" msgid "Theme"
msgstr "Thema" msgstr "Thema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Toggle leesstatus van huidige invoer" msgstr "Toggle leesstatus van huidige invoer"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Alle" msgstr "Alle"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Hvis det ikke er tomt, et uttrykk som vurderes til 'sant' eller 'usant'. " msgstr "Hvis det ikke er tomt, et uttrykk som vurderes til 'sant' eller 'usant'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Hvis du støter på et problem, vennligst rapporter det på problemsiden til GitHub-prosjektet." msgstr "Hvis du støter på et problem, vennligst rapporter det på problemsiden til GitHub-prosjektet."
@@ -545,6 +545,10 @@ msgstr "Navn"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Naviger til et abonnement ved å skrive inn navnet" msgstr "Naviger til et abonnement ved å skrive inn navnet"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nytt passord" msgstr "Nytt passord"
@@ -578,6 +582,10 @@ msgstr "Ingenting funnet"
msgid "Oldest first" msgid "Oldest first"
msgstr "Eldste først" msgstr "Eldste først"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Beklager!" msgstr "Beklager!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Lagre" msgstr "Lagre"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Rull jevnt når du navigerer mellom oppføringer" msgstr "Rull jevnt når du navigerer mellom oppføringer"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Suksess" msgstr "Suksess"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL-en til feeden du vil abonnere på. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Veksle lesestatus for gjeldende oppføring" msgstr "Veksle lesestatus for gjeldende oppføring"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Wszystkie" msgstr "Wszystkie"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "Identyfikator"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Jeśli nie jest puste, wyrażenie oceniające jako „prawda” lub „fałsz”. " msgstr "Jeśli nie jest puste, wyrażenie oceniające jako „prawda” lub „fałsz”. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Jeśli napotkasz problem, zgłoś go na stronie problemów projektu GitHub." msgstr "Jeśli napotkasz problem, zgłoś go na stronie problemów projektu GitHub."
@@ -545,6 +545,10 @@ msgstr "Nazwa"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Przejdź do subskrypcji, wpisując jej nazwę" msgstr "Przejdź do subskrypcji, wpisując jej nazwę"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nowe hasło" msgstr "Nowe hasło"
@@ -578,6 +582,10 @@ msgstr "Nic nie znaleziono"
msgid "Oldest first" msgid "Oldest first"
msgstr "Najstarsze jako pierwsze" msgstr "Najstarsze jako pierwsze"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Ups!" msgstr "Ups!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Zapisz" msgstr "Zapisz"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Przewijaj płynnie podczas nawigowania między wpisami" msgstr "Przewijaj płynnie podczas nawigowania między wpisami"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Sukces" msgstr "Sukces"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL kanału, który chcesz subskrybować. "
msgid "Theme" msgid "Theme"
msgstr "Motyw" msgstr "Motyw"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Przełącz stan odczytu bieżącego wpisu" msgstr "Przełącz stan odczytu bieżącego wpisu"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Todos" msgstr "Todos"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "ID"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Se não estiver vazio, uma expressão avaliada como 'true' ou 'false'. " msgstr "Se não estiver vazio, uma expressão avaliada como 'true' ou 'false'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Se você encontrar um problema, informe-o na página de problemas do projeto GitHub." msgstr "Se você encontrar um problema, informe-o na página de problemas do projeto GitHub."
@@ -545,6 +545,10 @@ msgstr "Nome"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navegue até uma assinatura digitando seu nome" msgstr "Navegue até uma assinatura digitando seu nome"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nova senha" msgstr "Nova senha"
@@ -578,6 +582,10 @@ msgstr "Nada encontrado"
msgid "Oldest first" msgid "Oldest first"
msgstr "Mais antigo primeiro" msgstr "Mais antigo primeiro"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Opa!" msgstr "Opa!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Salvar" msgstr "Salvar"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Rolar suavemente ao navegar entre as entradas" msgstr "Rolar suavemente ao navegar entre as entradas"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Sucesso" msgstr "Sucesso"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "A URL do feed que você deseja assinar. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Alternar o status de leitura da entrada atual" msgstr "Alternar o status de leitura da entrada atual"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Все" msgstr "Все"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "идентификатор"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Если не пусто, выражение оценивается как «истина» или «ложь». " msgstr "Если не пусто, выражение оценивается как «истина» или «ложь». "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Если вы столкнулись с проблемой, сообщите о ней на странице проблем проекта GitHub." msgstr "Если вы столкнулись с проблемой, сообщите о ней на странице проблем проекта GitHub."
@@ -545,6 +545,10 @@ msgstr "Имя"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Перейдите к подписке, введя ее имя." msgstr "Перейдите к подписке, введя ее имя."
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Новый пароль" msgstr "Новый пароль"
@@ -578,6 +582,10 @@ msgstr "Ничего не найдено"
msgid "Oldest first" msgid "Oldest first"
msgstr "Сначала самые старые" msgstr "Сначала самые старые"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Ой!" msgstr "Ой!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Сохранить" msgstr "Сохранить"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Плавная прокрутка при переходе между записями" msgstr "Плавная прокрутка при переходе между записями"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Успех" msgstr "Успех"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL канала, на который вы хотите подписат
msgid "Theme" msgid "Theme"
msgstr "Тема" msgstr "Тема"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Переключить статус чтения текущей записи" msgstr "Переключить статус чтения текущей записи"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Všetky" msgstr "Všetky"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Ak nie je prázdny, výraz vyhodnotený ako 'pravda' alebo 'nepravda'. " msgstr "Ak nie je prázdny, výraz vyhodnotený ako 'pravda' alebo 'nepravda'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Ak narazíte na problém, nahláste ho na stránke problémov projektu GitHub." msgstr "Ak narazíte na problém, nahláste ho na stránke problémov projektu GitHub."
@@ -545,6 +545,10 @@ msgstr "Meno"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Prejdite na predplatné zadaním jeho názvu" msgstr "Prejdite na predplatné zadaním jeho názvu"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nové heslo" msgstr "Nové heslo"
@@ -578,6 +582,10 @@ msgstr "Nič sa nenašlo"
msgid "Oldest first" msgid "Oldest first"
msgstr "Najprv najstarší" msgstr "Najprv najstarší"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Ojoj!" msgstr "Ojoj!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Uložiť" msgstr "Uložiť"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Pri navigácii medzi položkami plynulo rolujte" msgstr "Pri navigácii medzi položkami plynulo rolujte"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Úspech" msgstr "Úspech"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL zdroja, na odber ktorého sa chcete prihlásiť. "
msgid "Theme" msgid "Theme"
msgstr "Téma" msgstr "Téma"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Prepne stav čítania aktuálneho záznamu" msgstr "Prepne stav čítania aktuálneho záznamu"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "Alla" msgstr "Alla"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr ""
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Om det inte är tomt, ett uttryck som utvärderas till 'sant' eller 'falskt'. " msgstr "Om det inte är tomt, ett uttryck som utvärderas till 'sant' eller 'falskt'. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Om du stöter på ett problem, vänligen rapportera det på problemsidan för GitHub-projektet." msgstr "Om du stöter på ett problem, vänligen rapportera det på problemsidan för GitHub-projektet."
@@ -545,6 +545,10 @@ msgstr "Namn"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Navigera till ett abonnemang genom att ange dess namn" msgstr "Navigera till ett abonnemang genom att ange dess namn"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Nytt lösenord" msgstr "Nytt lösenord"
@@ -578,6 +582,10 @@ msgstr "Inget hittades"
msgid "Oldest first" msgid "Oldest first"
msgstr "Äldst först" msgstr "Äldst först"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Hoppsan!" msgstr "Hoppsan!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "Spara" msgstr "Spara"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Bläddra mjukt när du navigerar mellan poster" msgstr "Bläddra mjukt när du navigerar mellan poster"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "Framgång" msgstr "Framgång"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "URL:en för flödet du vill prenumerera på. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Växla lässtatus för aktuell post" msgstr "Växla lässtatus för aktuell post"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr "{0} ({1} içinde)"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "<0>CommaFeed açık kaynak kodlu bir proje. Kaynak kodları </0><1>GitHub</1>'da." msgstr "<0>CommaFeed açık kaynak kodlu bir proje. Kaynak kodları </0><1>GitHub</1>'da."
@@ -72,8 +68,8 @@ msgid "All"
msgstr "Tümü" msgstr "Tümü"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "Seçilen girişi her zaman sayfanın üstüne kaydır, ekrana tamamen sığsa bile" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
msgid "An email has been sent if this address was registered. Check your inbox." msgid "An email has been sent if this address was registered. Check your inbox."
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "CommaFeed tarayıcı eklentisi sürüm {browserExtensionVersion}." msgstr "CommaFeed tarayıcı eklentisi sürüm {browserExtensionVersion}."
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "Kimlik"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "Boş değilse, 'doğru' veya 'yanlış' olarak değerlendirilen bir ifade. " msgstr "Boş değilse, 'doğru' veya 'yanlış' olarak değerlendirilen bir ifade. "
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "Bir sorunla karşılaşırsanız lütfen GitHub projesinin sorunlar sayfasında bildirin." msgstr "Bir sorunla karşılaşırsanız lütfen GitHub projesinin sorunlar sayfasında bildirin."
@@ -545,6 +545,10 @@ msgstr "İsim"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "Adını girerek bir aboneliğe gidin" msgstr "Adını girerek bir aboneliğe gidin"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Yeni şifre" msgstr "Yeni şifre"
@@ -578,6 +582,10 @@ msgstr "Hiçbir şey bulunamadı"
msgid "Oldest first" msgid "Oldest first"
msgstr "Önce en eski" msgstr "Önce en eski"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Hata!" msgstr "Hata!"
@@ -706,10 +714,18 @@ msgstr "Sağ tık"
msgid "Save" msgid "Save"
msgstr "Kaydet" msgstr "Kaydet"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "Girişler arasında gezinirken sorunsuz ilerleyin" msgstr "Girişler arasında gezinirken sorunsuz ilerleyin"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,8 +839,8 @@ msgid "Success"
msgstr "Başarı" msgstr "Başarı"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "Başlığı sağa kaydır" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
@@ -851,6 +867,10 @@ msgstr "Abone olmak istediğiniz beslemenin URL'si. "
msgid "Theme" msgid "Theme"
msgstr "Tema" msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "Geçerli girişin okuma durumunu değiştir" msgstr "Geçerli girişin okuma durumunu değiştir"

View File

@@ -13,10 +13,6 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>." msgid "<0>CommaFeed is an open-source project. Sources are hosted on </0><1>GitHub</1>."
msgstr "" msgstr ""
@@ -72,7 +68,7 @@ msgid "All"
msgstr "全部" msgstr "全部"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -184,7 +180,7 @@ msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key." msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
msgstr "" msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
@@ -411,6 +407,10 @@ msgstr "身份证"
msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically." msgid "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically."
msgstr "如果不为空,则表达式评估为“真”或“假”。" msgstr "如果不为空,则表达式评估为“真”或“假”。"
#: src/components/settings/DisplaySettings.tsx
msgid "If the entry doesn't entirely fit on the screen"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "If you encounter an issue, please report it on the issues page of the GitHub project." msgid "If you encounter an issue, please report it on the issues page of the GitHub project."
msgstr "如果遇到问题请在GitHub项目的issues页面上报告。" msgstr "如果遇到问题请在GitHub项目的issues页面上报告。"
@@ -545,6 +545,10 @@ msgstr "​​名称"
msgid "Navigate to a subscription by entering its name" msgid "Navigate to a subscription by entering its name"
msgstr "通过输入订阅名称导航到订阅" msgstr "通过输入订阅名称导航到订阅"
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr ""
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "新密码" msgstr "新密码"
@@ -578,6 +582,10 @@ msgstr "没有找到"
msgid "Oldest first" msgid "Oldest first"
msgstr "最早的优先" msgstr "最早的优先"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr ""
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "哎呀!" msgstr "哎呀!"
@@ -706,10 +714,18 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "保存" msgstr "保存"
#: src/components/settings/DisplaySettings.tsx
msgid "Scroll selected entry to the top of the page"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Scroll smoothly when navigating between entries" msgid "Scroll smoothly when navigating between entries"
msgstr "在条目之间导航时平滑滚动" msgstr "在条目之间导航时平滑滚动"
#: src/components/settings/DisplaySettings.tsx
msgid "Scrolling"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
@@ -823,7 +839,7 @@ msgid "Success"
msgstr "成功" msgstr "成功"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right" msgid "Swipe header to the left"
msgstr "" msgstr ""
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
@@ -851,6 +867,10 @@ msgstr "您要订阅的订阅源的 URL。"
msgid "Theme" msgid "Theme"
msgstr "主题" msgstr "主题"
#: src/components/settings/ProfileSettings.tsx
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
msgstr "切换当前条目的读取状态" msgstr "切换当前条目的读取状态"

View File

@@ -1,44 +1,39 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box, Button, Container, Group, type MantineTheme, Text, Title, useMantineTheme } from "@mantine/core" import { Box, Button, Container, Group, Text, Title } from "@mantine/core"
import { TbRefresh } from "react-icons/tb" import { TbRefresh } from "react-icons/tb"
import { tss } from "tss" import { tss } from "tss"
import { PageTitle } from "./PageTitle" import { PageTitle } from "./PageTitle"
const useStyles = tss const useStyles = tss.create(({ theme }) => ({
.withParams<{ root: {
theme: MantineTheme paddingTop: 80,
}>() },
.create(({ theme }) => ({
root: {
paddingTop: 80,
},
label: { label: {
textAlign: "center", textAlign: "center",
fontWeight: "bold", fontWeight: "bold",
fontSize: 120, fontSize: 120,
lineHeight: 1, lineHeight: 1,
marginBottom: `calc(${theme.spacing.xl} * 1.5)`, marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
color: theme.colors[theme.primaryColor][3], color: theme.colors[theme.primaryColor][3],
}, },
title: { title: {
textAlign: "center", textAlign: "center",
fontWeight: "bold", fontWeight: "bold",
fontSize: 32, fontSize: 32,
}, },
description: { description: {
maxWidth: 540, maxWidth: 540,
margin: "auto", margin: "auto",
marginTop: theme.spacing.xl, marginTop: theme.spacing.xl,
marginBottom: `calc(${theme.spacing.xl} * 1.5)`, marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
}, },
})) }))
export function ErrorPage(props: { error: Error }) { export function ErrorPage(props: { error: Error }) {
const theme = useMantineTheme() const { classes } = useStyles()
const { classes } = useStyles({ theme })
return ( return (
<div className={classes.root}> <div className={classes.root}>

View File

@@ -3,8 +3,8 @@ import { Anchor, Box, Center, Container, Divider, Group, Image, Space, Title, us
import { client } from "app/client" import { client } from "app/client"
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks" import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import welcome_page_dark from "assets/welcome_page_dark.png" import welcomePageDark from "assets/welcome_page_dark.png"
import welcome_page_light from "assets/welcome_page_light.png" import welcomePageLight from "assets/welcome_page_light.png"
import { ActionButton } from "components/ActionButton" import { ActionButton } from "components/ActionButton"
import { useBrowserExtension } from "hooks/useBrowserExtension" import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useMobile } from "hooks/useMobile" import { useMobile } from "hooks/useMobile"
@@ -19,7 +19,7 @@ export function WelcomePage() {
const serverInfos = useAppSelector(state => state.server.serverInfos) const serverInfos = useAppSelector(state => state.server.serverInfos)
const { colorScheme } = useMantineColorScheme() const { colorScheme } = useMantineColorScheme()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const image = colorScheme === "light" ? welcome_page_light : welcome_page_dark const image = colorScheme === "light" ? welcomePageLight : welcomePageDark
const login = useAsyncCallback(client.user.login, { const login = useAsyncCallback(client.user.login, {
onSuccess: () => { onSuccess: () => {

View File

@@ -108,7 +108,7 @@ export function AdminUsersPage() {
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{users?.map(u => ( {users.map(u => (
<Table.Tr key={u.id}> <Table.Tr key={u.id}>
<Table.Td>{u.id}</Table.Td> <Table.Td>{u.id}</Table.Td>
<Table.Td>{u.name}</Table.Td> <Table.Td>{u.name}</Table.Td>

View File

@@ -14,7 +14,7 @@ const shownMeters: Record<string, string> = {
"com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed update rate", "com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed update rate",
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit rate", "com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit rate",
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate", "com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate",
"com.commafeed.backend.service.DatabaseCleaningService.entriesDeleted": "Entries deleted", "com.commafeed.backend.service.db.DatabaseCleaningService.entriesDeleted": "Entries deleted",
} }
const shownGauges: Record<string, string> = { const shownGauges: Record<string, string> = {

View File

@@ -3,7 +3,9 @@ import { HistoryService, RedocStandalone } from "redoc"
// disable redoc url sync because it causes issues with hashrouter // disable redoc url sync because it causes issues with hashrouter
Object.defineProperty(HistoryService.prototype, "replace", { Object.defineProperty(HistoryService.prototype, "replace", {
value: () => {}, value: () => {
// do nothing
},
}) })
function ApiDocumentationPage() { function ApiDocumentationPage() {

View File

@@ -49,11 +49,17 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const titleClicked = () => { const titleClicked = () => {
if (props.sourceType === "category") { switch (props.sourceType) {
dispatch(redirectToCategoryDetails(id)) case "category":
} else if (props.sourceType === "feed") { dispatch(redirectToCategoryDetails(id))
dispatch(redirectToFeedDetails(id)) break
} else if (props.sourceType === "tag") dispatch(redirectToTagDetails(id)) case "feed":
dispatch(redirectToFeedDetails(id))
break
case "tag":
dispatch(redirectToTagDetails(id))
break
}
} }
useEffect(() => { useEffect(() => {
@@ -72,7 +78,7 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
if (noSubscriptions) return <NoSubscriptionHelp /> if (noSubscriptions) return <NoSubscriptionHelp />
return ( 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 // 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 - Constants.layout.headerHeight - 210}> <Box mb={viewport.height * 0.75}>
<Group gap="xl"> <Group gap="xl">
{sourceWebsiteUrl && ( {sourceWebsiteUrl && (
<a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}> <a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}>

View File

@@ -13,12 +13,15 @@ import { Logo } from "components/Logo"
import { OnDesktop } from "components/responsive/OnDesktop" import { OnDesktop } from "components/responsive/OnDesktop"
import { OnMobile } from "components/responsive/OnMobile" import { OnMobile } from "components/responsive/OnMobile"
import { useAppLoading } from "hooks/useAppLoading" import { useAppLoading } from "hooks/useAppLoading"
import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useMobile } from "hooks/useMobile"
import { useWebSocket } from "hooks/useWebSocket" import { useWebSocket } from "hooks/useWebSocket"
import { LoadingPage } from "pages/LoadingPage" import { LoadingPage } from "pages/LoadingPage"
import { type ReactNode, Suspense, useEffect } from "react" import { type ReactNode, Suspense, useEffect } from "react"
import Draggable from "react-draggable" import Draggable from "react-draggable"
import { TbMenu2, TbPlus, TbX } from "react-icons/tb" import { TbMenu2, TbPlus, TbX } from "react-icons/tb"
import { Outlet } from "react-router-dom" import { Outlet } from "react-router-dom"
import { useSwipeable } from "react-swipeable"
import { tss } from "tss" import { tss } from "tss"
import useLocalStorage from "use-local-storage" import useLocalStorage from "use-local-storage"
@@ -59,6 +62,8 @@ const useStyles = tss
export default function Layout(props: LayoutProps) { export default function Layout(props: LayoutProps) {
const theme = useMantineTheme() const theme = useMantineTheme()
const mobile = useMobile()
const { isBrowserExtensionPopup } = useBrowserExtension()
const [sidebarWidth, setSidebarWidth] = useLocalStorage("sidebar-width", 350) const [sidebarWidth, setSidebarWidth] = useLocalStorage("sidebar-width", 350)
const sidebarPadding = theme.spacing.xs const sidebarPadding = theme.spacing.xs
const { classes } = useStyles({ const { classes } = useStyles({
@@ -70,6 +75,8 @@ export default function Layout(props: LayoutProps) {
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval)
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
const headerInFooter = mobile && !isBrowserExtensionPopup && mobileFooter
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useWebSocket() useWebSocket()
@@ -111,81 +118,100 @@ export default function Layout(props: LayoutProps) {
</ActionIcon> </ActionIcon>
) )
if (loading) return <LoadingPage /> const header = (
return ( <>
<AppShell <OnMobile>
header={{ height: Constants.layout.headerHeight }} {mobileMenuOpen && (
navbar={{ <Group justify="space-between" p="md">
width: sidebarWidth, <Box>{burger}</Box>
breakpoint: Constants.layout.mobileBreakpoint, <Box>
collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible }, <LogoAndTitle />
}} </Box>
padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }} <Box>{addButton}</Box>
> </Group>
<AppShell.Header id="header"> )}
<OnMobile> {!mobileMenuOpen && (
{mobileMenuOpen && (
<Group justify="space-between" p="md">
<Box>{burger}</Box>
<Box>
<LogoAndTitle />
</Box>
<Box>{addButton}</Box>
</Group>
)}
{!mobileMenuOpen && (
<Group p="md">
<Box>{burger}</Box>
<Box style={{ flexGrow: 1 }}>{props.header}</Box>
</Group>
)}
</OnMobile>
<OnDesktop>
<Group p="md"> <Group p="md">
<Group justify="space-between" style={{ width: sidebarWidth - 16 }}> <Box>{burger}</Box>
<Box>
<LogoAndTitle />
</Box>
<Box>{addButton}</Box>
</Group>
<Box style={{ flexGrow: 1 }}>{props.header}</Box> <Box style={{ flexGrow: 1 }}>{props.header}</Box>
</Group> </Group>
</OnDesktop> )}
</AppShell.Header> </OnMobile>
<AppShell.Navbar id="sidebar" p={sidebarPadding}> <OnDesktop>
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm"> <Group p="md">
<Box className={classes.sidebarContent}>{props.sidebar}</Box> <Group justify="space-between" style={{ width: sidebarWidth - 16 }}>
</AppShell.Section> <Box>
</AppShell.Navbar> <LogoAndTitle />
<Draggable </Box>
axis="x" <Box>{addButton}</Box>
defaultPosition={{ </Group>
x: sidebarWidth, <Box style={{ flexGrow: 1 }}>{props.header}</Box>
y: Constants.layout.headerHeight, </Group>
}} </OnDesktop>
bounds={{ </>
left: 120, )
right: 1000,
}}
grid={[30, 30]}
onDrag={(_e, data) => setSidebarWidth(data.x)}
>
<Box
style={{
position: "fixed",
height: "100%",
width: "10px",
cursor: "ew-resize",
}}
></Box>
</Draggable>
<AppShell.Main id="content"> const swipeHandlers = useSwipeable({
<Suspense fallback={<Loader />}> onSwiping: e => {
<AnnouncementDialog /> const threshold = document.documentElement.clientWidth / 6
<Outlet /> if (e.absX > threshold) {
</Suspense> dispatch(setMobileMenuOpen(e.dir === "Right"))
</AppShell.Main> }
</AppShell> },
})
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 id={Constants.dom.headerId}>{!headerInFooter && header}</AppShell.Header>
<AppShell.Footer id={Constants.dom.footerId}>{headerInFooter && header}</AppShell.Footer>
<AppShell.Navbar id="sidebar" p={sidebarPadding}>
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm">
<Box className={classes.sidebarContent}>{props.sidebar}</Box>
</AppShell.Section>
</AppShell.Navbar>
<OnDesktop>
<Draggable
axis="x"
defaultPosition={{
x: sidebarWidth,
y: 0,
}}
bounds={{
left: 120,
right: 1000,
}}
grid={[30, 30]}
onDrag={(_e, data) => setSidebarWidth(data.x)}
>
<Box
style={{
position: "fixed",
height: "100%",
width: "10px",
cursor: "ew-resize",
}}
></Box>
</Draggable>
</OnDesktop>
<AppShell.Main id="content">
<Suspense fallback={<Loader />}>
<AnnouncementDialog />
<Outlet />
</Suspense>
</AppShell.Main>
</AppShell>
</Box>
) )
} }

View File

@@ -1,11 +1,14 @@
import { useMantineTheme } from "@mantine/core"
import { useColorScheme } from "hooks/useColorScheme"
import { createTss } from "tss-react" import { createTss } from "tss-react"
const useContext = () => { const useContext = () => {
// return anything here that will be accessible in tss.create() // return anything here that will be accessible in tss.create()
// we don't need anything right now
return {} const theme = useMantineTheme()
const colorScheme = useColorScheme()
return { theme, colorScheme }
} }
export const { tss } = createTss({ useContext }) export const { tss } = createTss({ useContext })
export const useStyles = tss.create({})

View File

@@ -6,7 +6,7 @@ import eslint from "vite-plugin-eslint"
import tsconfigPaths from "vite-tsconfig-paths" import tsconfigPaths from "vite-tsconfig-paths"
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig(env => ({
plugins: [ plugins: [
react({ react({
babel: { babel: {
@@ -15,7 +15,8 @@ export default defineConfig({
}, },
}), }),
lingui(), lingui(),
eslint(), // https://github.com/vitest-dev/vitest/issues/4055#issuecomment-1732994672
env.mode !== "test" && eslint(),
tsconfigPaths(), tsconfigPaths(),
visualizer(), visualizer(),
], ],
@@ -29,10 +30,11 @@ export default defineConfig({
"/openapi.json": "http://localhost:8083", "/openapi.json": "http://localhost:8083",
"/custom_css.css": "http://localhost:8083", "/custom_css.css": "http://localhost:8083",
"/custom_js.js": "http://localhost:8083", "/custom_js.js": "http://localhost:8083",
"/logout": "http://localhost:8083",
}, },
}, },
build: { build: {
chunkSizeWarningLimit: 3000, chunkSizeWarningLimit: 3500,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: id => { manualChunks: id => {
@@ -44,4 +46,4 @@ export default defineConfig({
}, },
}, },
}, },
}) }))

View File

@@ -67,6 +67,9 @@ app:
# entries to keep per feed, old entries will be deleted, 0 to disable # entries to keep per feed, old entries will be deleted, 0 to disable
maxFeedCapacity: 500 maxFeedCapacity: 500
# entries older than this will be deleted, 0 to disable
maxEntriesAgeDays: 365
# limit the number of feeds a user can subscribe to, 0 to disable # limit the number of feeds a user can subscribe to, 0 to disable
maxFeedsPerUser: 0 maxFeedsPerUser: 0
@@ -92,11 +95,11 @@ app:
# ------------------- # -------------------
# for MariaDB # for MariaDB
# driverClass is org.mariadb.jdbc.Driver # driverClass is org.mariadb.jdbc.Driver
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true # url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
# #
# for MySQL # for MySQL
# driverClass is com.mysql.cj.jdbc.Driver # driverClass is com.mysql.cj.jdbc.Driver
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true # url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
# #
# for PostgreSQL # for PostgreSQL
# driverClass is org.postgresql.Driver # driverClass is org.postgresql.Driver

View File

@@ -67,6 +67,9 @@ app:
# entries to keep per feed, old entries will be deleted, 0 to disable # entries to keep per feed, old entries will be deleted, 0 to disable
maxFeedCapacity: 500 maxFeedCapacity: 500
# entries older than this will be deleted, 0 to disable
maxEntriesAgeDays: 365
# limit the number of feeds a user can subscribe to, 0 to disable # limit the number of feeds a user can subscribe to, 0 to disable
maxFeedsPerUser: 0 maxFeedsPerUser: 0
@@ -92,11 +95,11 @@ app:
# ------------------- # -------------------
# for MariaDB # for MariaDB
# driverClass is org.mariadb.jdbc.Driver # driverClass is org.mariadb.jdbc.Driver
# url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true # url is jdbc:mariadb://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
# #
# for MySQL # for MySQL
# driverClass is com.mysql.cj.jdbc.Driver # driverClass is com.mysql.cj.jdbc.Driver
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true # url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true&timezone=UTC
# #
# for PostgreSQL # for PostgreSQL
# driverClass is org.postgresql.Driver # driverClass is org.postgresql.Driver

View File

@@ -12,8 +12,8 @@ services:
postgresql: postgresql:
image: postgres image: postgres
environment: environment:
POSTGRES_USER: root - POSTGRES_USER=root
POSTGRES_PASSWORD: root - POSTGRES_PASSWORD=root
POSTGRES_DB: commafeed - POSTGRES_DB=commafeed
ports: ports:
- 5432:5432 - 5432:5432

View File

@@ -6,14 +6,14 @@
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>4.0.0</version> <version>4.3.2</version>
</parent> </parent>
<artifactId>commafeed-server</artifactId> <artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name> <name>CommaFeed Server</name>
<properties> <properties>
<guice.version>7.0.0</guice.version> <guice.version>7.0.0</guice.version>
<querydsl.version>5.0.0</querydsl.version> <querydsl.version>5.1.0</querydsl.version>
<rome.version>2.1.0</rome.version> <rome.version>2.1.0</rome.version>
</properties> </properties>
@@ -22,7 +22,7 @@
<dependency> <dependency>
<groupId>io.dropwizard</groupId> <groupId>io.dropwizard</groupId>
<artifactId>dropwizard-dependencies</artifactId> <artifactId>dropwizard-dependencies</artifactId>
<version>4.0.5</version> <version>4.0.6</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@@ -31,23 +31,17 @@
<build> <build>
<finalName>commafeed</finalName> <finalName>commafeed</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.2.3</version> <version>3.2.5</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId> <artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.3</version> <version>3.2.5</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
@@ -69,7 +63,8 @@
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<generateGitPropertiesFile>false</generateGitPropertiesFile> <generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
<failOnNoGitDirectory>false</failOnNoGitDirectory> <failOnNoGitDirectory>false</failOnNoGitDirectory>
<failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo> <failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo>
</configuration> </configuration>
@@ -77,7 +72,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version> <version>3.5.2</version>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.kordamp.shade</groupId> <groupId>org.kordamp.shade</groupId>
@@ -126,7 +121,7 @@
<plugin> <plugin>
<groupId>io.swagger.core.v3</groupId> <groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-maven-plugin-jakarta</artifactId> <artifactId>swagger-maven-plugin-jakarta</artifactId>
<version>2.2.19</version> <version>2.2.20</version>
<?m2e ignore?> <?m2e ignore?>
<configuration> <configuration>
<outputPath>${project.build.directory}/classes/assets</outputPath> <outputPath>${project.build.directory}/classes/assets</outputPath>
@@ -189,7 +184,7 @@
<plugin> <plugin>
<groupId>com.diffplug.spotless</groupId> <groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId> <artifactId>spotless-maven-plugin</artifactId>
<version>2.41.1</version> <version>2.43.0</version>
<?m2e ignore?> <?m2e ignore?>
<executions> <executions>
<execution> <execution>
@@ -216,7 +211,7 @@
<dependency> <dependency>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId> <artifactId>commafeed-client</artifactId>
<version>4.0.0</version> <version>4.3.2</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -244,6 +239,10 @@
<groupId>io.dropwizard</groupId> <groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId> <artifactId>dropwizard-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-unix-socket</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.dropwizard</groupId> <groupId>io.dropwizard</groupId>
<artifactId>dropwizard-hibernate</artifactId> <artifactId>dropwizard-hibernate</artifactId>
@@ -268,21 +267,20 @@
<groupId>io.dropwizard.metrics</groupId> <groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-json</artifactId> <artifactId>metrics-json</artifactId>
</dependency> </dependency>
<dependency>
<groupId>be.tomcools</groupId>
<artifactId>dropwizard-websocket-jsr356-bundle</artifactId>
<version>4.0.0</version>
</dependency>
<dependency> <dependency>
<groupId>io.whitfin</groupId> <groupId>io.whitfin</groupId>
<artifactId>dropwizard-environment-substitutor</artifactId> <artifactId>dropwizard-environment-substitutor</artifactId>
<version>1.1.1</version> <version>1.1.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-jakarta-server</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.swagger.core.v3</groupId> <groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId> <artifactId>swagger-annotations</artifactId>
<version>2.2.19</version> <version>2.2.20</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -338,7 +336,7 @@
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>
<version>5.1.0</version> <version>5.1.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.sun.mail</groupId> <groupId>com.sun.mail</groupId>
@@ -370,7 +368,7 @@
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
<version>1.17.1</version> <version>1.17.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.ibm.icu</groupId> <groupId>com.ibm.icu</groupId>
@@ -390,7 +388,7 @@
<dependency> <dependency>
<groupId>org.gwtproject</groupId> <groupId>org.gwtproject</groupId>
<artifactId>gwt-servlet</artifactId> <artifactId>gwt-servlet</artifactId>
<version>2.10.0</version> <version>2.11.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents.client5</groupId> <groupId>org.apache.httpcomponents.client5</groupId>
@@ -399,35 +397,39 @@
<dependency> <dependency>
<groupId>io.github.hakky54</groupId> <groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-apache5</artifactId> <artifactId>sslcontext-kickstart-for-apache5</artifactId>
<version>8.2.0</version> <version>8.3.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.apis</groupId> <groupId>com.google.apis</groupId>
<artifactId>google-api-services-youtube</artifactId> <artifactId>google-api-services-youtube</artifactId>
<version>v3-rev20231011-2.0.0</version> <version>v3-rev20240225-2.0.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<!-- stay on 2.1 because 2.2 file format changed to version '3' -->
<version>2.1.214</version><!--$NO-MVN-MAN-VER$ -->
</dependency> </dependency>
<dependency>
<groupId>com.manticore-projects.tools</groupId>
<artifactId>h2migrationtool</artifactId>
<version>1.4</version>
</dependency>
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version> <version>8.3.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mariadb.jdbc</groupId> <groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId> <artifactId>mariadb-java-client</artifactId>
<version>3.3.1</version> <version>3.3.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>42.7.1</version> <version>42.7.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.sourceforge.jtds</groupId> <groupId>net.sourceforge.jtds</groupId>
@@ -488,7 +490,7 @@
<dependency> <dependency>
<groupId>com.microsoft.playwright</groupId> <groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId> <artifactId>playwright</artifactId>
<version>1.40.0</version> <version>1.41.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>

View File

@@ -1,15 +1,19 @@
package com.commafeed; package com.commafeed;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import com.codahale.metrics.json.MetricsModule; import com.codahale.metrics.json.MetricsModule;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.feed.FeedRefreshEngine; import com.commafeed.backend.feed.FeedRefreshEngine;
import com.commafeed.backend.model.AbstractModel; import com.commafeed.backend.model.AbstractModel;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
@@ -22,8 +26,9 @@ import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole; import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserSettings; import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.service.DatabaseStartupService;
import com.commafeed.backend.service.UserService; import com.commafeed.backend.service.UserService;
import com.commafeed.backend.service.db.DatabaseStartupService;
import com.commafeed.backend.service.db.H2MigrationService;
import com.commafeed.backend.task.ScheduledTask; import com.commafeed.backend.task.ScheduledTask;
import com.commafeed.frontend.auth.PasswordConstraintValidator; import com.commafeed.frontend.auth.PasswordConstraintValidator;
import com.commafeed.frontend.auth.SecurityCheckFactoryProvider; import com.commafeed.frontend.auth.SecurityCheckFactoryProvider;
@@ -42,19 +47,21 @@ import com.commafeed.frontend.servlet.RobotsTxtDisallowAllServlet;
import com.commafeed.frontend.session.SessionHelperFactoryProvider; import com.commafeed.frontend.session.SessionHelperFactoryProvider;
import com.commafeed.frontend.ws.WebSocketConfigurator; import com.commafeed.frontend.ws.WebSocketConfigurator;
import com.commafeed.frontend.ws.WebSocketEndpoint; import com.commafeed.frontend.ws.WebSocketEndpoint;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import be.tomcools.dropwizard.websocket.WebsocketBundle;
import io.dropwizard.assets.AssetsBundle; import io.dropwizard.assets.AssetsBundle;
import io.dropwizard.configuration.DefaultConfigurationFactoryFactory; import io.dropwizard.configuration.DefaultConfigurationFactoryFactory;
import io.dropwizard.configuration.EnvironmentVariableSubstitutor; import io.dropwizard.configuration.EnvironmentVariableSubstitutor;
import io.dropwizard.configuration.SubstitutingSourceProvider; import io.dropwizard.configuration.SubstitutingSourceProvider;
import io.dropwizard.core.Application; import io.dropwizard.core.Application;
import io.dropwizard.core.ConfiguredBundle;
import io.dropwizard.core.setup.Bootstrap; import io.dropwizard.core.setup.Bootstrap;
import io.dropwizard.core.setup.Environment; import io.dropwizard.core.setup.Environment;
import io.dropwizard.db.DataSourceFactory; import io.dropwizard.db.DataSourceFactory;
@@ -76,10 +83,9 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
public static final String USERNAME_ADMIN = "admin"; public static final String USERNAME_ADMIN = "admin";
public static final String USERNAME_DEMO = "demo"; public static final String USERNAME_DEMO = "demo";
public static final Date STARTUP_TIME = new Date(); public static final Instant STARTUP_TIME = Instant.now();
private HibernateBundle<CommaFeedConfiguration> hibernateBundle; private HibernateBundle<CommaFeedConfiguration> hibernateBundle;
private WebsocketBundle<CommaFeedConfiguration> websocketBundle;
@Override @Override
public String getName() { public String getName() {
@@ -89,10 +95,32 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
@Override @Override
public void initialize(Bootstrap<CommaFeedConfiguration> bootstrap) { public void initialize(Bootstrap<CommaFeedConfiguration> bootstrap) {
configureEnvironmentSubstitutor(bootstrap); configureEnvironmentSubstitutor(bootstrap);
configureObjectMapper(bootstrap.getObjectMapper());
bootstrap.getObjectMapper().registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false)); // run h2 migration as the first bundle because we need to migrate before hibernate is initialized
bootstrap.addBundle(new ConfiguredBundle<CommaFeedConfiguration>() {
@Override
public void run(CommaFeedConfiguration config, Environment environment) throws Exception {
DataSourceFactory dataSourceFactory = config.getDataSourceFactory();
String url = dataSourceFactory.getUrl();
if (isFileBasedH2(url)) {
Path path = getFilePath(url);
String user = dataSourceFactory.getUser();
String password = dataSourceFactory.getPassword();
new H2MigrationService().migrateIfNeeded(path, user, password);
}
}
private boolean isFileBasedH2(String url) {
return url.startsWith("jdbc:h2:") && !url.startsWith("jdbc:h2:mem:");
}
private Path getFilePath(String url) {
String name = url.substring("jdbc:h2:".length()).split(";")[0];
return Paths.get(name + ".mv.db");
}
});
bootstrap.addBundle(websocketBundle = new WebsocketBundle<>());
bootstrap.addBundle(hibernateBundle = new HibernateBundle<>(AbstractModel.class, Feed.class, FeedCategory.class, FeedEntry.class, bootstrap.addBundle(hibernateBundle = new HibernateBundle<>(AbstractModel.class, Feed.class, FeedCategory.class, FeedEntry.class,
FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class, FeedSubscription.class, User.class, UserRole.class, FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class, FeedSubscription.class, User.class, UserRole.class,
UserSettings.class) { UserSettings.class) {
@@ -134,6 +162,15 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
bootstrap.setConfigurationSourceProvider(buildEnvironmentSubstitutor(bootstrap)); bootstrap.setConfigurationSourceProvider(buildEnvironmentSubstitutor(bootstrap));
} }
private static void configureObjectMapper(ObjectMapper objectMapper) {
// read and write instants as milliseconds instead of nanoseconds
objectMapper.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));
}
private static EnvironmentSubstitutor buildEnvironmentSubstitutor(Bootstrap<CommaFeedConfiguration> bootstrap) { private static EnvironmentSubstitutor buildEnvironmentSubstitutor(Bootstrap<CommaFeedConfiguration> bootstrap) {
// enable config.yml string substitution // enable config.yml string substitution
// e.g. having a custom config.yml file with app.session.path=${SOME_ENV_VAR} will substitute SOME_ENV_VAR // e.g. having a custom config.yml file with app.session.path=${SOME_ENV_VAR} will substitute SOME_ENV_VAR
@@ -156,7 +193,9 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
environment.servlets().setSessionHandler(config.getSessionHandlerFactory().build(config.getDataSourceFactory())); environment.servlets().setSessionHandler(config.getSessionHandlerFactory().build(config.getDataSourceFactory()));
// support for "@SecurityCheck User user" injection // support for "@SecurityCheck User user" injection
environment.jersey().register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserService.class))); environment.jersey()
.register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserDAO.class),
injector.getInstance(UserService.class), config));
// support for "@Context SessionHelper sessionHelper" injection // support for "@Context SessionHelper sessionHelper" injection
environment.jersey().register(new SessionHelperFactoryProvider.Binder()); environment.jersey().register(new SessionHelperFactoryProvider.Binder());
@@ -182,10 +221,13 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
} }
// WebSocket endpoint // WebSocket endpoint
ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder.create(WebSocketEndpoint.class, "/ws") JakartaWebSocketServletContainerInitializer.configure(environment.getApplicationContext(), (context, container) -> {
.configurator(injector.getInstance(WebSocketConfigurator.class)) container.setDefaultMaxSessionIdleTimeout(config.getApplicationSettings().getWebsocketPingInterval().toMilliseconds() + 10000);
.build();
websocketBundle.addEndpoint(serverEndpointConfig); container.addEndpoint(ServerEndpointConfig.Builder.create(WebSocketEndpoint.class, "/ws")
.configurator(injector.getInstance(WebSocketConfigurator.class))
.build());
});
// Scheduled tasks // Scheduled tasks
Set<ScheduledTask> tasks = injector.getInstance(Key.get(new TypeLiteral<>() { Set<ScheduledTask> tasks = injector.getInstance(Key.get(new TypeLiteral<>() {
@@ -208,6 +250,12 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
environment.servlets() environment.servlets()
.addFilter("index-cache-busting-filter", new CacheBustingFilter()) .addFilter("index-cache-busting-filter", new CacheBustingFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/"); .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/");
// prevent caching openapi files, so that the documentation is always up to date
environment.servlets()
.addFilter("openapi-cache-busting-filter", new CacheBustingFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/openapi.json", "/openapi.yaml");
// prevent caching REST resources, except for favicons // prevent caching REST resources, except for favicons
environment.servlets().addFilter("rest-cache-busting-filter", new CacheBustingFilter() { environment.servlets().addFilter("rest-cache-busting-filter", new CacheBustingFilter() {
@Override @Override

View File

@@ -1,16 +1,15 @@
package com.commafeed; package com.commafeed;
import java.util.Date; import java.io.IOException;
import java.util.ResourceBundle; import java.io.InputStream;
import java.time.Instant;
import org.apache.commons.lang3.time.DateUtils; import java.time.temporal.ChronoUnit;
import java.util.Properties;
import com.commafeed.backend.cache.RedisPoolFactory; import com.commafeed.backend.cache.RedisPoolFactory;
import com.commafeed.frontend.session.SessionHandlerFactory; import com.commafeed.frontend.session.SessionHandlerFactory;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import be.tomcools.dropwizard.websocket.WebsocketBundleConfiguration;
import be.tomcools.dropwizard.websocket.WebsocketConfiguration;
import io.dropwizard.core.Configuration; import io.dropwizard.core.Configuration;
import io.dropwizard.db.DataSourceFactory; import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.util.Duration; import io.dropwizard.util.Duration;
@@ -24,7 +23,7 @@ import lombok.Setter;
@Getter @Getter
@Setter @Setter
public class CommaFeedConfiguration extends Configuration implements WebsocketBundleConfiguration { public class CommaFeedConfiguration extends Configuration {
public enum CacheType { public enum CacheType {
NOOP, REDIS NOOP, REDIS
@@ -54,17 +53,17 @@ public class CommaFeedConfiguration extends Configuration implements WebsocketBu
private final String gitCommit; private final String gitCommit;
public CommaFeedConfiguration() { public CommaFeedConfiguration() {
ResourceBundle bundle = ResourceBundle.getBundle("application"); Properties properties = new Properties();
try (InputStream stream = getClass().getResourceAsStream("/git.properties")) {
if (stream != null) {
properties.load(stream);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
this.version = bundle.getString("version"); this.version = properties.getProperty("git.build.version", "unknown");
this.gitCommit = bundle.getString("git.commit"); this.gitCommit = properties.getProperty("git.commit.id.abbrev", "unknown");
}
@Override
public WebsocketConfiguration getWebsocketConfiguration() {
WebsocketConfiguration config = new WebsocketConfiguration();
config.setMaxSessionIdleTimeout(getApplicationSettings().getWebsocketPingInterval().toMilliseconds() + 10000);
return config;
} }
@Getter @Getter
@@ -146,6 +145,11 @@ public class CommaFeedConfiguration extends Configuration implements WebsocketBu
@Valid @Valid
private Integer maxFeedCapacity; private Integer maxFeedCapacity;
@NotNull
@Min(0)
@Valid
private Integer maxEntriesAgeDays = 0;
@NotNull @NotNull
@Valid @Valid
private Integer maxFeedsPerUser = 0; private Integer maxFeedsPerUser = 0;
@@ -170,9 +174,8 @@ public class CommaFeedConfiguration extends Configuration implements WebsocketBu
private Duration treeReloadInterval = Duration.seconds(30); private Duration treeReloadInterval = Duration.seconds(30);
public Date getUnreadThreshold() { public Instant getUnreadThreshold() {
int keepStatusDays = getKeepStatusDays(); return getKeepStatusDays() > 0 ? Instant.now().minus(getKeepStatusDays(), ChronoUnit.DAYS) : null;
return keepStatusDays > 0 ? DateUtils.addDays(new Date(), -1 * keepStatusDays) : null;
} }
} }

View File

@@ -19,6 +19,7 @@ import com.commafeed.backend.favicon.DefaultFaviconFetcher;
import com.commafeed.backend.favicon.FacebookFaviconFetcher; import com.commafeed.backend.favicon.FacebookFaviconFetcher;
import com.commafeed.backend.favicon.YoutubeFaviconFetcher; import com.commafeed.backend.favicon.YoutubeFaviconFetcher;
import com.commafeed.backend.task.DemoAccountCleanupTask; import com.commafeed.backend.task.DemoAccountCleanupTask;
import com.commafeed.backend.task.EntriesExceedingFeedCapacityCleanupTask;
import com.commafeed.backend.task.OldEntriesCleanupTask; import com.commafeed.backend.task.OldEntriesCleanupTask;
import com.commafeed.backend.task.OldStatusesCleanupTask; import com.commafeed.backend.task.OldStatusesCleanupTask;
import com.commafeed.backend.task.OrphanedContentsCleanupTask; import com.commafeed.backend.task.OrphanedContentsCleanupTask;
@@ -66,6 +67,7 @@ public class CommaFeedModule extends AbstractModule {
Multibinder<ScheduledTask> taskMultibinder = Multibinder.newSetBinder(binder(), ScheduledTask.class); Multibinder<ScheduledTask> taskMultibinder = Multibinder.newSetBinder(binder(), ScheduledTask.class);
taskMultibinder.addBinding().to(OldStatusesCleanupTask.class); taskMultibinder.addBinding().to(OldStatusesCleanupTask.class);
taskMultibinder.addBinding().to(EntriesExceedingFeedCapacityCleanupTask.class);
taskMultibinder.addBinding().to(OldEntriesCleanupTask.class); taskMultibinder.addBinding().to(OldEntriesCleanupTask.class);
taskMultibinder.addBinding().to(OrphanedFeedsCleanupTask.class); taskMultibinder.addBinding().to(OrphanedFeedsCleanupTask.class);
taskMultibinder.addBinding().to(OrphanedContentsCleanupTask.class); taskMultibinder.addBinding().to(OrphanedContentsCleanupTask.class);

View File

@@ -10,14 +10,14 @@ import java.util.List;
* *
* *
*/ */
public class FixedSizeSortedSet<E> { public class FixedSizeSortedList<E> {
private final List<E> inner; private final List<E> inner;
private final Comparator<? super E> comparator; private final Comparator<? super E> comparator;
private final int capacity; private final int capacity;
public FixedSizeSortedSet(int capacity, Comparator<? super E> comparator) { public FixedSizeSortedList(int capacity, Comparator<? super E> comparator) {
this.inner = new ArrayList<>(Math.max(0, capacity)); this.inner = new ArrayList<>(Math.max(0, capacity));
this.capacity = capacity < 0 ? Integer.MAX_VALUE : capacity; this.capacity = capacity < 0 ? Integer.MAX_VALUE : capacity;
this.comparator = comparator; this.comparator = comparator;

View File

@@ -31,7 +31,6 @@ import com.commafeed.CommaFeedConfiguration;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import io.dropwizard.lifecycle.Managed;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import lombok.Getter; import lombok.Getter;
@@ -44,7 +43,7 @@ import nl.altindag.ssl.apache5.util.Apache5SslUtils;
* *
*/ */
@Singleton @Singleton
public class HttpGetter implements Managed { public class HttpGetter {
private final CloseableHttpClient client; private final CloseableHttpClient client;
@@ -154,11 +153,6 @@ public class HttpGetter implements Managed {
.build(); .build();
} }
@Override
public void stop() throws Exception {
client.close();
}
@Getter @Getter
public static class NotModifiedException extends Exception { public static class NotModifiedException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -1,11 +1,12 @@
package com.commafeed.backend.cache; package com.commafeed.backend.cache;
import java.util.List; import java.util.List;
import java.util.Set;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import com.commafeed.backend.feed.parser.FeedParserResult.Entry;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.frontend.model.Category; import com.commafeed.frontend.model.Category;
@@ -14,12 +15,12 @@ import com.commafeed.frontend.model.UnreadCount;
public abstract class CacheService { public abstract class CacheService {
// feed entries for faster refresh // feed entries for faster refresh
public abstract List<String> getLastEntries(Feed feed); public abstract Set<String> getLastEntries(Feed feed);
public abstract void setLastEntries(Feed feed, List<String> entries); public abstract void setLastEntries(Feed feed, List<String> entries);
public String buildUniqueEntryKey(Feed feed, FeedEntry entry) { public String buildUniqueEntryKey(Entry entry) {
return DigestUtils.sha1Hex(entry.getGuid() + entry.getUrl()); return DigestUtils.sha1Hex(entry.guid() + entry.url());
} }
// user categories // user categories

View File

@@ -2,6 +2,7 @@ package com.commafeed.backend.cache;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
@@ -12,8 +13,8 @@ import com.commafeed.frontend.model.UnreadCount;
public class NoopCacheService extends CacheService { public class NoopCacheService extends CacheService {
@Override @Override
public List<String> getLastEntries(Feed feed) { public Set<String> getLastEntries(Feed feed) {
return Collections.emptyList(); return Collections.emptySet();
} }
@Override @Override

View File

@@ -1,6 +1,5 @@
package com.commafeed.backend.cache; package com.commafeed.backend.cache;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -13,6 +12,7 @@ import com.commafeed.frontend.model.Category;
import com.commafeed.frontend.model.UnreadCount; import com.commafeed.frontend.model.UnreadCount;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -24,19 +24,16 @@ import redis.clients.jedis.Pipeline;
@RequiredArgsConstructor @RequiredArgsConstructor
public class RedisCacheService extends CacheService { public class RedisCacheService extends CacheService {
private static final ObjectMapper MAPPER = new ObjectMapper(); private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());
private final JedisPool pool; private final JedisPool pool;
@Override @Override
public List<String> getLastEntries(Feed feed) { public Set<String> getLastEntries(Feed feed) {
List<String> list = new ArrayList<>();
try (Jedis jedis = pool.getResource()) { try (Jedis jedis = pool.getResource()) {
String key = buildRedisEntryKey(feed); String key = buildRedisEntryKey(feed);
Set<String> members = jedis.smembers(key); return jedis.smembers(key);
list.addAll(members);
} }
return list;
} }
@Override @Override

View File

@@ -1,16 +1,14 @@
package com.commafeed.backend.dao; package com.commafeed.backend.dao;
import java.util.Date; import java.time.Instant;
import java.util.List; import java.util.List;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.QFeed; import com.commafeed.backend.model.QFeed;
import com.commafeed.backend.model.QFeedSubscription; import com.commafeed.backend.model.QFeedSubscription;
import com.google.common.collect.Iterables;
import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQuery;
@@ -28,8 +26,8 @@ public class FeedDAO extends GenericDAO<Feed> {
super(sessionFactory); super(sessionFactory);
} }
public List<Feed> findNextUpdatable(int count, Date lastLoginThreshold) { public List<Feed> findNextUpdatable(int count, Instant lastLoginThreshold) {
JPAQuery<Feed> query = query().selectFrom(feed).where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date()))); JPAQuery<Feed> query = query().selectFrom(feed).where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(Instant.now())));
if (lastLoginThreshold != null) { if (lastLoginThreshold != null) {
query.where(JPAExpressions.selectOne() query.where(JPAExpressions.selectOne()
.from(subscription) .from(subscription)
@@ -41,17 +39,18 @@ public class FeedDAO extends GenericDAO<Feed> {
return query.orderBy(feed.disabledUntil.asc()).limit(count).fetch(); return query.orderBy(feed.disabledUntil.asc()).limit(count).fetch();
} }
public void setDisabledUntil(List<Long> feedIds, Date date) { public void setDisabledUntil(List<Long> feedIds, Instant date) {
updateQuery(feed).set(feed.disabledUntil, date).where(feed.id.in(feedIds)).execute(); updateQuery(feed).set(feed.disabledUntil, date).where(feed.id.in(feedIds)).execute();
} }
public Feed findByUrl(String normalizedUrl) { public Feed findByUrl(String normalizedUrl, String normalizedUrlHash) {
List<Feed> feeds = query().selectFrom(feed).where(feed.normalizedUrlHash.eq(DigestUtils.sha1Hex(normalizedUrl))).fetch(); return query().selectFrom(feed)
Feed feed = Iterables.getFirst(feeds, null); .where(feed.normalizedUrlHash.eq(normalizedUrlHash))
if (feed != null && StringUtils.equals(normalizedUrl, feed.getNormalizedUrl())) { .fetch()
return feed; .stream()
} .filter(f -> StringUtils.equals(normalizedUrl, f.getNormalizedUrl()))
return null; .findFirst()
.orElse(null);
} }
public List<Feed> findWithoutSubscriptions(int max) { public List<Feed> findWithoutSubscriptions(int max) {

View File

@@ -1,8 +1,8 @@
package com.commafeed.backend.dao; package com.commafeed.backend.dao;
import java.time.Instant;
import java.util.List; import java.util.List;
import org.apache.commons.codec.digest.DigestUtils;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
@@ -26,12 +26,8 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
super(sessionFactory); super(sessionFactory);
} }
public Long findExisting(String guid, Feed feed) { public FeedEntry findExisting(String guidHash, Feed feed) {
return query().select(entry.id) return query().select(entry).from(entry).where(entry.guidHash.eq(guidHash), entry.feed.eq(feed)).limit(1).fetchOne();
.from(entry)
.where(entry.guidHash.eq(DigestUtils.sha1Hex(guid)), entry.feed.eq(feed))
.limit(1)
.fetchOne();
} }
public List<FeedCapacity> findFeedsExceedingCapacity(long maxCapacity, long max) { public List<FeedCapacity> findFeedsExceedingCapacity(long maxCapacity, long max) {
@@ -50,6 +46,17 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
return delete(list); return delete(list);
} }
/**
* Delete entries older than a certain date
*/
public int deleteEntriesOlderThan(Instant olderThan, long max) {
List<FeedEntry> list = query().selectFrom(entry).where(entry.updated.lt(olderThan)).orderBy(entry.updated.asc()).limit(max).fetch();
return delete(list);
}
/**
* Delete the oldest entries of a feed
*/
public int deleteOldEntries(Long feedId, long max) { public int deleteOldEntries(Long feedId, long max) {
List<FeedEntry> list = query().selectFrom(entry).where(entry.feed.id.eq(feedId)).orderBy(entry.updated.asc()).limit(max).fetch(); List<FeedEntry> list = query().selectFrom(entry).where(entry.feed.id.eq(feedId)).orderBy(entry.updated.asc()).limit(max).fetch();
return delete(list); return delete(list);

View File

@@ -1,8 +1,8 @@
package com.commafeed.backend.dao; package com.commafeed.backend.dao;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
@@ -10,7 +10,7 @@ import org.apache.commons.lang3.builder.CompareToBuilder;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.FixedSizeSortedSet; import com.commafeed.backend.FixedSizeSortedList;
import com.commafeed.backend.feed.FeedEntryKeyword; import com.commafeed.backend.feed.FeedEntryKeyword;
import com.commafeed.backend.feed.FeedEntryKeyword.Mode; import com.commafeed.backend.feed.FeedEntryKeyword.Mode;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
@@ -73,8 +73,8 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
private FeedEntryStatus handleStatus(User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) { private FeedEntryStatus handleStatus(User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
if (status == null) { if (status == null) {
Date unreadThreshold = config.getApplicationSettings().getUnreadThreshold(); Instant unreadThreshold = config.getApplicationSettings().getUnreadThreshold();
boolean read = unreadThreshold != null && entry.getUpdated().before(unreadThreshold); boolean read = unreadThreshold != null && entry.getUpdated().isBefore(unreadThreshold);
status = new FeedEntryStatus(user, sub, entry); status = new FeedEntryStatus(user, sub, entry);
status.setRead(read); status.setRead(read);
status.setMarkable(!read); status.setMarkable(!read);
@@ -90,7 +90,8 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
return status; return status;
} }
public List<FeedEntryStatus> findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { 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()); JPAQuery<FeedEntryStatus> query = query().selectFrom(status).where(status.user.eq(user), status.starred.isTrue());
if (newerThan != null) { if (newerThan != null) {
query.where(status.entryInserted.gt(newerThan)); query.where(status.entryInserted.gt(newerThan));
@@ -114,7 +115,8 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
private JPAQuery<FeedEntry> buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List<FeedEntryKeyword> keywords, private JPAQuery<FeedEntry> buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List<FeedEntryKeyword> keywords,
Date newerThan, int offset, int limit, ReadingOrder order, FeedEntryStatus last, String tag, Long minEntryId, Long maxEntryId) { Instant newerThan, int offset, int limit, ReadingOrder order, FeedEntryStatus last, String tag, Long minEntryId,
Long maxEntryId) {
JPAQuery<FeedEntry> query = query().selectFrom(entry).where(entry.feed.eq(sub.getFeed())); JPAQuery<FeedEntry> query = query().selectFrom(entry).where(entry.feed.eq(sub.getFeed()));
@@ -139,7 +141,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
or.or(status.read.isFalse()); or.or(status.read.isFalse());
query.where(or); query.where(or);
Date unreadThreshold = config.getApplicationSettings().getUnreadThreshold(); Instant unreadThreshold = config.getApplicationSettings().getUnreadThreshold();
if (unreadThreshold != null) { if (unreadThreshold != null) {
query.where(entry.updated.goe(unreadThreshold)); query.where(entry.updated.goe(unreadThreshold));
} }
@@ -193,22 +195,22 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly, public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly,
List<FeedEntryKeyword> keywords, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, List<FeedEntryKeyword> keywords, Instant newerThan, int offset, int limit, ReadingOrder order, boolean includeContent,
boolean onlyIds, String tag, Long minEntryId, Long maxEntryId) { boolean onlyIds, String tag, Long minEntryId, Long maxEntryId) {
int capacity = offset + limit; int capacity = offset + limit;
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC; Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<>(capacity, comparator); FixedSizeSortedList<FeedEntryStatus> fssl = new FixedSizeSortedList<>(capacity, comparator);
for (FeedSubscription sub : subs) { for (FeedSubscription sub : subs) {
FeedEntryStatus last = (order != null && set.isFull()) ? set.last() : null; FeedEntryStatus last = (order != null && fssl.isFull()) ? fssl.last() : null;
JPAQuery<FeedEntry> query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag, minEntryId, JPAQuery<FeedEntry> query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag, minEntryId,
maxEntryId); maxEntryId);
List<Tuple> tuples = query.select(entry.id, entry.updated, status.id, entry.content.title).fetch(); List<Tuple> tuples = query.select(entry.id, entry.updated, status.id, entry.content.title).fetch();
for (Tuple tuple : tuples) { for (Tuple tuple : tuples) {
Long id = tuple.get(entry.id); Long id = tuple.get(entry.id);
Date updated = tuple.get(entry.updated); Instant updated = tuple.get(entry.updated);
Long statusId = tuple.get(status.id); Long statusId = tuple.get(status.id);
FeedEntryContent content = new FeedEntryContent(); FeedEntryContent content = new FeedEntryContent();
@@ -225,11 +227,11 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
status.setEntry(entry); status.setEntry(entry);
status.setSubscription(sub); status.setSubscription(sub);
set.add(status); fssl.add(status);
} }
} }
List<FeedEntryStatus> placeholders = set.asList(); List<FeedEntryStatus> placeholders = fssl.asList();
int size = placeholders.size(); int size = placeholders.size();
if (size < offset) { if (size < offset) {
return new ArrayList<>(); return new ArrayList<>();
@@ -260,7 +262,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
List<Tuple> tuples = query.select(entry.count(), entry.updated.max()).fetch(); List<Tuple> tuples = query.select(entry.count(), entry.updated.max()).fetch();
for (Tuple tuple : tuples) { for (Tuple tuple : tuples) {
Long count = tuple.get(entry.count()); Long count = tuple.get(entry.count());
Date updated = tuple.get(entry.updated.max()); Instant updated = tuple.get(entry.updated.max());
uc = new UnreadCount(subscription.getId(), count == null ? 0 : count, updated); uc = new UnreadCount(subscription.getId(), count == null ? 0 : count, updated);
} }
return uc; return uc;
@@ -276,7 +278,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
return results; return results;
} }
public long deleteOldStatuses(Date olderThan, int limit) { public long deleteOldStatuses(Instant olderThan, int limit) {
List<Long> ids = query().select(status.id) List<Long> ids = query().select(status.id)
.from(status) .from(status)
.where(status.entryInserted.lt(olderThan), status.starred.isFalse()) .where(status.entryInserted.lt(olderThan), status.starred.isFalse())

View File

@@ -1,8 +1,7 @@
package com.commafeed.backend.feed; package com.commafeed.backend.feed;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.time.Instant;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.codec.binary.StringUtils;
@@ -11,16 +10,14 @@ import org.apache.commons.codec.digest.DigestUtils;
import com.commafeed.backend.HttpGetter; import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.HttpGetter.HttpResult; import com.commafeed.backend.HttpGetter.HttpResult;
import com.commafeed.backend.HttpGetter.NotModifiedException; import com.commafeed.backend.HttpGetter.NotModifiedException;
import com.commafeed.backend.feed.FeedParser.FeedParserResult; import com.commafeed.backend.feed.parser.FeedParser;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.feed.parser.FeedParserResult;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.urlprovider.FeedURLProvider; import com.commafeed.backend.urlprovider.FeedURLProvider;
import com.rometools.rome.io.FeedException; import com.rometools.rome.io.FeedException;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
@@ -35,8 +32,8 @@ public class FeedFetcher {
private final HttpGetter getter; private final HttpGetter getter;
private final Set<FeedURLProvider> urlProviders; private final Set<FeedURLProvider> urlProviders;
public FeedFetcherResult fetch(String feedUrl, boolean extractFeedUrlFromHtml, String lastModified, String eTag, Date lastPublishedDate, public FeedFetcherResult fetch(String feedUrl, boolean extractFeedUrlFromHtml, String lastModified, String eTag,
String lastContentHash) throws FeedException, IOException, NotModifiedException { Instant lastPublishedDate, String lastContentHash) throws FeedException, IOException, NotModifiedException {
log.debug("Fetching feed {}", feedUrl); log.debug("Fetching feed {}", feedUrl);
int timeout = 20000; int timeout = 20000;
@@ -79,20 +76,15 @@ public class FeedFetcher {
etagHeaderValueChanged ? result.getETag() : null); etagHeaderValueChanged ? result.getETag() : null);
} }
if (lastPublishedDate != null && parserResult.getFeed().getLastPublishedDate() != null if (lastPublishedDate != null && lastPublishedDate.equals(parserResult.lastPublishedDate())) {
&& lastPublishedDate.getTime() == parserResult.getFeed().getLastPublishedDate().getTime()) {
log.debug("publishedDate not modified: {}", feedUrl); log.debug("publishedDate not modified: {}", feedUrl);
throw new NotModifiedException("publishedDate not modified", throw new NotModifiedException("publishedDate not modified",
lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null, lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null,
etagHeaderValueChanged ? result.getETag() : null); etagHeaderValueChanged ? result.getETag() : null);
} }
Feed feed = parserResult.getFeed(); return new FeedFetcherResult(parserResult, result.getUrlAfterRedirect(), result.getLastModifiedSince(), result.getETag(), hash,
feed.setLastModifiedHeader(result.getLastModifiedSince()); result.getDuration());
feed.setEtagHeader(FeedUtils.truncate(result.getETag(), 255));
feed.setLastContentHash(hash);
return new FeedFetcherResult(parserResult.getFeed(), parserResult.getEntries(), parserResult.getTitle(),
result.getUrlAfterRedirect(), result.getDuration());
} }
private static String extractFeedUrl(Set<FeedURLProvider> urlProviders, String url, String urlContent) { private static String extractFeedUrl(Set<FeedURLProvider> urlProviders, String url, String urlContent) {
@@ -106,13 +98,8 @@ public class FeedFetcher {
return null; return null;
} }
@Value public record FeedFetcherResult(FeedParserResult feed, String urlAfterRedirect, String lastModifiedHeader, String lastETagHeader,
public static class FeedFetcherResult { String contentHash, long fetchDuration) {
Feed feed;
List<FeedEntry> entries;
String title;
String urlAfterRedirect;
long fetchDuration;
} }
} }

View File

@@ -1,263 +0,0 @@
package com.commafeed.backend.feed;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.xml.sax.InputSource;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent;
import com.google.common.collect.Iterables;
import com.rometools.modules.mediarss.MediaEntryModule;
import com.rometools.modules.mediarss.MediaModule;
import com.rometools.modules.mediarss.types.MediaGroup;
import com.rometools.modules.mediarss.types.Metadata;
import com.rometools.modules.mediarss.types.Thumbnail;
import com.rometools.rome.feed.synd.SyndCategory;
import com.rometools.rome.feed.synd.SyndContent;
import com.rometools.rome.feed.synd.SyndEnclosure;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.feed.synd.SyndLink;
import com.rometools.rome.feed.synd.SyndLinkImpl;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedInput;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.Value;
/**
* Parses raw xml as a Feed object
*/
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedParser {
private static final String ATOM_10_URI = "http://www.w3.org/2005/Atom";
private static final Namespace ATOM_10_NS = Namespace.getNamespace(ATOM_10_URI);
private static final Date START = new Date(86400000);
private static final Date END = new Date(1000L * Integer.MAX_VALUE - 86400000);
public FeedParserResult parse(String feedUrl, byte[] xml) throws FeedException {
try {
Charset encoding = FeedUtils.guessEncoding(xml);
String xmlString = FeedUtils.trimInvalidXmlCharacters(new String(xml, encoding));
if (xmlString == null) {
throw new FeedException("Input string is null for url " + feedUrl);
}
xmlString = FeedUtils.replaceHtmlEntitiesWithNumericEntities(xmlString);
InputSource source = new InputSource(new StringReader(xmlString));
SyndFeed rss = new SyndFeedInput().build(source);
handleForeignMarkup(rss);
String title = rss.getTitle();
Feed feed = new Feed();
feed.setUrl(feedUrl);
feed.setLink(rss.getLink());
List<FeedEntry> entries = new ArrayList<>();
for (SyndEntry item : rss.getEntries()) {
FeedEntry entry = new FeedEntry();
String guid = item.getUri();
if (StringUtils.isBlank(guid)) {
guid = item.getLink();
}
if (StringUtils.isBlank(guid)) {
// no guid and no link, skip entry
continue;
}
entry.setGuid(FeedUtils.truncate(guid, 2048));
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feedUrl), 2048));
// if link is empty but guid is used as url
if (StringUtils.isBlank(entry.getUrl()) && StringUtils.startsWith(entry.getGuid(), "http")) {
entry.setUrl(entry.getGuid());
}
FeedEntryContent content = new FeedEntryContent();
content.setContent(getContent(item));
content.setCategories(FeedUtils
.truncate(item.getCategories().stream().map(SyndCategory::getName).collect(Collectors.joining(", ")), 4096));
content.setTitle(getTitle(item));
content.setAuthor(StringUtils.trimToNull(item.getAuthor()));
SyndEnclosure enclosure = Iterables.getFirst(item.getEnclosures(), null);
if (enclosure != null) {
content.setEnclosureUrl(FeedUtils.truncate(enclosure.getUrl(), 2048));
content.setEnclosureType(enclosure.getType());
}
MediaEntryModule module = (MediaEntryModule) item.getModule(MediaModule.URI);
if (module != null) {
Media media = getMedia(module);
if (media != null) {
content.setMediaDescription(media.getDescription());
content.setMediaThumbnailUrl(FeedUtils.truncate(media.getThumbnailUrl(), 2048));
content.setMediaThumbnailWidth(media.getThumbnailWidth());
content.setMediaThumbnailHeight(media.getThumbnailHeight());
}
}
entry.setContent(content);
entries.add(entry);
}
Date lastEntryDate = null;
Date publishedDate = validateDate(rss.getPublishedDate(), false);
if (!entries.isEmpty()) {
List<Long> sortedTimestamps = FeedUtils.getSortedTimestamps(entries);
Long timestamp = sortedTimestamps.get(0);
lastEntryDate = new Date(timestamp);
publishedDate = (publishedDate == null || publishedDate.before(lastEntryDate)) ? lastEntryDate : publishedDate;
}
feed.setLastPublishedDate(publishedDate);
feed.setAverageEntryInterval(FeedUtils.averageTimeBetweenEntries(entries));
feed.setLastEntryDate(lastEntryDate);
return new FeedParserResult(feed, entries, title);
} catch (Exception e) {
throw new FeedException(String.format("Could not parse feed from %s : %s", feedUrl, e.getMessage()), e);
}
}
/**
* Adds atom links for rss feeds
*/
private void handleForeignMarkup(SyndFeed feed) {
List<Element> foreignMarkup = feed.getForeignMarkup();
if (foreignMarkup == null) {
return;
}
for (Element element : foreignMarkup) {
if ("link".equals(element.getName()) && ATOM_10_NS.equals(element.getNamespace())) {
SyndLink link = new SyndLinkImpl();
link.setRel(element.getAttributeValue("rel"));
link.setHref(element.getAttributeValue("href"));
feed.getLinks().add(link);
}
}
}
private Date getEntryUpdateDate(SyndEntry item) {
Date date = item.getUpdatedDate();
if (date == null) {
date = item.getPublishedDate();
}
if (date == null) {
date = new Date();
}
return date;
}
private Date validateDate(Date date, boolean nullToNow) {
Date now = new Date();
if (date == null) {
return nullToNow ? now : null;
}
if (date.before(START) || date.after(END)) {
return now;
}
if (date.after(now)) {
return now;
}
return date;
}
private String getContent(SyndEntry item) {
String content;
if (item.getContents().isEmpty()) {
content = item.getDescription() == null ? null : item.getDescription().getValue();
} else {
content = item.getContents().stream().map(SyndContent::getValue).collect(Collectors.joining(System.lineSeparator()));
}
return StringUtils.trimToNull(content);
}
private String getTitle(SyndEntry item) {
String title = item.getTitle();
if (StringUtils.isBlank(title)) {
Date date = item.getPublishedDate();
if (date != null) {
title = DateFormat.getInstance().format(date);
} else {
title = "(no title)";
}
}
return StringUtils.trimToNull(title);
}
private Media getMedia(MediaEntryModule module) {
Media media = getMedia(module.getMetadata());
if (media == null && ArrayUtils.isNotEmpty(module.getMediaGroups())) {
MediaGroup group = module.getMediaGroups()[0];
media = getMedia(group.getMetadata());
}
return media;
}
private Media getMedia(Metadata metadata) {
if (metadata == null) {
return null;
}
Media media = new Media();
media.setDescription(metadata.getDescription());
if (ArrayUtils.isNotEmpty(metadata.getThumbnail())) {
Thumbnail thumbnail = metadata.getThumbnail()[0];
media.setThumbnailWidth(thumbnail.getWidth());
media.setThumbnailHeight(thumbnail.getHeight());
if (thumbnail.getUrl() != null) {
media.setThumbnailUrl(thumbnail.getUrl().toString());
}
}
if (media.isEmpty()) {
return null;
}
return media;
}
@Data
private static class Media {
private String description;
private String thumbnailUrl;
private Integer thumbnailWidth;
private Integer thumbnailHeight;
public boolean isEmpty() {
return description == null && thumbnailUrl == null;
}
}
@Value
public static class FeedParserResult {
Feed feed;
List<FeedEntry> entries;
String title;
}
}

View File

@@ -1,6 +1,7 @@
package com.commafeed.backend.feed; package com.commafeed.backend.feed;
import java.util.Date; import java.time.Duration;
import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingDeque;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -11,8 +12,6 @@ import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.time.DateUtils;
import com.codahale.metrics.Gauge; import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter; import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
@@ -156,7 +155,7 @@ public class FeedRefreshEngine implements Managed {
private void processFeedAsync(Feed feed) { private void processFeedAsync(Feed feed) {
CompletableFuture.supplyAsync(() -> worker.update(feed), workerExecutor) CompletableFuture.supplyAsync(() -> worker.update(feed), workerExecutor)
.thenApplyAsync(r -> updater.update(r.getFeed(), r.getEntries()), databaseUpdaterExecutor) .thenApplyAsync(r -> updater.update(r.feed(), r.entries()), databaseUpdaterExecutor)
.whenComplete((data, ex) -> { .whenComplete((data, ex) -> {
if (ex != null) { if (ex != null) {
log.error("error while processing feed {}", feed.getUrl(), ex); log.error("error while processing feed {}", feed.getUrl(), ex);
@@ -166,12 +165,12 @@ public class FeedRefreshEngine implements Managed {
private List<Feed> getNextUpdatableFeeds(int max) { private List<Feed> getNextUpdatableFeeds(int max) {
return unitOfWork.call(() -> { return unitOfWork.call(() -> {
Date lastLoginThreshold = Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad()) Instant lastLoginThreshold = Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad())
? DateUtils.addDays(new Date(), -30) ? Instant.now().minus(Duration.ofDays(30))
: null; : null;
List<Feed> feeds = feedDAO.findNextUpdatable(max, lastLoginThreshold); List<Feed> feeds = feedDAO.findNextUpdatable(max, lastLoginThreshold);
// update disabledUntil to prevent feeds from being returned again by feedDAO.findNextUpdatable() // update disabledUntil to prevent feeds from being returned again by feedDAO.findNextUpdatable()
Date nextUpdateDate = DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes()); Instant nextUpdateDate = Instant.now().plus(Duration.ofMinutes(config.getApplicationSettings().getRefreshIntervalMinutes()));
feedDAO.setDisabledUntil(feeds.stream().map(AbstractModel::getId).toList(), nextUpdateDate); feedDAO.setDisabledUntil(feeds.stream().map(AbstractModel::getId).toList(), nextUpdateDate);
return feeds; return feeds;
}); });

View File

@@ -1,11 +1,10 @@
package com.commafeed.backend.feed; package com.commafeed.backend.feed;
import java.util.Date; import java.time.Duration;
import java.time.Instant;
import org.apache.commons.lang3.time.DateUtils; import java.time.temporal.ChronoUnit;
import com.commafeed.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.model.Feed;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
@@ -22,62 +21,59 @@ public class FeedRefreshIntervalCalculator {
this.refreshIntervalMinutes = config.getApplicationSettings().getRefreshIntervalMinutes(); this.refreshIntervalMinutes = config.getApplicationSettings().getRefreshIntervalMinutes();
} }
public Date onFetchSuccess(Feed feed) { public Instant onFetchSuccess(Instant publishedDate, Long averageEntryInterval) {
Date defaultRefreshInterval = getDefaultRefreshInterval(); Instant defaultRefreshInterval = getDefaultRefreshInterval();
return heavyLoad ? computeRefreshIntervalForHeavyLoad(feed, defaultRefreshInterval) : defaultRefreshInterval; return heavyLoad ? computeRefreshIntervalForHeavyLoad(publishedDate, averageEntryInterval, defaultRefreshInterval)
: defaultRefreshInterval;
} }
public Date onFeedNotModified(Feed feed) { public Instant onFeedNotModified(Instant publishedDate, Long averageEntryInterval) {
Date defaultRefreshInterval = getDefaultRefreshInterval(); return onFetchSuccess(publishedDate, averageEntryInterval);
return heavyLoad ? computeRefreshIntervalForHeavyLoad(feed, defaultRefreshInterval) : defaultRefreshInterval;
} }
public Date onFetchError(Feed feed) { public Instant onFetchError(int errorCount) {
int errorCount = feed.getErrorCount();
int retriesBeforeDisable = 3; int retriesBeforeDisable = 3;
if (errorCount < retriesBeforeDisable || !heavyLoad) { if (errorCount < retriesBeforeDisable || !heavyLoad) {
return getDefaultRefreshInterval(); return getDefaultRefreshInterval();
} }
int disabledHours = Math.min(24 * 7, errorCount - retriesBeforeDisable + 1); int disabledHours = Math.min(24 * 7, errorCount - retriesBeforeDisable + 1);
return DateUtils.addHours(new Date(), disabledHours); return Instant.now().plus(Duration.ofHours(disabledHours));
} }
private Date getDefaultRefreshInterval() { private Instant getDefaultRefreshInterval() {
return DateUtils.addMinutes(new Date(), refreshIntervalMinutes); return Instant.now().plus(Duration.ofMinutes(refreshIntervalMinutes));
} }
private Date computeRefreshIntervalForHeavyLoad(Feed feed, Date defaultRefreshInterval) { private Instant computeRefreshIntervalForHeavyLoad(Instant publishedDate, Long averageEntryInterval, Instant defaultRefreshInterval) {
Date now = new Date(); Instant now = Instant.now();
Date publishedDate = feed.getLastEntryDate();
Long averageEntryInterval = feed.getAverageEntryInterval();
if (publishedDate == null) { if (publishedDate == null) {
// feed with no entries, recheck in 24 hours // feed with no entries, recheck in 24 hours
return DateUtils.addHours(now, 24); return now.plus(Duration.ofHours(24));
} else if (publishedDate.before(DateUtils.addMonths(now, -1))) { } else if (ChronoUnit.DAYS.between(publishedDate, now) >= 30) {
// older than a month, recheck in 24 hours // older than a month, recheck in 24 hours
return DateUtils.addHours(now, 24); return now.plus(Duration.ofHours(24));
} else if (publishedDate.before(DateUtils.addDays(now, -14))) { } else if (ChronoUnit.DAYS.between(publishedDate, now) >= 14) {
// older than two weeks, recheck in 12 hours // older than two weeks, recheck in 12 hours
return DateUtils.addHours(now, 12); return now.plus(Duration.ofHours(12));
} else if (publishedDate.before(DateUtils.addDays(now, -7))) { } else if (ChronoUnit.DAYS.between(publishedDate, now) >= 7) {
// older than a week, recheck in 6 hours // older than a week, recheck in 6 hours
return DateUtils.addHours(now, 6); return now.plus(Duration.ofHours(6));
} else if (averageEntryInterval != null) { } else if (averageEntryInterval != null) {
// use average time between entries to decide when to refresh next, divided by factor // use average time between entries to decide when to refresh next, divided by factor
int factor = 2; int factor = 2;
// not more than 6 hours // not more than 6 hours
long date = Math.min(DateUtils.addHours(now, 6).getTime(), now.getTime() + averageEntryInterval / factor); long date = Math.min(now.plus(Duration.ofHours(6)).toEpochMilli(), now.toEpochMilli() + averageEntryInterval / factor);
// not less than default refresh interval // not less than default refresh interval
date = Math.max(defaultRefreshInterval.getTime(), date); date = Math.max(defaultRefreshInterval.toEpochMilli(), date);
return new Date(date); return Instant.ofEpochMilli(date);
} else { } else {
// unknown case, recheck in 24 hours // unknown case, recheck in 24 hours
return DateUtils.addHours(now, 24); return now.plus(Duration.ofHours(24));
} }
} }

View File

@@ -1,10 +1,14 @@
package com.commafeed.backend.feed; package com.commafeed.backend.feed;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
@@ -16,10 +20,12 @@ import com.codahale.metrics.MetricRegistry;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.dao.UnitOfWork; import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.feed.parser.FeedParserResult.Content;
import com.commafeed.backend.feed.parser.FeedParserResult.Entry;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.Models;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.service.FeedEntryService; import com.commafeed.backend.service.FeedEntryService;
import com.commafeed.backend.service.FeedService; import com.commafeed.backend.service.FeedService;
@@ -27,7 +33,6 @@ import com.commafeed.frontend.ws.WebSocketMessageBuilder;
import com.commafeed.frontend.ws.WebSocketSessions; import com.commafeed.frontend.ws.WebSocketSessions;
import com.google.common.util.concurrent.Striped; import com.google.common.util.concurrent.Striped;
import io.dropwizard.lifecycle.Managed;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -38,7 +43,7 @@ import lombok.extern.slf4j.Slf4j;
*/ */
@Slf4j @Slf4j
@Singleton @Singleton
public class FeedRefreshUpdater implements Managed { public class FeedRefreshUpdater {
private final UnitOfWork unitOfWork; private final UnitOfWork unitOfWork;
private final FeedService feedService; private final FeedService feedService;
@@ -72,9 +77,10 @@ public class FeedRefreshUpdater implements Managed {
entryInserted = metrics.meter(MetricRegistry.name(getClass(), "entryInserted")); entryInserted = metrics.meter(MetricRegistry.name(getClass(), "entryInserted"));
} }
private AddEntryResult addEntry(final Feed feed, final FeedEntry entry, final List<FeedSubscription> subscriptions) { private AddEntryResult addEntry(final Feed feed, final Entry entry, final List<FeedSubscription> subscriptions) {
boolean processed = false; boolean processed = false;
boolean inserted = false; boolean inserted = false;
Set<FeedSubscription> subscriptionsForWhichEntryIsUnread = new HashSet<>();
// lock on feed, make sure we are not updating the same feed twice at // lock on feed, make sure we are not updating the same feed twice at
// the same time // the same time
@@ -82,8 +88,8 @@ public class FeedRefreshUpdater implements Managed {
// lock on content, make sure we are not updating the same entry // lock on content, make sure we are not updating the same entry
// twice at the same time // twice at the same time
FeedEntryContent content = entry.getContent(); Content content = entry.content();
String key2 = DigestUtils.sha1Hex(StringUtils.trimToEmpty(content.getContent() + content.getTitle())); String key2 = DigestUtils.sha1Hex(StringUtils.trimToEmpty(content.content() + content.title()));
Iterator<Lock> iterator = locks.bulkGet(Arrays.asList(key1, key2)).iterator(); Iterator<Lock> iterator = locks.bulkGet(Arrays.asList(key1, key2)).iterator();
Lock lock1 = iterator.next(); Lock lock1 = iterator.next();
@@ -96,10 +102,21 @@ public class FeedRefreshUpdater implements Managed {
locked2 = lock2.tryLock(1, TimeUnit.MINUTES); locked2 = lock2.tryLock(1, TimeUnit.MINUTES);
if (locked1 && locked2) { if (locked1 && locked2) {
processed = true; processed = true;
inserted = unitOfWork.call(() -> feedEntryService.addEntry(feed, entry, subscriptions)); inserted = unitOfWork.call(() -> {
if (inserted) { Instant now = Instant.now();
entryInserted.mark(); FeedEntry feedEntry = feedEntryService.findOrCreate(feed, entry);
} boolean newEntry = !feedEntry.getInserted().isBefore(now);
if (newEntry) {
entryInserted.mark();
for (FeedSubscription sub : subscriptions) {
boolean unread = feedEntryService.applyFilter(sub, feedEntry);
if (unread) {
subscriptionsForWhichEntryIsUnread.add(sub);
}
}
}
return newEntry;
});
} else { } else {
log.error("lock timeout for " + feed.getUrl() + " - " + key1); log.error("lock timeout for " + feed.getUrl() + " - " + key1);
} }
@@ -113,32 +130,34 @@ public class FeedRefreshUpdater implements Managed {
lock2.unlock(); lock2.unlock();
} }
} }
return new AddEntryResult(processed, inserted); return new AddEntryResult(processed, inserted, subscriptionsForWhichEntryIsUnread);
} }
public boolean update(Feed feed, List<FeedEntry> entries) { public boolean update(Feed feed, List<Entry> entries) {
boolean processed = true; boolean processed = true;
boolean insertedAtLeastOneEntry = false; long inserted = 0;
Map<FeedSubscription, Long> unreadCountBySubscription = new HashMap<>();
if (!entries.isEmpty()) { if (!entries.isEmpty()) {
List<String> lastEntries = cache.getLastEntries(feed); Set<String> lastEntries = cache.getLastEntries(feed);
List<String> currentEntries = new ArrayList<>(); List<String> currentEntries = new ArrayList<>();
List<FeedSubscription> subscriptions = null; List<FeedSubscription> subscriptions = null;
for (FeedEntry entry : entries) { for (Entry entry : entries) {
String cacheKey = cache.buildUniqueEntryKey(feed, entry); String cacheKey = cache.buildUniqueEntryKey(entry);
if (!lastEntries.contains(cacheKey)) { if (!lastEntries.contains(cacheKey)) {
log.debug("cache miss for {}", entry.getUrl()); log.debug("cache miss for {}", entry.url());
if (subscriptions == null) { if (subscriptions == null) {
subscriptions = unitOfWork.call(() -> feedSubscriptionDAO.findByFeed(feed)); subscriptions = unitOfWork.call(() -> feedSubscriptionDAO.findByFeed(feed));
} }
AddEntryResult addEntryResult = addEntry(feed, entry, subscriptions); AddEntryResult addEntryResult = addEntry(feed, entry, subscriptions);
processed &= addEntryResult.processed; processed &= addEntryResult.processed;
insertedAtLeastOneEntry |= addEntryResult.inserted; inserted += addEntryResult.inserted ? 1 : 0;
addEntryResult.subscriptionsForWhichEntryIsUnread.forEach(sub -> unreadCountBySubscription.merge(sub, 1L, Long::sum));
entryCacheMiss.mark(); entryCacheMiss.mark();
} else { } else {
log.debug("cache hit for {}", entry.getUrl()); log.debug("cache hit for {}", entry.url());
entryCacheHit.mark(); entryCacheHit.mark();
} }
@@ -148,22 +167,21 @@ public class FeedRefreshUpdater implements Managed {
if (subscriptions == null) { if (subscriptions == null) {
feed.setMessage("No new entries found"); feed.setMessage("No new entries found");
} else if (insertedAtLeastOneEntry) { } else if (inserted > 0) {
List<User> users = subscriptions.stream().map(FeedSubscription::getUser).toList(); List<User> users = subscriptions.stream().map(FeedSubscription::getUser).toList();
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0])); cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
cache.invalidateUserRootCategory(users.toArray(new User[0])); cache.invalidateUserRootCategory(users.toArray(new User[0]));
// notify over websocket notifyOverWebsocket(unreadCountBySubscription);
subscriptions.forEach(sub -> webSocketSessions.sendMessage(sub.getUser(), WebSocketMessageBuilder.newFeedEntries(sub)));
} }
} }
if (!processed) { if (!processed) {
// requeue asap // requeue asap
feed.setDisabledUntil(new Date(0)); feed.setDisabledUntil(Models.MINIMUM_INSTANT);
} }
if (insertedAtLeastOneEntry) { if (inserted > 0) {
feedUpdated.mark(); feedUpdated.mark();
} }
@@ -172,10 +190,16 @@ public class FeedRefreshUpdater implements Managed {
return processed; return processed;
} }
private void notifyOverWebsocket(Map<FeedSubscription, Long> unreadCountBySubscription) {
unreadCountBySubscription.forEach((sub, unreadCount) -> webSocketSessions.sendMessage(sub.getUser(),
WebSocketMessageBuilder.newFeedEntries(sub, unreadCount)));
}
@AllArgsConstructor @AllArgsConstructor
private static class AddEntryResult { private static class AddEntryResult {
private final boolean processed; private final boolean processed;
private final boolean inserted; private final boolean inserted;
private final Set<FeedSubscription> subscriptionsForWhichEntryIsUnread;
} }
} }

View File

@@ -1,5 +1,7 @@
package com.commafeed.backend.feed; package com.commafeed.backend.feed;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -11,16 +13,15 @@ import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.HttpGetter.NotModifiedException; import com.commafeed.backend.HttpGetter.NotModifiedException;
import com.commafeed.backend.feed.FeedFetcher.FeedFetcherResult; import com.commafeed.backend.feed.FeedFetcher.FeedFetcherResult;
import com.commafeed.backend.feed.parser.FeedParserResult.Entry;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
* Calls {@link FeedFetcher} and updates the Feed object, but does not update the database ({@link FeedRefreshUpdater} does that) * Calls {@link FeedFetcher} and updates the Feed object, but does not update the database, ({@link FeedRefreshUpdater} does that)
*/ */
@Slf4j @Slf4j
@Singleton @Singleton
@@ -44,32 +45,41 @@ public class FeedRefreshWorker {
public FeedRefreshWorkerResult update(Feed feed) { public FeedRefreshWorkerResult update(Feed feed) {
try { try {
String url = Optional.ofNullable(feed.getUrlAfterRedirect()).orElse(feed.getUrl()); String url = Optional.ofNullable(feed.getUrlAfterRedirect()).orElse(feed.getUrl());
FeedFetcherResult feedFetcherResult = fetcher.fetch(url, false, feed.getLastModifiedHeader(), feed.getEtagHeader(), FeedFetcherResult result = fetcher.fetch(url, false, feed.getLastModifiedHeader(), feed.getEtagHeader(),
feed.getLastPublishedDate(), feed.getLastContentHash()); feed.getLastPublishedDate(), feed.getLastContentHash());
// stops here if NotModifiedException or any other exception is thrown // stops here if NotModifiedException or any other exception is thrown
List<FeedEntry> entries = feedFetcherResult.getEntries();
List<Entry> entries = result.feed().entries();
Integer maxFeedCapacity = config.getApplicationSettings().getMaxFeedCapacity(); Integer maxFeedCapacity = config.getApplicationSettings().getMaxFeedCapacity();
if (maxFeedCapacity > 0) { if (maxFeedCapacity > 0) {
entries = entries.stream().limit(maxFeedCapacity).toList(); entries = entries.stream().limit(maxFeedCapacity).toList();
} }
String urlAfterRedirect = feedFetcherResult.getUrlAfterRedirect(); Integer maxEntriesAgeDays = config.getApplicationSettings().getMaxEntriesAgeDays();
if (maxEntriesAgeDays > 0) {
Instant threshold = Instant.now().minus(Duration.ofDays(maxEntriesAgeDays));
entries = entries.stream().filter(entry -> entry.updated().isAfter(threshold)).toList();
}
String urlAfterRedirect = result.urlAfterRedirect();
if (StringUtils.equals(url, urlAfterRedirect)) { if (StringUtils.equals(url, urlAfterRedirect)) {
urlAfterRedirect = null; urlAfterRedirect = null;
} }
feed.setUrlAfterRedirect(urlAfterRedirect); feed.setUrlAfterRedirect(urlAfterRedirect);
feed.setLink(feedFetcherResult.getFeed().getLink()); feed.setLink(result.feed().link());
feed.setLastModifiedHeader(feedFetcherResult.getFeed().getLastModifiedHeader()); feed.setLastModifiedHeader(result.lastModifiedHeader());
feed.setEtagHeader(feedFetcherResult.getFeed().getEtagHeader()); feed.setEtagHeader(result.lastETagHeader());
feed.setLastContentHash(feedFetcherResult.getFeed().getLastContentHash()); feed.setLastContentHash(result.contentHash());
feed.setLastPublishedDate(feedFetcherResult.getFeed().getLastPublishedDate()); feed.setLastPublishedDate(result.feed().lastPublishedDate());
feed.setAverageEntryInterval(feedFetcherResult.getFeed().getAverageEntryInterval()); feed.setAverageEntryInterval(result.feed().averageEntryInterval());
feed.setLastEntryDate(feedFetcherResult.getFeed().getLastEntryDate()); feed.setLastEntryDate(result.feed().lastEntryDate());
feed.setErrorCount(0); feed.setErrorCount(0);
feed.setMessage(null); feed.setMessage(null);
feed.setDisabledUntil(refreshIntervalCalculator.onFetchSuccess(feedFetcherResult.getFeed())); feed.setDisabledUntil(
refreshIntervalCalculator.onFetchSuccess(result.feed().lastPublishedDate(), result.feed().averageEntryInterval()));
return new FeedRefreshWorkerResult(feed, entries); return new FeedRefreshWorkerResult(feed, entries);
} catch (NotModifiedException e) { } catch (NotModifiedException e) {
@@ -77,7 +87,7 @@ public class FeedRefreshWorker {
feed.setErrorCount(0); feed.setErrorCount(0);
feed.setMessage(e.getMessage()); feed.setMessage(e.getMessage());
feed.setDisabledUntil(refreshIntervalCalculator.onFeedNotModified(feed)); feed.setDisabledUntil(refreshIntervalCalculator.onFeedNotModified(feed.getLastPublishedDate(), feed.getAverageEntryInterval()));
if (e.getNewLastModifiedHeader() != null) { if (e.getNewLastModifiedHeader() != null) {
feed.setLastModifiedHeader(e.getNewLastModifiedHeader()); feed.setLastModifiedHeader(e.getNewLastModifiedHeader());
@@ -93,7 +103,7 @@ public class FeedRefreshWorker {
feed.setErrorCount(feed.getErrorCount() + 1); feed.setErrorCount(feed.getErrorCount() + 1);
feed.setMessage("Unable to refresh feed : " + e.getMessage()); feed.setMessage("Unable to refresh feed : " + e.getMessage());
feed.setDisabledUntil(refreshIntervalCalculator.onFetchError(feed)); feed.setDisabledUntil(refreshIntervalCalculator.onFetchError(feed.getErrorCount()));
return new FeedRefreshWorkerResult(feed, Collections.emptyList()); return new FeedRefreshWorkerResult(feed, Collections.emptyList());
} finally { } finally {
@@ -101,10 +111,7 @@ public class FeedRefreshWorker {
} }
} }
@Value public record FeedRefreshWorkerResult(Feed feed, List<Entry> entries) {
public static class FeedRefreshWorkerResult {
Feed feed;
List<FeedEntry> entries;
} }
} }

View File

@@ -2,20 +2,12 @@ package com.commafeed.backend.feed;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.ahocorasick.trie.Emit;
import org.ahocorasick.trie.Trie;
import org.ahocorasick.trie.Trie.TrieBuilder;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
@@ -29,8 +21,6 @@ import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.frontend.model.Entry; import com.commafeed.frontend.model.Entry;
import com.google.gwt.i18n.client.HasDirection.Direction; import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.i18n.shared.BidiUtils; import com.google.gwt.i18n.shared.BidiUtils;
import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -50,70 +40,6 @@ public class FeedUtils {
return string; return string;
} }
/**
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
* feed
*
*/
public static Charset guessEncoding(byte[] bytes) {
String extracted = extractDeclaredEncoding(bytes);
if (StringUtils.startsWithIgnoreCase(extracted, "iso-8859-")) {
if (!StringUtils.endsWith(extracted, "1")) {
return Charset.forName(extracted);
}
} else if (StringUtils.startsWithIgnoreCase(extracted, "windows-")) {
return Charset.forName(extracted);
}
return detectEncoding(bytes);
}
/**
* Detect encoding by analyzing characters in the array
*/
public static Charset detectEncoding(byte[] bytes) {
String encoding = "UTF-8";
CharsetDetector detector = new CharsetDetector();
detector.setText(bytes);
CharsetMatch match = detector.detect();
if (match != null) {
encoding = match.getName();
}
if (encoding.equalsIgnoreCase("ISO-8859-1")) {
encoding = "windows-1252";
}
return Charset.forName(encoding);
}
public static String replaceHtmlEntitiesWithNumericEntities(String source) {
// Create a buffer sufficiently large that re-allocations are minimized.
StringBuilder sb = new StringBuilder(source.length() << 1);
TrieBuilder builder = Trie.builder();
builder.ignoreOverlaps();
for (String key : HtmlEntities.HTML_ENTITIES) {
builder.addKeyword(key);
}
Trie trie = builder.build();
Collection<Emit> emits = trie.parseText(source);
int prevIndex = 0;
for (Emit emit : emits) {
int matchIndex = emit.getStart();
sb.append(source, prevIndex, matchIndex);
sb.append(HtmlEntities.HTML_TO_NUMERIC_MAP.get(emit.getKeyword()));
prevIndex = emit.getEnd() + 1;
}
// Add the remainder of the string (contains no more matches).
sb.append(source.substring(prevIndex));
return sb.toString();
}
public static boolean isHttp(String url) { public static boolean isHttp(String url) {
return url.startsWith("http://"); return url.startsWith("http://");
} }
@@ -122,6 +48,10 @@ public class FeedUtils {
return url.startsWith("https://"); return url.startsWith("https://");
} }
public static boolean isAbsoluteUrl(String url) {
return isHttp(url) || isHttps(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 * 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
*/ */
@@ -163,25 +93,6 @@ public class FeedUtils {
return normalized; return normalized;
} }
/**
* Extract the declared encoding from the xml
*/
public static String extractDeclaredEncoding(byte[] bytes) {
int index = ArrayUtils.indexOf(bytes, (byte) '>');
if (index == -1) {
return null;
}
String pi = new String(ArrayUtils.subarray(bytes, 0, index + 1)).replace('\'', '"');
index = StringUtils.indexOf(pi, "encoding=\"");
if (index == -1) {
return null;
}
String encoding = pi.substring(index + 10);
encoding = encoding.substring(0, encoding.indexOf('"'));
return encoding;
}
public static boolean isRTL(FeedEntry entry) { public static boolean isRTL(FeedEntry entry) {
String text = entry.getContent().getContent(); String text = entry.getContent().getContent();
@@ -202,52 +113,6 @@ public class FeedUtils {
return direction == Direction.RTL; return direction == Direction.RTL;
} }
public static String trimInvalidXmlCharacters(String xml) {
if (StringUtils.isBlank(xml)) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean firstTagFound = false;
for (int i = 0; i < xml.length(); i++) {
char c = xml.charAt(i);
if (!firstTagFound) {
if (c == '<') {
firstTagFound = true;
} else {
continue;
}
}
if (c >= 32 || c == 9 || c == 10 || c == 13) {
if (!Character.isHighSurrogate(c) && !Character.isLowSurrogate(c)) {
sb.append(c);
}
}
}
return sb.toString();
}
public static Long averageTimeBetweenEntries(List<FeedEntry> entries) {
if (entries.isEmpty() || entries.size() == 1) {
return null;
}
List<Long> timestamps = getSortedTimestamps(entries);
SummaryStatistics stats = new SummaryStatistics();
for (int i = 0; i < timestamps.size() - 1; i++) {
long diff = Math.abs(timestamps.get(i) - timestamps.get(i + 1));
stats.addValue(diff);
}
return (long) stats.getMean();
}
public static List<Long> getSortedTimestamps(List<FeedEntry> entries) {
return entries.stream().map(t -> t.getUpdated().getTime()).sorted(Collections.reverseOrder()).toList();
}
public static String removeTrailingSlash(String url) { public static String removeTrailingSlash(String url) {
if (url.endsWith("/")) { if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1); url = url.substring(0, url.length() - 1);
@@ -257,7 +122,7 @@ public class FeedUtils {
/** /**
* *
* @param url * @param relativeUrl
* the url of the entry * the url of the entry
* @param feedLink * @param feedLink
* the url of the feed as described in the feed * the url of the feed as described in the feed
@@ -265,32 +130,18 @@ public class FeedUtils {
* the url of the feed that we used to fetch the feed * the url of the feed that we used to fetch the feed
* @return an absolute url pointing to the entry * @return an absolute url pointing to the entry
*/ */
public static String toAbsoluteUrl(String url, String feedLink, String feedUrl) { public static String toAbsoluteUrl(String relativeUrl, String feedLink, String feedUrl) {
url = StringUtils.trimToNull(StringUtils.normalizeSpace(url)); String baseUrl = (feedLink != null && isAbsoluteUrl(feedLink)) ? feedLink : feedUrl;
if (url == null || url.startsWith("http")) {
return url;
}
String baseUrl = (feedLink == null || isRelative(feedLink)) ? feedUrl : feedLink;
if (baseUrl == null) { if (baseUrl == null) {
return url; return null;
} }
String result;
try { try {
result = new URL(new URL(baseUrl), url).toString(); return new URL(new URL(baseUrl), relativeUrl).toString();
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
log.debug("could not parse url : " + e.getMessage(), e); log.debug("could not parse url : " + e.getMessage(), e);
result = url; return null;
} }
return result;
}
public static boolean isRelative(final String url) {
// the regex means "start with 'scheme://'"
return url.startsWith("/") || url.startsWith("#") || !url.matches("^\\w+\\:\\/\\/.*");
} }
public static String getFaviconUrl(FeedSubscription subscription) { public static String getFaviconUrl(FeedSubscription subscription) {

View File

@@ -0,0 +1,70 @@
package com.commafeed.backend.feed.parser;
import java.nio.charset.Charset;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch;
import jakarta.inject.Singleton;
@Singleton
class EncodingDetector {
/**
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
* feed
*
*/
public Charset getEncoding(byte[] bytes) {
String extracted = extractDeclaredEncoding(bytes);
if (StringUtils.startsWithIgnoreCase(extracted, "iso-8859-")) {
if (!StringUtils.endsWith(extracted, "1")) {
return Charset.forName(extracted);
}
} else if (StringUtils.startsWithIgnoreCase(extracted, "windows-")) {
return Charset.forName(extracted);
}
return detectEncoding(bytes);
}
/**
* Extract the declared encoding from the xml
*/
public String extractDeclaredEncoding(byte[] bytes) {
int index = ArrayUtils.indexOf(bytes, (byte) '>');
if (index == -1) {
return null;
}
String pi = new String(ArrayUtils.subarray(bytes, 0, index + 1)).replace('\'', '"');
index = StringUtils.indexOf(pi, "encoding=\"");
if (index == -1) {
return null;
}
String encoding = pi.substring(index + 10);
encoding = encoding.substring(0, encoding.indexOf('"'));
return encoding;
}
/**
* Detect encoding by analyzing characters in the array
*/
private Charset detectEncoding(byte[] bytes) {
String encoding = "UTF-8";
CharsetDetector detector = new CharsetDetector();
detector.setText(bytes);
CharsetMatch match = detector.detect();
if (match != null) {
encoding = match.getName();
}
if (encoding.equalsIgnoreCase("ISO-8859-1")) {
encoding = "windows-1252";
}
return Charset.forName(encoding);
}
}

View File

@@ -0,0 +1,63 @@
package com.commafeed.backend.feed.parser;
import java.util.Collection;
import org.ahocorasick.trie.Emit;
import org.ahocorasick.trie.Trie;
import org.apache.commons.lang3.StringUtils;
import jakarta.inject.Singleton;
@Singleton
class FeedCleaner {
public String trimInvalidXmlCharacters(String xml) {
if (StringUtils.isBlank(xml)) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean firstTagFound = false;
for (int i = 0; i < xml.length(); i++) {
char c = xml.charAt(i);
if (!firstTagFound) {
if (c == '<') {
firstTagFound = true;
} else {
continue;
}
}
if (c >= 32 || c == 9 || c == 10 || c == 13) {
if (!Character.isHighSurrogate(c) && !Character.isLowSurrogate(c)) {
sb.append(c);
}
}
}
return sb.toString();
}
// https://stackoverflow.com/a/40836618/1885506
public String replaceHtmlEntitiesWithNumericEntities(String source) {
// Create a buffer sufficiently large that re-allocations are minimized.
StringBuilder sb = new StringBuilder(source.length() << 1);
Collection<Emit> emits = Trie.builder().ignoreOverlaps().addKeywords(HtmlEntities.HTML_ENTITIES).build().parseText(source);
int prevIndex = 0;
for (Emit emit : emits) {
int matchIndex = emit.getStart();
sb.append(source, prevIndex, matchIndex);
sb.append(HtmlEntities.HTML_TO_NUMERIC_MAP.get(emit.getKeyword()));
prevIndex = emit.getEnd() + 1;
}
// Add the remainder of the string (contains no more matches).
sb.append(source.substring(prevIndex));
return sb.toString();
}
}

Some files were not shown because too many files have changed in this diff Show More