Compare commits

..

495 Commits
4.2.1 ... 4.5.0

Author SHA1 Message Date
Athou
4d83173dbd release 4.5.0 2024-07-06 21:29:21 +02:00
Athou
f13368cb96 remove unnecessary joins 2024-07-06 13:40:53 +02:00
Athou
ec7e97e1de abort current request if we're changing what we're going to display 2024-07-04 07:19:16 +02:00
Athou
d4c9bd1dd7 remove warnings 2024-07-03 20:11:51 +02:00
renovate[bot]
6bff657d4d Update linguijs monorepo to ^4.11.2 2024-07-03 17:13:52 +00:00
renovate[bot]
613d286be1 Update dependency react-router-dom to ^6.24.1 2024-07-03 16:05:19 +00:00
Athou
fd48108f8b don't rely on dates to know if an entry has been inserted in the database 2024-07-03 17:53:41 +02:00
Athou
c3cbd18df9 add debug logging 2024-07-03 17:40:23 +02:00
Athou
6685057dae notify over websocket after everything has been committed 2024-07-03 17:27:17 +02:00
Athou
0dec0e3788 fix a race condition where a feed could be refreshed before it was created 2024-07-03 14:21:40 +02:00
Athou
1a73dd4004 the feed refresh engine is now fast enough, it doesn't need workarounds anymore 2024-07-03 13:30:25 +02:00
Athou
eae80a6450 remove support for microsoft sqlserver, AFAIK nobody's using it and it's not covered with integration tests 2024-07-03 13:02:20 +02:00
Jérémie Panzer
21a32ce0eb Merge pull request #1479 from Athou/renovate/mysql-9.x
Update mysql Docker tag to v9
2024-07-03 11:35:35 +02:00
renovate[bot]
325533c5d9 Update mysql Docker tag to v9 2024-07-03 09:10:22 +00:00
renovate[bot]
7d819022f6 Update redis Docker tag to v7.2.5 2024-07-03 09:10:19 +00:00
Athou
dba944874b add redis integration test 2024-07-03 11:09:23 +02:00
Jérémie Panzer
ce9c12ec92 Merge pull request #1478 from Athou/renovate/postgres-16.x
Update postgres Docker tag to v16.3
2024-07-03 09:23:35 +02:00
Jérémie Panzer
22dfc5774f Merge pull request #1477 from Athou/renovate/mariadb-11.x
Update mariadb Docker tag to v11.4.2
2024-07-03 09:23:24 +02:00
renovate[bot]
d59091ab2b Update postgres Docker tag to v16.3 2024-07-03 07:08:14 +00:00
renovate[bot]
f69146a6bf Update mariadb Docker tag to v11.4.2 2024-07-03 07:08:11 +00:00
Athou
43cdf3db3b add integration tests for postgresql, mysql and mariadb using testcontainers 2024-07-03 09:02:18 +02:00
renovate[bot]
280a354228 Update dependency vite-plugin-biome to ^1.0.12 2024-07-03 06:39:02 +00:00
renovate[bot]
573b0431f9 Update dependency vite to ^5.3.3 2024-07-03 06:32:04 +00:00
renovate[bot]
9878b60e97 Update dependency io.github.git-commit-id:git-commit-id-maven-plugin to v9.0.1 2024-07-02 18:02:54 +00:00
renovate[bot]
964033c2a7 Update mantine monorepo to ^7.11.1 2024-07-02 14:12:43 +00:00
Jérémie Panzer
d2e45aca91 Merge pull request #1475 from Athou/renovate/com.mysql-mysql-connector-j-9.x
Update dependency com.mysql:mysql-connector-j to v9
2024-07-02 16:11:43 +02:00
renovate[bot]
daa99a2efc Update dependency com.mysql:mysql-connector-j to v9 2024-07-02 10:08:39 +00:00
Jérémie Panzer
e986e9999a Merge pull request #1473 from Athou/renovate/node-20.x
Update dependency node to v20.15.0
2024-07-02 09:19:55 +02:00
Jérémie Panzer
98d302cb94 Merge pull request #1474 from Athou/renovate/npm-10.x
Update dependency npm to v10.8.1
2024-07-02 09:19:44 +02:00
renovate[bot]
bf11c4a7e4 Update dependency npm to v10.8.1 2024-07-02 07:13:46 +00:00
renovate[bot]
e1cab952f8 Update dependency node to v20.15.0 2024-07-02 07:13:42 +00:00
Athou
bc28d4de27 renovate can now update node and npm 2024-07-02 09:12:42 +02:00
renovate[bot]
bb901564e3 Update dependency typescript to ^5.5.3 2024-07-01 22:21:16 +00:00
Athou
93acc9ded1 we don't need the user we already have the subscription 2024-07-01 13:55:50 +02:00
renovate[bot]
9b1c6a371e Lock file maintenance 2024-07-01 07:50:40 +00:00
Athou
82bf8cd807 fetch all tags at once 2024-07-01 08:52:05 +02:00
Athou
c2f2780c3f remove unused onlyIds parameter 2024-07-01 08:52:05 +02:00
Athou
08f71d1f6f implement faster querying by fetching directly what we need 2024-07-01 08:52:05 +02:00
Athou
f498088beb no need to insert statuses that will be collected during next cleanup 2024-06-30 21:56:42 +02:00
Athou
347b41cf35 fix exception when trying to mark starred entries as read 2024-06-30 16:50:37 +02:00
renovate[bot]
61ae90ad28 Update dependency @reduxjs/toolkit to ^2.2.6 2024-06-29 14:12:13 +00:00
Athou
9a42fbafb2 only automerge patch updates, keep creating pull requests for minor updates 2024-06-29 08:47:49 +02:00
Athou
938f9e9434 remove automergePr because we now specify automergeBranch 2024-06-28 07:27:58 +02:00
Jérémie Panzer
9004e453c2 Merge pull request #1470 from Athou/renovate/querydsl.version
Update querydsl.version to v6.5
2024-06-28 07:26:21 +02:00
renovate[bot]
7d33542691 Update querydsl.version to v6.5 2024-06-28 05:25:56 +00:00
Athou
c99348862c reduce renovate noise by automerging if tests pass 2024-06-28 07:25:10 +02:00
Jérémie Panzer
ac86db3966 Merge pull request #1463 from Athou/renovate/com.manticore-projects.tools-h2migrationtool-1.x
Update dependency com.manticore-projects.tools:h2migrationtool to v1.6
2024-06-28 07:21:29 +02:00
Athou
e368810731 adapt script to new H2MigrationTool file output name 2024-06-28 07:16:42 +02:00
renovate[bot]
edae2f5a61 Update dependency com.manticore-projects.tools:h2migrationtool to v1.6 2024-06-28 03:34:34 +00:00
renovate[bot]
ab17c6f44e Update dependency org.projectlombok:lombok to v1.18.34 2024-06-28 03:34:21 +00:00
renovate[bot]
59dbae4f66 Update dependency com.microsoft.playwright:playwright to v1.45.0 2024-06-28 01:23:51 +00:00
renovate[bot]
d7956292df Update dependency vite-plugin-biome to ^1.0.11 2024-06-27 21:19:36 +00:00
renovate[bot]
1075497559 Update dependency vite to ^5.3.2 2024-06-27 19:44:59 +00:00
renovate[bot]
2d99fa03d3 Update dependency @biomejs/biome to v1.8.3 2024-06-27 16:49:21 +00:00
Jérémie Panzer
72b64b6f0d Merge pull request #1465 from Athou/renovate/mantine-monorepo
Update mantine monorepo to ^7.11.0
2024-06-27 09:35:00 +02:00
Jérémie Panzer
a2096d3622 Merge pull request #1457 from Athou/renovate/monaco-editor-0.x
Update dependency monaco-editor to ^0.50.0
2024-06-27 09:34:52 +02:00
Jérémie Panzer
c81f9fb7b1 Merge pull request #1459 from Athou/renovate/throttle-debounce-5.x
Update dependency throttle-debounce to ^5.0.2
2024-06-27 09:34:44 +02:00
Jérémie Panzer
cc7e9e21fb Merge pull request #1461 from Athou/renovate/react-router-monorepo
Update dependency react-router-dom to ^6.24.0
2024-06-27 09:34:35 +02:00
Jérémie Panzer
803d537e51 Merge pull request #1456 from Athou/renovate/biomejs-biome-1.x
Update dependency @biomejs/biome to v1.8.2
2024-06-27 09:34:20 +02:00
Jérémie Panzer
9a83e5b6ef Merge pull request #1458 from Athou/renovate/typescript-5.x
Update dependency typescript to ^5.5.2
2024-06-27 09:34:08 +02:00
renovate[bot]
4323da9007 Update mantine monorepo to ^7.11.0 2024-06-27 07:28:56 +00:00
renovate[bot]
30b9b24be4 Update dependency typescript to ^5.5.2 2024-06-27 07:28:42 +00:00
renovate[bot]
b191b00003 Update dependency react-router-dom to ^6.24.0 2024-06-27 07:28:30 +00:00
renovate[bot]
7e5cdcba34 Update dependency monaco-editor to ^0.50.0 2024-06-27 07:28:16 +00:00
renovate[bot]
45b30ad333 Update dependency throttle-debounce to ^5.0.2 2024-06-27 07:27:58 +00:00
renovate[bot]
7ca087b0a6 Update dependency @biomejs/biome to v1.8.2 2024-06-27 07:27:47 +00:00
Jérémie Panzer
188e4594fd automerge small dependency updates if they pass tests 2024-06-27 09:26:48 +02:00
Jérémie Panzer
2da80ce7d8 Merge pull request #1453 from Athou/renovate/org.apache.maven.plugins-maven-jar-plugin-3.x
Update dependency org.apache.maven.plugins:maven-jar-plugin to v3.4.2
2024-06-20 14:09:39 +02:00
renovate[bot]
d5820f9aa5 Update dependency org.apache.maven.plugins:maven-jar-plugin to v3.4.2 2024-06-19 18:42:15 +00:00
Athou
b1a0aae0a5 treat javac warnings as errors 2024-06-19 19:41:09 +02:00
Athou
cdd4d4b063 change BaseIT test class so that authentication with the "admin" user is not the default 2024-06-19 19:41:02 +02:00
Jérémie Panzer
21f675e80b Merge pull request #1451 from Athou/renovate/querydsl.version
Update querydsl.version to v6.4
2024-06-19 18:53:35 +02:00
renovate[bot]
380724d73e Update querydsl.version to v6.4 2024-06-19 16:49:05 +00:00
Athou
2d26c5dee3 remove empty file 2024-06-18 20:40:36 +02:00
Jérémie Panzer
29bcc5ccf5 Merge pull request #1437 from Athou/renovate/maven-3.x
Update dependency maven to v3.9.8
2024-06-17 22:31:07 +02:00
Jérémie Panzer
91497ab45a Merge pull request #1450 from Athou/renovate/docker-build-push-action-6.x
Update docker/build-push-action action to v6
2024-06-17 22:30:49 +02:00
renovate[bot]
be77968570 Update docker/build-push-action action to v6 2024-06-17 10:38:49 +00:00
renovate[bot]
a42dacc48d Update dependency maven to v3.9.8 2024-06-17 10:38:45 +00:00
Athou
cd06055246 release 4.4.1 2024-06-15 14:52:26 +02:00
Jérémie Panzer
62c1f25ffc Merge pull request #1448 from Athou/renovate/vite-5.x
Update dependency vite to ^5.3.1
2024-06-15 14:42:42 +02:00
Jérémie Panzer
415dc15d6c Merge pull request #1449 from Athou/renovate/mantine-monorepo
Update mantine monorepo to ^7.10.2
2024-06-15 14:41:52 +02:00
renovate[bot]
1d0c87c679 Update mantine monorepo to ^7.10.2 2024-06-15 12:32:58 +00:00
renovate[bot]
e51c486a04 Update dependency vite to ^5.3.1 2024-06-15 12:32:47 +00:00
Athou
73808c1a70 make renovate also bump versions in package.json 2024-06-15 14:32:05 +02:00
Athou
fbcc2ecd0f add a little delay to simulate a network operation 2024-06-15 14:00:15 +02:00
Athou
3997606774 looks like sometimes the websocket connection is not established on github actions, refresh the tree with an interval smaller than the timeout of playwright 2024-06-15 08:46:55 +02:00
Jérémie Panzer
b988b599d5 Merge pull request #1446 from Athou/renovate/org.apache.maven.plugins-maven-failsafe-plugin-3.x
Update dependency org.apache.maven.plugins:maven-failsafe-plugin to v3.3.0
2024-06-14 22:31:42 +02:00
Jérémie Panzer
3e2ff2959d Merge pull request #1447 from Athou/renovate/org.apache.maven.plugins-maven-surefire-plugin-3.x
Update dependency org.apache.maven.plugins:maven-surefire-plugin to v3.3.0
2024-06-14 22:31:31 +02:00
renovate[bot]
5714a63d27 Update dependency org.apache.maven.plugins:maven-surefire-plugin to v3.3.0 2024-06-14 19:55:02 +00:00
renovate[bot]
12b18d1e04 Update dependency org.apache.maven.plugins:maven-failsafe-plugin to v3.3.0 2024-06-14 19:54:59 +00:00
Athou
232141cb56 fix UnsupportedTemporalTypeException when tests fail 2024-06-14 21:50:50 +02:00
Jérémie Panzer
c4334e5e6e Merge pull request #1445 from Athou/renovate/vite-5.x-lockfile
Update dependency vite to v5.3.1
2024-06-14 13:00:43 +02:00
renovate[bot]
ddf78f880b Update dependency vite to v5.3.1 2024-06-14 09:59:39 +00:00
Athou
b3651f3fba upload playwright artifacts on test failure to help debug what went wrong 2024-06-14 07:59:26 +02:00
Jérémie Panzer
24943b868c Merge pull request #1442 from Athou/renovate/mantine-monorepo
Update mantine monorepo to v7.10.2
2024-06-13 23:51:58 +02:00
renovate[bot]
ef71a691ef Update mantine monorepo to v7.10.2 2024-06-13 21:47:30 +00:00
Jérémie Panzer
01593d94eb Merge pull request #1443 from Athou/renovate/vite-5.x-lockfile
Update dependency vite to v5.3.0
2024-06-13 23:45:28 +02:00
renovate[bot]
b793cc66d1 Update dependency vite to v5.3.0 2024-06-13 21:31:42 +00:00
Athou
3810dedf47 replace complex eslint config with biome 2024-06-13 23:28:45 +02:00
Athou
9115797dee try to fix renovatebot warning about not being able to update commafeed-client 2024-06-13 06:53:23 +02:00
Athou
232658b934 remove commons-io since we already have guava 2024-06-12 17:17:54 +02:00
Athou
f99fe57695 remove 32bit arm7 because its support was dropped from temurin 21 2024-06-12 16:40:08 +02:00
Athou
ec89d41112 update mvn wrapper 2024-06-12 16:36:57 +02:00
Jérémie Panzer
f6d26a77cc Merge pull request #1439 from Athou/renovate/eclipse-temurin-21.x
Update eclipse-temurin Docker tag to v21
2024-06-12 16:33:07 +02:00
renovate[bot]
860852cc12 Update eclipse-temurin Docker tag to v21 2024-06-12 14:28:49 +00:00
Athou
d06d76401c download maven-wrapper binaries if needed 2024-06-12 16:24:26 +02:00
Athou
f5b04a783e remove commons-codec since we already have guava 2024-06-12 16:18:52 +02:00
Athou
964e470951 manually bump dependencies left behind by dependabot 2024-06-12 15:45:58 +02:00
Jérémie Panzer
612f8722dd Merge pull request #1433 from Athou/renovate/prettier-3.x-lockfile
Update dependency prettier to v3.3.2
2024-06-12 15:33:22 +02:00
Jérémie Panzer
e118dc9b7f Merge pull request #1434 from Athou/renovate/eclipse-temurin-17.x
Update eclipse-temurin Docker tag to v17.0.11_9-jre
2024-06-12 15:32:49 +02:00
renovate[bot]
6e42cdaf2d Update eclipse-temurin Docker tag to v17.0.11_9-jre 2024-06-12 13:28:00 +00:00
renovate[bot]
5198792ca5 Update dependency prettier to v3.3.2 2024-06-12 13:27:56 +00:00
Athou
10a71213f3 use renovate instead of dependabot 2024-06-12 15:27:48 +02:00
Jérémie Panzer
a5d0979d9f Merge pull request #1432 from Athou/renovate/configure
Configure Renovate
2024-06-12 15:27:13 +02:00
renovate[bot]
d84225ab1c Add renovate.json 2024-06-12 13:23:16 +00:00
Athou
cd86947e64 keep pull to refresh for safari (#1168) 2024-06-12 13:12:33 +02:00
Athou
f6b3114a91 use react helmet to manipulate scripts and styles programatically 2024-06-12 13:03:19 +02:00
Athou
cd50b6b058 use new playwright locators 2024-06-12 11:39:51 +02:00
Athou
b0c7ef18db fix test race condition 2024-06-12 10:30:47 +02:00
Athou
24171faf86 fetchFeedInternal follows redirects, we don't need to call it twice (#1431) 2024-06-12 08:21:11 +02:00
Jérémie Panzer
941f14dd41 Merge pull request #1374 from Athou/dependabot/npm_and_yarn/commafeed-client/eslint-plugin-react-hooks-4.6.2
Bump eslint-plugin-react-hooks from 4.6.0 to 4.6.2 in /commafeed-client
2024-06-11 08:37:23 +02:00
Jérémie Panzer
d46ef787db Merge pull request #1404 from Athou/dependabot/npm_and_yarn/commafeed-client/vitest-1.6.0
Bump vitest from 1.5.0 to 1.6.0 in /commafeed-client
2024-06-11 08:37:10 +02:00
Jérémie Panzer
ec7447a38c Merge pull request #1411 from Athou/dependabot/npm_and_yarn/commafeed-client/react-router-dom-6.23.1
Bump react-router-dom from 6.22.3 to 6.23.1 in /commafeed-client
2024-06-11 08:33:22 +02:00
dependabot[bot]
2a3fc3ae15 Bump eslint-plugin-react-hooks from 4.6.0 to 4.6.2 in /commafeed-client
Bumps [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) from 4.6.0 to 4.6.2.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/HEAD/packages/eslint-plugin-react-hooks)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 06:31:59 +00:00
dependabot[bot]
ef25582bcb Bump react-router-dom from 6.22.3 to 6.23.1 in /commafeed-client
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.3 to 6.23.1.
- [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.23.1/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-06-11 06:29:29 +00:00
Jérémie Panzer
55bbb2542d Merge pull request #1402 from Athou/dependabot/npm_and_yarn/commafeed-client/axios-1.7.2
Bump axios from 1.6.8 to 1.7.2 in /commafeed-client
2024-06-11 08:28:51 +02:00
Jérémie Panzer
8e94ac74a8 Merge pull request #1401 from Athou/dependabot/npm_and_yarn/commafeed-client/react-redux-9.1.2
Bump react-redux from 9.1.0 to 9.1.2 in /commafeed-client
2024-06-11 08:28:42 +02:00
Jérémie Panzer
90ecb9253c Merge pull request #1416 from Athou/dependabot/npm_and_yarn/commafeed-client/eslint-plugin-react-7.34.2
Bump eslint-plugin-react from 7.34.1 to 7.34.2 in /commafeed-client
2024-06-11 08:28:26 +02:00
Jérémie Panzer
6721842d98 Merge pull request #1414 from Athou/dependabot/npm_and_yarn/commafeed-client/lingui-240f3c17ef
Bump the lingui group across 1 directory with 5 updates
2024-06-11 08:28:15 +02:00
Jérémie Panzer
8b487ec414 Merge pull request #1428 from Athou/dependabot/npm_and_yarn/commafeed-client/typescript-eslint/eslint-plugin-7.13.0
Bump @typescript-eslint/eslint-plugin from 7.6.0 to 7.13.0 in /commafeed-client
2024-06-11 08:28:03 +02:00
dependabot[bot]
d6382861c3 Bump the lingui group across 1 directory with 5 updates
Bumps the lingui group with 5 updates in the /commafeed-client directory:

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



Updates `@lingui/core` from 4.10.0 to 4.11.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.10.0...v4.11.1)

Updates `@lingui/macro` from 4.10.0 to 4.11.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.10.0...v4.11.1)

Updates `@lingui/react` from 4.10.0 to 4.11.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.10.0...v4.11.1)

Updates `@lingui/cli` from 4.10.0 to 4.11.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.10.0...v4.11.1)

Updates `@lingui/vite-plugin` from 4.10.0 to 4.11.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.10.0...v4.11.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:27:42 +00:00
dependabot[bot]
2cdea99a69 Bump axios from 1.6.8 to 1.7.2 in /commafeed-client
Bumps [axios](https://github.com/axios/axios) from 1.6.8 to 1.7.2.
- [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.8...v1.7.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:26:35 +00:00
dependabot[bot]
b1ae1c8afd Bump react-redux from 9.1.0 to 9.1.2 in /commafeed-client
Bumps [react-redux](https://github.com/reduxjs/react-redux) from 9.1.0 to 9.1.2.
- [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.1.0...v9.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:26:07 +00:00
Jérémie Panzer
c09cd0c717 Merge pull request #1379 from Athou/dependabot/npm_and_yarn/commafeed-client/dayjs-1.11.11
Bump dayjs from 1.11.10 to 1.11.11 in /commafeed-client
2024-06-11 07:26:00 +02:00
Jérémie Panzer
f50e0ae272 Merge pull request #1375 from Athou/dependabot/npm_and_yarn/commafeed-client/multi-ec30208de6
Bump react-dom and @types/react-dom in /commafeed-client
2024-06-11 07:25:51 +02:00
Jérémie Panzer
b99b91a2a8 Merge pull request #1384 from Athou/dependabot/npm_and_yarn/commafeed-client/tss-react-4.9.10
Bump tss-react from 4.9.6 to 4.9.10 in /commafeed-client
2024-06-11 07:25:28 +02:00
Jérémie Panzer
d9759de6f1 Merge pull request #1403 from Athou/dependabot/npm_and_yarn/commafeed-client/monaco-editor-0.49.0
Bump monaco-editor from 0.47.0 to 0.49.0 in /commafeed-client
2024-06-11 07:25:19 +02:00
dependabot[bot]
cf2b7f9e4f Bump eslint-plugin-react from 7.34.1 to 7.34.2 in /commafeed-client
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.34.1 to 7.34.2.
- [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.34.1...v7.34.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:25:14 +00:00
Jérémie Panzer
ee880c06ed Merge pull request #1407 from Athou/dependabot/npm_and_yarn/commafeed-client/react-icons-5.2.1
Bump react-icons from 5.0.1 to 5.2.1 in /commafeed-client
2024-06-11 07:25:10 +02:00
Jérémie Panzer
bc2e13ef22 Merge pull request #1410 from Athou/dependabot/npm_and_yarn/commafeed-client/reduxjs/toolkit-2.2.5
Bump @reduxjs/toolkit from 2.2.3 to 2.2.5 in /commafeed-client
2024-06-11 07:25:01 +02:00
dependabot[bot]
39ecfe2782 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.6.0 to 7.13.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.13.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-06-11 05:24:54 +00:00
Jérémie Panzer
3295d82f69 Merge pull request #1371 from Athou/dependabot/npm_and_yarn/commafeed-client/fontsource/open-sans-5.0.28
Bump @fontsource/open-sans from 5.0.27 to 5.0.28 in /commafeed-client
2024-06-11 07:24:42 +02:00
dependabot[bot]
1cd27a59e2 Bump vitest from 1.5.0 to 1.6.0 in /commafeed-client
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.6.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-06-11 05:24:35 +00:00
Jérémie Panzer
e1602edff1 Merge pull request #1426 from Athou/dependabot/npm_and_yarn/commafeed-client/redoc-2.1.5
Bump redoc from 2.1.3 to 2.1.5 in /commafeed-client
2024-06-11 07:24:03 +02:00
Jérémie Panzer
ef8e61d6fc Merge pull request #1427 from Athou/dependabot/npm_and_yarn/commafeed-client/prettier-3.3.1
Bump prettier from 3.2.5 to 3.3.1 in /commafeed-client
2024-06-11 07:23:54 +02:00
Jérémie Panzer
0057030442 Merge pull request #1429 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.2.13
Bump vite from 5.2.8 to 5.2.13 in /commafeed-client
2024-06-11 07:23:33 +02:00
Jérémie Panzer
6fabe46d6e Merge pull request #1430 from Athou/dependabot/npm_and_yarn/commafeed-client/vitejs/plugin-react-4.3.1
Bump @vitejs/plugin-react from 4.2.1 to 4.3.1 in /commafeed-client
2024-06-11 07:23:21 +02:00
dependabot[bot]
37c58f2755 Bump monaco-editor from 0.47.0 to 0.49.0 in /commafeed-client
Bumps [monaco-editor](https://github.com/microsoft/monaco-editor) from 0.47.0 to 0.49.0.
- [Release notes](https://github.com/microsoft/monaco-editor/releases)
- [Changelog](https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/microsoft/monaco-editor/compare/v0.47.0...v0.49.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-06-11 05:18:41 +00:00
dependabot[bot]
bb982c3caf Bump @reduxjs/toolkit from 2.2.3 to 2.2.5 in /commafeed-client
Bumps [@reduxjs/toolkit](https://github.com/reduxjs/redux-toolkit) from 2.2.3 to 2.2.5.
- [Release notes](https://github.com/reduxjs/redux-toolkit/releases)
- [Commits](https://github.com/reduxjs/redux-toolkit/compare/v2.2.3...v2.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:18:39 +00:00
Jérémie Panzer
7e4c3737a8 Merge pull request #1415 from Athou/dependabot/npm_and_yarn/commafeed-client/mantine-1f5c67be2f
Bump the mantine group across 1 directory with 6 updates
2024-06-11 07:17:44 +02:00
dependabot[bot]
23596b5ac6 Bump @vitejs/plugin-react from 4.2.1 to 4.3.1 in /commafeed-client
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 4.2.1 to 4.3.1.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.1/packages/plugin-react)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:12:52 +00:00
dependabot[bot]
2fdeb7acd8 Bump vite from 5.2.8 to 5.2.13 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.8 to 5.2.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.2.13/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.2.13/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-06-11 05:12:29 +00:00
dependabot[bot]
c62cac478c Bump the mantine group across 1 directory with 6 updates
Bumps the mantine group with 6 updates in the /commafeed-client directory:

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



Updates `@mantine/core` from 7.8.0 to 7.10.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.10.1/packages/@mantine/core)

Updates `@mantine/form` from 7.8.0 to 7.10.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.10.1/packages/@mantine/form)

Updates `@mantine/hooks` from 7.8.0 to 7.10.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.10.1/packages/@mantine/hooks)

Updates `@mantine/modals` from 7.8.0 to 7.10.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.10.1/packages/@mantine/modals)

Updates `@mantine/notifications` from 7.8.0 to 7.10.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.10.1/packages/@mantine/notifications)

Updates `@mantine/spotlight` from 7.8.0 to 7.10.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.10.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-06-11 05:12:05 +00:00
dependabot[bot]
e9026e0371 Bump prettier from 3.2.5 to 3.3.1 in /commafeed-client
Bumps [prettier](https://github.com/prettier/prettier) from 3.2.5 to 3.3.1.
- [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.5...3.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:11:50 +00:00
dependabot[bot]
7446d906ae Bump redoc from 2.1.3 to 2.1.5 in /commafeed-client
Bumps [redoc](https://github.com/Redocly/redoc) from 2.1.3 to 2.1.5.
- [Release notes](https://github.com/Redocly/redoc/releases)
- [Changelog](https://github.com/Redocly/redoc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Redocly/redoc/compare/v2.1.3...v2.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:11:42 +00:00
Jérémie Panzer
62ad09ac93 Merge pull request #1358 from Athou/dependabot/maven/com.ibm.icu-icu4j-75.1
Bump com.ibm.icu:icu4j from 74.2 to 75.1
2024-06-11 07:10:33 +02:00
Jérémie Panzer
01d1f920a8 Merge pull request #1391 from Athou/dependabot/maven/com.microsoft.playwright-playwright-1.44.0
Bump com.microsoft.playwright:playwright from 1.43.0 to 1.44.0
2024-06-11 07:10:25 +02:00
Jérémie Panzer
057810470c Merge pull request #1392 from Athou/dependabot/maven/io.swagger.core.v3-swagger-annotations-2.2.22
Bump io.swagger.core.v3:swagger-annotations from 2.2.21 to 2.2.22
2024-06-11 07:10:18 +02:00
Jérémie Panzer
5a6d6be8e5 Merge pull request #1394 from Athou/dependabot/maven/com.google.apis-google-api-services-youtube-v3-rev20240514-2.0.0
Bump com.google.apis:google-api-services-youtube from v3-rev20240310-2.0.0 to v3-rev20240514-2.0.0
2024-06-11 07:10:09 +02:00
Jérémie Panzer
c6c813a4ee Merge pull request #1395 from Athou/dependabot/maven/io.swagger.core.v3-swagger-maven-plugin-jakarta-2.2.22
Bump io.swagger.core.v3:swagger-maven-plugin-jakarta from 2.2.21 to 2.2.22
2024-06-11 07:10:01 +02:00
Jérémie Panzer
ad5787a38b Merge pull request #1421 from Athou/dependabot/maven/io.github.hakky54-sslcontext-kickstart-for-apache5-8.3.6
Bump io.github.hakky54:sslcontext-kickstart-for-apache5 from 8.3.4 to 8.3.6
2024-06-11 07:09:53 +02:00
Jérémie Panzer
387ceabf30 Merge pull request #1418 from Athou/dependabot/maven/org.apache.maven.plugins-maven-shade-plugin-3.6.0
Bump org.apache.maven.plugins:maven-shade-plugin from 3.5.2 to 3.6.0
2024-06-11 07:09:32 +02:00
Jérémie Panzer
ffe6962c36 Merge pull request #1423 from Athou/dependabot/maven/io.github.git-commit-id-git-commit-id-maven-plugin-9.0.0
Bump io.github.git-commit-id:git-commit-id-maven-plugin from 8.0.2 to 9.0.0
2024-06-11 07:09:24 +02:00
Jérémie Panzer
6d599fc77d Merge pull request #1422 from Athou/dependabot/maven/org.apache.maven.plugins-maven-checkstyle-plugin-3.4.0
Bump org.apache.maven.plugins:maven-checkstyle-plugin from 3.3.1 to 3.4.0
2024-06-11 07:09:01 +02:00
Jérémie Panzer
9fcff1342c Merge pull request #1425 from Athou/dependabot/npm_and_yarn/commafeed-client/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3 in /commafeed-client
2024-06-11 07:08:01 +02:00
dependabot[bot]
f7dbc2e9aa Bump braces from 3.0.2 to 3.0.3 in /commafeed-client
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 05:03:52 +00:00
Athou
468f2e4c76 remove warnings 2024-06-11 07:02:38 +02:00
Athou
883c9c79aa replace source and target with the new release setting 2024-06-10 12:20:26 +02:00
Athou
f171d05088 querydsl is no longer maintained, use an active fork 2024-06-10 12:17:36 +02:00
dependabot[bot]
f85745fe40 Bump io.github.git-commit-id:git-commit-id-maven-plugin
Bumps [io.github.git-commit-id:git-commit-id-maven-plugin](https://github.com/git-commit-id/git-commit-id-maven-plugin) from 8.0.2 to 9.0.0.
- [Release notes](https://github.com/git-commit-id/git-commit-id-maven-plugin/releases)
- [Commits](https://github.com/git-commit-id/git-commit-id-maven-plugin/compare/v8.0.2...v9.0.0)

---
updated-dependencies:
- dependency-name: io.github.git-commit-id:git-commit-id-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 09:17:15 +00:00
Athou
5ad93bb3ba add more checkstyle rules 2024-06-10 07:40:35 +02:00
dependabot[bot]
d80ed9d4dd Bump org.apache.maven.plugins:maven-checkstyle-plugin
Bumps [org.apache.maven.plugins:maven-checkstyle-plugin](https://github.com/apache/maven-checkstyle-plugin) from 3.3.1 to 3.4.0.
- [Commits](https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.3.1...maven-checkstyle-plugin-3.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-08 20:35:05 +00:00
dependabot[bot]
69b5f5418a 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.4 to 8.3.6.
- [Changelog](https://github.com/Hakky54/sslcontext-kickstart/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Hakky54/sslcontext-kickstart/compare/v8.3.4...v8.3.6)

---
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-06-08 20:35:01 +00:00
dependabot[bot]
06aa37659c Bump org.apache.maven.plugins:maven-shade-plugin from 3.5.2 to 3.6.0
Bumps [org.apache.maven.plugins:maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.5.2 to 3.6.0.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.5.2...maven-shade-plugin-3.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 09:45:39 +00:00
Jérémie Panzer
d5c98de839 Merge pull request #1360 from Athou/dependabot/maven/org.apache.maven.plugins-maven-jar-plugin-3.4.1
Bump org.apache.maven.plugins:maven-jar-plugin from 3.4.0 to 3.4.1
2024-06-02 10:19:14 +02:00
Jérémie Panzer
920975059c Merge pull request #1393 from Athou/dependabot/maven/org.mariadb.jdbc-mariadb-java-client-3.4.0
Bump org.mariadb.jdbc:mariadb-java-client from 3.3.3 to 3.4.0
2024-06-02 10:18:07 +02:00
Jérémie Panzer
7c6e4c3356 Merge pull request #1397 from Athou/dependabot/maven/redis.clients-jedis-5.1.3
Bump redis.clients:jedis from 5.1.2 to 5.1.3
2024-06-02 10:17:46 +02:00
Jérémie Panzer
143971da5e Merge pull request #1385 from Athou/dependabot/maven/com.mysql-mysql-connector-j-8.4.0
Bump com.mysql:mysql-connector-j from 8.3.0 to 8.4.0
2024-06-02 10:17:32 +02:00
dependabot[bot]
8976e9c01a Bump react-icons from 5.0.1 to 5.2.1 in /commafeed-client
Bumps [react-icons](https://github.com/react-icons/react-icons) from 5.0.1 to 5.2.1.
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.0.1...v5.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 07:10:59 +00:00
dependabot[bot]
20c6355efd Bump redis.clients:jedis from 5.1.2 to 5.1.3
Bumps [redis.clients:jedis](https://github.com/redis/jedis) from 5.1.2 to 5.1.3.
- [Release notes](https://github.com/redis/jedis/releases)
- [Commits](https://github.com/redis/jedis/compare/v5.1.2...v5.1.3)

---
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-05-27 07:06:21 +00:00
Jérémie Panzer
f86f38ef7a Merge pull request #1396 from SConaway/patch-1
login page: don't autocapitalize the username input
2024-05-26 10:14:36 +02:00
Jérémie Panzer
24311df551 Merge pull request #1389 from luckrnx09/luckrnx09-cmd-k-for-macos-users
Maint: Show `Cmd + K` for macOS users
2024-05-26 10:14:10 +02:00
luckrnx09
d02aa78def fix: determine os type from useOs hook 2024-05-25 21:35:10 +08:00
Steven Conaway
b131020f46 login page: don't autocapitalize the username input 2024-05-22 10:37:32 -06:00
dependabot[bot]
4ab82782b0 Bump io.swagger.core.v3:swagger-maven-plugin-jakarta
Bumps io.swagger.core.v3:swagger-maven-plugin-jakarta from 2.2.21 to 2.2.22.

---
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-05-20 09:56:18 +00:00
dependabot[bot]
6f9ebd5d78 Bump com.google.apis:google-api-services-youtube
Bumps com.google.apis:google-api-services-youtube from v3-rev20240310-2.0.0 to v3-rev20240514-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-05-20 09:56:15 +00:00
dependabot[bot]
7ebbf26369 Bump org.mariadb.jdbc:mariadb-java-client from 3.3.3 to 3.4.0
Bumps [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 3.3.3 to 3.4.0.
- [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.3...3.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 09:55:10 +00:00
dependabot[bot]
dbc93f9928 Bump io.swagger.core.v3:swagger-annotations from 2.2.21 to 2.2.22
Bumps io.swagger.core.v3:swagger-annotations from 2.2.21 to 2.2.22.

---
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-05-20 09:55:06 +00:00
dependabot[bot]
ad6ebd7e4d Bump com.microsoft.playwright:playwright from 1.43.0 to 1.44.0
Bumps [com.microsoft.playwright:playwright](https://github.com/microsoft/playwright-java) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/microsoft/playwright-java/releases)
- [Commits](https://github.com/microsoft/playwright-java/compare/v1.43.0...v1.44.0)

---
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-05-20 09:55:04 +00:00
luckrnx09
ab86247c8c update the hot key to mod+k for call spotlight 2024-05-15 21:47:34 +08:00
luckrnx09
884516be28 Maint: Show Cmd + K for macOS users 2024-05-15 21:42:32 +08:00
dependabot[bot]
c236b1adda Bump com.mysql:mysql-connector-j from 8.3.0 to 8.4.0
Bumps [com.mysql:mysql-connector-j](https://github.com/mysql/mysql-connector-j) from 8.3.0 to 8.4.0.
- [Changelog](https://github.com/mysql/mysql-connector-j/blob/release/8.x/CHANGES)
- [Commits](https://github.com/mysql/mysql-connector-j/compare/8.3.0...8.4.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-05-06 09:42:14 +00:00
dependabot[bot]
222117dafe Bump tss-react from 4.9.6 to 4.9.10 in /commafeed-client
Bumps [tss-react](https://github.com/garronej/tss-react) from 4.9.6 to 4.9.10.
- [Release notes](https://github.com/garronej/tss-react/releases)
- [Commits](https://github.com/garronej/tss-react/compare/v4.9.6...v4.9.10)

---
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-05-01 09:22:50 +00:00
dependabot[bot]
38cd27df57 Bump dayjs from 1.11.10 to 1.11.11 in /commafeed-client
Bumps [dayjs](https://github.com/iamkun/dayjs) from 1.11.10 to 1.11.11.
- [Release notes](https://github.com/iamkun/dayjs/releases)
- [Changelog](https://github.com/iamkun/dayjs/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/iamkun/dayjs/compare/v1.11.10...v1.11.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 15:58:08 +00:00
dependabot[bot]
5e07e74bb2 Bump react-dom and @types/react-dom in /commafeed-client
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). These dependencies needed to be updated together.

Updates `react-dom` from 18.2.0 to 18.3.1
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/HEAD/packages/react-dom)

Updates `@types/react-dom` from 18.2.25 to 18.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-26 19:04:55 +00:00
dependabot[bot]
fe779e361f Bump @fontsource/open-sans from 5.0.27 to 5.0.28 in /commafeed-client
Bumps [@fontsource/open-sans](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/open-sans) from 5.0.27 to 5.0.28.
- [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-04-26 19:03:47 +00:00
dependabot[bot]
1a51799497 Bump org.apache.maven.plugins:maven-jar-plugin from 3.4.0 to 3.4.1
Bumps [org.apache.maven.plugins:maven-jar-plugin](https://github.com/apache/maven-jar-plugin) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/apache/maven-jar-plugin/releases)
- [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.4.0...maven-jar-plugin-3.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 09:47:58 +00:00
dependabot[bot]
6ea926cdb0 Bump com.ibm.icu:icu4j from 74.2 to 75.1
Bumps [com.ibm.icu:icu4j](https://github.com/unicode-org/icu) from 74.2 to 75.1.
- [Release notes](https://github.com/unicode-org/icu/releases)
- [Commits](https://github.com/unicode-org/icu/commits)

---
updated-dependencies:
- dependency-name: com.ibm.icu:icu4j
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 09:46:50 +00:00
Jérémie Panzer
439d61946a Merge pull request #1351 from WangLei1993/master
add Chinese translation for new entry
2024-04-16 10:20:52 +02:00
WangLei1993
426c8d7dfb add Chinese translation for new entry 2024-04-16 15:01:23 +08:00
Athou
f1b51e8342 set the default value for new users to the same value as the default value for existing users 2024-04-15 21:59:54 +02:00
Athou
9de19e9f2d release 4.4.0 2024-04-15 16:40:10 +02:00
Athou
fc2eac7f2c align star vertically with feed favicon 2024-04-14 19:46:27 +02:00
Athou
bc6fc01c3f set on_desktop as the default value for icon because we don't have a lot of room on mobile 2024-04-14 18:40:43 +02:00
Athou
85ae70f278 fix combobox labels not displayed correctly 2024-04-14 18:25:45 +02:00
Athou
e415d1d945 remove dependency on bouncycastle 2024-04-14 17:36:35 +02:00
Jérémie Panzer
acb06c3405 Merge pull request #1344 from Athou/dependabot/npm_and_yarn/commafeed-client/lingui-be06312aa6
Bump the lingui group in /commafeed-client with 5 updates
2024-04-14 17:34:04 +02:00
Jérémie Panzer
a137ecb293 Merge pull request #1343 from Athou/dependabot/npm_and_yarn/commafeed-client/vitest-1.5.0
Bump vitest from 1.3.1 to 1.5.0 in /commafeed-client
2024-04-14 17:33:57 +02:00
dependabot[bot]
b4c1aea7c4 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.1` | `4.10.0` |
| [@lingui/macro](https://github.com/lingui/js-lingui) | `4.7.1` | `4.10.0` |
| [@lingui/react](https://github.com/lingui/js-lingui) | `4.7.1` | `4.10.0` |
| [@lingui/cli](https://github.com/lingui/js-lingui) | `4.7.1` | `4.10.0` |
| [@lingui/vite-plugin](https://github.com/lingui/js-lingui) | `4.7.1` | `4.10.0` |


Updates `@lingui/core` from 4.7.1 to 4.10.0
- [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.1...v4.10.0)

Updates `@lingui/macro` from 4.7.1 to 4.10.0
- [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.1...v4.10.0)

Updates `@lingui/react` from 4.7.1 to 4.10.0
- [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.1...v4.10.0)

Updates `@lingui/cli` from 4.7.1 to 4.10.0
- [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.1...v4.10.0)

Updates `@lingui/vite-plugin` from 4.7.1 to 4.10.0
- [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.1...v4.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-14 15:22:41 +00:00
dependabot[bot]
a0d86ce94a Bump vitest from 1.3.1 to 1.5.0 in /commafeed-client
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.3.1 to 1.5.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.5.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-04-14 15:21:20 +00:00
Jérémie Panzer
6ce0e2f151 Merge pull request #1342 from Athou/dependabot/npm_and_yarn/commafeed-client/typescript-5.4.5
Bump typescript from 5.4.2 to 5.4.5 in /commafeed-client
2024-04-14 17:20:31 +02:00
dependabot[bot]
628f7aca90 Bump typescript from 5.4.2 to 5.4.5 in /commafeed-client
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.4.2 to 5.4.5.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.4.2...v5.4.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-14 15:20:26 +00:00
Jérémie Panzer
a4a7d53670 Merge pull request #1346 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.2.8
Bump vite from 5.2.7 to 5.2.8 in /commafeed-client
2024-04-14 17:19:40 +02:00
Jérémie Panzer
e76e7879cd Merge pull request #1348 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-dom-18.2.25
Bump @types/react-dom from 18.2.21 to 18.2.25 in /commafeed-client
2024-04-14 17:19:33 +02:00
Jérémie Panzer
7a00e743eb Merge pull request #1347 from Athou/dependabot/npm_and_yarn/commafeed-client/mantine-49597fe963
Bump the mantine group in /commafeed-client with 6 updates
2024-04-14 17:18:53 +02:00
Athou
6e0e692ae8 generate new translations 2024-04-14 17:16:32 +02:00
dependabot[bot]
321b3d4819 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.6.1` | `7.8.0` |
| [@mantine/form](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/form) | `7.6.1` | `7.8.0` |
| [@mantine/hooks](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/hooks) | `7.6.1` | `7.8.0` |
| [@mantine/modals](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/modals) | `7.6.1` | `7.8.0` |
| [@mantine/notifications](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/notifications) | `7.6.1` | `7.8.0` |
| [@mantine/spotlight](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/spotlight) | `7.6.1` | `7.8.0` |


Updates `@mantine/core` from 7.6.1 to 7.8.0
- [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.8.0/packages/@mantine/core)

Updates `@mantine/form` from 7.6.1 to 7.8.0
- [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.8.0/packages/@mantine/form)

Updates `@mantine/hooks` from 7.6.1 to 7.8.0
- [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.8.0/packages/@mantine/hooks)

Updates `@mantine/modals` from 7.6.1 to 7.8.0
- [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.8.0/packages/@mantine/modals)

Updates `@mantine/notifications` from 7.6.1 to 7.8.0
- [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.8.0/packages/@mantine/notifications)

Updates `@mantine/spotlight` from 7.6.1 to 7.8.0
- [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.8.0/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-04-14 15:12:55 +00:00
Athou
211708255e actually save new settings 2024-04-14 16:59:04 +02:00
Athou
dcc32cb539 use a random available port for tests 2024-04-14 16:49:26 +02:00
dependabot[bot]
c9367afd9d Bump vite from 5.2.7 to 5.2.8 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.7 to 5.2.8.
- [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.2.8/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-04-14 14:29:58 +00:00
dependabot[bot]
af724fbb87 Bump @types/react-dom from 18.2.21 to 18.2.25 in /commafeed-client
Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.2.21 to 18.2.25.
- [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-04-14 14:29:56 +00:00
Jérémie Panzer
9d052f2f59 Merge pull request #1349 from Athou/dependabot/npm_and_yarn/commafeed-client/typescript-eslint/eslint-plugin-7.6.0
Bump @typescript-eslint/eslint-plugin from 7.5.0 to 7.6.0 in /commafeed-client
2024-04-14 16:29:34 +02:00
Jérémie Panzer
e9a9334c03 Merge pull request #1345 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.78
Bump @types/react from 18.2.64 to 18.2.78 in /commafeed-client
2024-04-14 16:29:19 +02:00
Jérémie Panzer
80c9adcf0f Merge pull request #1319 from Athou/dependabot/npm_and_yarn/commafeed-client/reduxjs/toolkit-2.2.3
Bump @reduxjs/toolkit from 2.2.1 to 2.2.3 in /commafeed-client
2024-04-14 16:28:59 +02:00
Jérémie Panzer
13c402d9d0 Merge pull request #1320 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-tsconfig-paths-4.3.2
Bump vite-tsconfig-paths from 4.3.1 to 4.3.2 in /commafeed-client
2024-04-14 16:28:53 +02:00
Jérémie Panzer
5d5dc67a46 Merge pull request #1323 from Athou/dependabot/npm_and_yarn/commafeed-client/fontsource/open-sans-5.0.27
Bump @fontsource/open-sans from 5.0.26 to 5.0.27 in /commafeed-client
2024-04-14 16:28:45 +02:00
Jérémie Panzer
f2330d8346 Merge pull request #1328 from Athou/dependabot/npm_and_yarn/commafeed-client/tss-react-4.9.6
Bump tss-react from 4.9.4 to 4.9.6 in /commafeed-client
2024-04-14 16:28:38 +02:00
Jérémie Panzer
ee061f3362 Merge pull request #1350 from Athou/dependabot/maven/commons-io-commons-io-2.16.1
Bump commons-io:commons-io from 2.16.0 to 2.16.1
2024-04-14 16:27:45 +02:00
Jérémie Panzer
e071cb457f Merge pull request #1310 from Athou/dependabot/maven/io.swagger.core.v3-swagger-maven-plugin-jakarta-2.2.21
Bump io.swagger.core.v3:swagger-maven-plugin-jakarta from 2.2.20 to 2.2.21
2024-04-14 16:27:33 +02:00
Jérémie Panzer
7972dec827 Merge pull request #1311 from Athou/dependabot/maven/io.github.git-commit-id-git-commit-id-maven-plugin-8.0.2
Bump io.github.git-commit-id:git-commit-id-maven-plugin from 8.0.1 to 8.0.2
2024-04-14 16:27:27 +02:00
Jérémie Panzer
41c0200270 Merge pull request #1308 from Athou/dependabot/maven/io.swagger.core.v3-swagger-annotations-2.2.21
Bump io.swagger.core.v3:swagger-annotations from 2.2.20 to 2.2.21
2024-04-14 16:27:18 +02:00
Jérémie Panzer
9e5fa5472a Merge pull request #1307 from Athou/dependabot/maven/io.github.hakky54-sslcontext-kickstart-for-apache5-8.3.4
Bump io.github.hakky54:sslcontext-kickstart-for-apache5 from 8.3.2 to 8.3.4
2024-04-14 16:27:10 +02:00
Jérémie Panzer
0ebab27588 Merge pull request #1297 from Athou/dependabot/maven/com.google.apis-google-api-services-youtube-v3-rev20240310-2.0.0
Bump com.google.apis:google-api-services-youtube from v3-rev20240303-2.0.0 to v3-rev20240310-2.0.0
2024-04-14 16:26:55 +02:00
Athou
0d081bc47e add button in the header to star entry (#1025) 2024-04-14 16:22:51 +02:00
dependabot[bot]
92853a164a 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.5.0 to 7.6.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.6.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-04-14 09:16:34 +00:00
Jérémie Panzer
5d75885352 Merge pull request #1341 from Athou/dependabot/maven/com.microsoft.playwright-playwright-1.43.0
Bump com.microsoft.playwright:playwright from 1.41.2 to 1.43.0
2024-04-14 11:15:40 +02:00
Jérémie Panzer
83b8886846 Merge pull request #1340 from Athou/dependabot/maven/org.apache.maven.plugins-maven-jar-plugin-3.4.0
Bump org.apache.maven.plugins:maven-jar-plugin from 3.3.0 to 3.4.0
2024-04-14 11:15:33 +02:00
Jérémie Panzer
f3869f92dc Merge pull request #1339 from Athou/dependabot/npm_and_yarn/commafeed-client/eslint-config-love-47.0.0
Bump eslint-config-love from 44.0.0 to 47.0.0 in /commafeed-client
2024-04-14 11:15:26 +02:00
dependabot[bot]
b1c1f2adc4 Bump commons-io:commons-io from 2.16.0 to 2.16.1
Bumps commons-io:commons-io from 2.16.0 to 2.16.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-14 09:12:37 +00:00
Jérémie Panzer
fd8c6c5531 Update dependabot.yml 2024-04-14 11:11:54 +02:00
dependabot[bot]
b37346ad20 Bump @types/react from 18.2.64 to 18.2.78 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.64 to 18.2.78.
- [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-04-14 09:09:30 +00:00
dependabot[bot]
1b2e2e6915 Bump com.microsoft.playwright:playwright from 1.41.2 to 1.43.0
Bumps [com.microsoft.playwright:playwright](https://github.com/microsoft/playwright-java) from 1.41.2 to 1.43.0.
- [Release notes](https://github.com/microsoft/playwright-java/releases)
- [Commits](https://github.com/microsoft/playwright-java/compare/v1.41.2...v1.43.0)

---
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-04-14 09:08:52 +00:00
dependabot[bot]
f483d569f0 Bump org.apache.maven.plugins:maven-jar-plugin from 3.3.0 to 3.4.0
Bumps [org.apache.maven.plugins:maven-jar-plugin](https://github.com/apache/maven-jar-plugin) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/apache/maven-jar-plugin/releases)
- [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.3.0...maven-jar-plugin-3.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-14 09:08:47 +00:00
dependabot[bot]
b9bbcf1e60 Bump eslint-config-love from 44.0.0 to 47.0.0 in /commafeed-client
Bumps [eslint-config-love](https://github.com/mightyiam/eslint-config-love) from 44.0.0 to 47.0.0.
- [Release notes](https://github.com/mightyiam/eslint-config-love/releases)
- [Changelog](https://github.com/mightyiam/eslint-config-love/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mightyiam/eslint-config-love/compare/v44.0.0...v47.0.0)

---
updated-dependencies:
- dependency-name: eslint-config-love
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-14 09:08:43 +00:00
Athou
3097272179 add button in the header to open entry link (#1333) 2024-04-05 13:38:07 +02:00
Athou
d0b92774bc remove unnecessary curly braces automatically 2024-04-04 08:24:05 +02:00
dependabot[bot]
c82a142c96 Bump vite-tsconfig-paths from 4.3.1 to 4.3.2 in /commafeed-client
Bumps [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) from 4.3.1 to 4.3.2.
- [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases)
- [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v4.3.1...v4.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 20:27:13 +00:00
Jérémie Panzer
5b43d416fc Merge pull request #1326 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.2.7
Bump vite from 5.1.6 to 5.2.7 in /commafeed-client
2024-04-03 22:26:14 +02:00
Athou
40e1c70fca don't try to mark entries that are not markable (#1303) 2024-04-03 15:47:01 +02:00
Athou
d610f980c7 fix typo 2024-04-03 09:11:06 +02:00
dependabot[bot]
68c8ce1ef3 Bump @reduxjs/toolkit from 2.2.1 to 2.2.3 in /commafeed-client
Bumps [@reduxjs/toolkit](https://github.com/reduxjs/redux-toolkit) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/reduxjs/redux-toolkit/releases)
- [Commits](https://github.com/reduxjs/redux-toolkit/compare/v2.2.1...v2.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 19:54:59 +00:00
dependabot[bot]
218a602c0b Bump vite from 5.1.6 to 5.2.7 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.6 to 5.2.7.
- [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.2.7/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-04-02 19:54:07 +00:00
Jérémie Panzer
cad65e953e Merge pull request #1329 from Athou/dependabot/npm_and_yarn/commafeed-client/axios-1.6.8
Bump axios from 1.6.7 to 1.6.8 in /commafeed-client
2024-04-02 21:54:00 +02:00
Athou
39bc9713e4 update eslint-plugin 2024-04-02 21:52:21 +02:00
Jérémie Panzer
812da21b6f Merge pull request #1330 from Athou/dependabot/npm_and_yarn/commafeed-client/eslint-plugin-react-7.34.1
Bump eslint-plugin-react from 7.34.0 to 7.34.1 in /commafeed-client
2024-04-02 21:44:30 +02:00
Athou
a756783604 readd eslint-config-love now that it has been updated 2024-04-02 21:44:06 +02:00
Athou
16199c5b54 add native browser sharing (#1255) 2024-04-01 20:23:44 +02:00
Jérémie Panzer
3964977a0a Merge pull request #1309 from Athou/dependabot/maven/org.apache.maven.plugins-maven-compiler-plugin-3.13.0
Bump org.apache.maven.plugins:maven-compiler-plugin from 3.12.1 to 3.13.0
2024-04-01 20:20:54 +02:00
Jérémie Panzer
f5b4d037ef Merge pull request #1298 from Athou/dependabot/maven/org.postgresql-postgresql-42.7.3
Bump org.postgresql:postgresql from 42.7.2 to 42.7.3
2024-04-01 20:20:46 +02:00
Jérémie Panzer
5929581fee Merge pull request #1316 from Athou/dependabot/maven/commons-io-commons-io-2.16.0
Bump commons-io:commons-io from 2.15.1 to 2.16.0
2024-04-01 20:20:29 +02:00
Jérémie Panzer
92d0d6af47 Merge pull request #1312 from Athou/dependabot/maven/org.projectlombok-lombok-1.18.32
Bump org.projectlombok:lombok from 1.18.30 to 1.18.32
2024-04-01 20:20:22 +02:00
dependabot[bot]
413253e4a9 Bump eslint-plugin-react from 7.34.0 to 7.34.1 in /commafeed-client
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.34.0 to 7.34.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/v7.34.1/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.34.0...v7.34.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 09:48:49 +00:00
dependabot[bot]
9c98e7eca1 Bump axios from 1.6.7 to 1.6.8 in /commafeed-client
Bumps [axios](https://github.com/axios/axios) from 1.6.7 to 1.6.8.
- [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.7...v1.6.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 09:48:36 +00:00
dependabot[bot]
d3d1aba834 Bump tss-react from 4.9.4 to 4.9.6 in /commafeed-client
Bumps [tss-react](https://github.com/garronej/tss-react) from 4.9.4 to 4.9.6.
- [Release notes](https://github.com/garronej/tss-react/releases)
- [Commits](https://github.com/garronej/tss-react/compare/v4.9.4...v4.9.6)

---
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-04-01 09:48:23 +00:00
dependabot[bot]
6dd3ce2e72 Bump @fontsource/open-sans from 5.0.26 to 5.0.27 in /commafeed-client
Bumps [@fontsource/open-sans](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/open-sans) from 5.0.26 to 5.0.27.
- [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-04-01 09:46:51 +00:00
dependabot[bot]
398648ac91 Bump commons-io:commons-io from 2.15.1 to 2.16.0
Bumps commons-io:commons-io from 2.15.1 to 2.16.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 09:18:38 +00:00
Jérémie Panzer
28ef9ccfd2 Merge pull request #1314 from WangLei1993/master
Chinese translations
2024-03-28 23:22:32 +01:00
WangLei1993
acab5295cc change and add some Chinese translation 2024-03-29 05:09:04 +08:00
WangLei1993
3d73435446 change and add some Chinese translation 2024-03-29 05:00:39 +08:00
Jérémie Panzer
eab08d2197 Merge pull request #1313 from maaaathis/patch-1
Update german (DE) locale
2024-03-27 19:15:00 +01:00
mathis
d13b96edd1 fix src/pages/app/AboutPage.tsx message 2024-03-27 18:31:05 +01:00
mathis
624aa9cb23 fix src/pages/app/FeedDetailsPage.tsx message 2024-03-27 18:30:36 +01:00
mathis
e76ee6dc9b update german messages 2024-03-27 18:15:30 +01:00
dependabot[bot]
9bf7dbe893 Bump org.projectlombok:lombok from 1.18.30 to 1.18.32
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.30 to 1.18.32.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.30...v1.18.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 09:29:31 +00:00
dependabot[bot]
0610080d2a Bump io.github.git-commit-id:git-commit-id-maven-plugin
Bumps [io.github.git-commit-id:git-commit-id-maven-plugin](https://github.com/git-commit-id/git-commit-id-maven-plugin) from 8.0.1 to 8.0.2.
- [Release notes](https://github.com/git-commit-id/git-commit-id-maven-plugin/releases)
- [Commits](https://github.com/git-commit-id/git-commit-id-maven-plugin/compare/v8.0.1...v8.0.2)

---
updated-dependencies:
- dependency-name: io.github.git-commit-id:git-commit-id-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 09:29:24 +00:00
dependabot[bot]
19d91cf07f Bump io.swagger.core.v3:swagger-maven-plugin-jakarta
Bumps io.swagger.core.v3:swagger-maven-plugin-jakarta from 2.2.20 to 2.2.21.

---
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-03-25 09:29:20 +00:00
dependabot[bot]
d9b9b8c3da 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.12.1 to 3.13.0.
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.12.1...maven-compiler-plugin-3.13.0)

---
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-03-25 09:29:18 +00:00
dependabot[bot]
0889dc145c Bump io.swagger.core.v3:swagger-annotations from 2.2.20 to 2.2.21
Bumps io.swagger.core.v3:swagger-annotations from 2.2.20 to 2.2.21.

---
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-03-25 09:29:14 +00:00
dependabot[bot]
113a8d49f0 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.2 to 8.3.4.
- [Changelog](https://github.com/Hakky54/sslcontext-kickstart/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Hakky54/sslcontext-kickstart/compare/v8.3.2...v8.3.4)

---
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-03-25 09:29:09 +00:00
Jérémie Panzer
f60a968fb1 Merge pull request #1302 from hywax/master
Update RU locale
2024-03-23 11:28:17 +01:00
hywax
02b060178b Update RU locale №4 2024-03-22 19:20:53 +05:00
hywax
b1f3afd494 Update RU locale №3 2024-03-22 19:17:14 +05:00
hywax
35acac7b93 Update RU locale №2 2024-03-22 19:02:00 +05:00
hywax
9334e7b7a8 Update RU locale 2024-03-22 13:54:48 +05:00
dependabot[bot]
f424314b0d Bump org.postgresql:postgresql from 42.7.2 to 42.7.3
Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.2 to 42.7.3.
- [Release notes](https://github.com/pgjdbc/pgjdbc/releases)
- [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.2...REL42.7.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 09:30:37 +00:00
dependabot[bot]
c4a9025160 Bump com.google.apis:google-api-services-youtube
Bumps com.google.apis:google-api-services-youtube from v3-rev20240303-2.0.0 to v3-rev20240310-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-03-18 09:30:33 +00:00
Jérémie Panzer
85a134ef53 Merge pull request #1295 from Athou/dependabot/npm_and_yarn/commafeed-client/follow-redirects-1.15.6
Bump follow-redirects from 1.15.4 to 1.15.6 in /commafeed-client
2024-03-17 10:19:06 +01:00
dependabot[bot]
db1fe0fe91 Bump follow-redirects from 1.15.4 to 1.15.6 in /commafeed-client
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 22:39:20 +00:00
Jérémie Panzer
30a45fc329 Merge pull request #1285 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-dom-18.2.21
Bump @types/react-dom from 18.2.19 to 18.2.21 in /commafeed-client
2024-03-11 17:43:43 +01:00
Jérémie Panzer
ce90fc356c Merge pull request #1294 from Athou/dependabot/npm_and_yarn/commafeed-client/vite-5.1.6
Bump vite from 5.1.4 to 5.1.6 in /commafeed-client
2024-03-11 17:43:18 +01:00
Athou
4f50e34b21 npm packages change too frequently and are only used in the client, reduce interval since it's not critical 2024-03-11 17:42:51 +01:00
Jérémie Panzer
8ccf148eaa Merge pull request #1290 from Athou/dependabot/npm_and_yarn/commafeed-client/monaco-editor-0.47.0
Bump monaco-editor from 0.46.0 to 0.47.0 in /commafeed-client
2024-03-11 17:40:40 +01:00
dependabot[bot]
fa343bda20 Bump @types/react-dom from 18.2.19 to 18.2.21 in /commafeed-client
Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.2.19 to 18.2.21.
- [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-03-11 16:39:57 +00:00
dependabot[bot]
a66f8d7065 Bump vite from 5.1.4 to 5.1.6 in /commafeed-client
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.4 to 5.1.6.
- [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.6/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-03-11 16:39:33 +00:00
Jérémie Panzer
819003e43c Merge pull request #1292 from Athou/dependabot/npm_and_yarn/commafeed-client/fontsource/open-sans-5.0.26
Bump @fontsource/open-sans from 5.0.25 to 5.0.26 in /commafeed-client
2024-03-11 17:39:17 +01:00
Jérémie Panzer
27800296fb Merge pull request #1291 from Athou/dependabot/npm_and_yarn/commafeed-client/typescript-eslint/eslint-plugin-7.1.1
Bump @typescript-eslint/eslint-plugin from 7.1.0 to 7.1.1 in /commafeed-client
2024-03-11 17:38:54 +01:00
Jérémie Panzer
b869ef072a Merge pull request #1287 from Athou/dependabot/npm_and_yarn/commafeed-client/react-router-dom-6.22.3
Bump react-router-dom from 6.22.2 to 6.22.3 in /commafeed-client
2024-03-11 17:38:46 +01:00
Jérémie Panzer
03dfee468c Merge pull request #1284 from Athou/dependabot/npm_and_yarn/commafeed-client/types/react-18.2.64
Bump @types/react from 18.2.61 to 18.2.64 in /commafeed-client
2024-03-11 17:38:33 +01:00
Jérémie Panzer
d3275074bb Merge pull request #1286 from Athou/dependabot/npm_and_yarn/commafeed-client/typescript-5.4.2
Bump typescript from 5.3.3 to 5.4.2 in /commafeed-client
2024-03-11 11:52:13 +01:00
Jérémie Panzer
cc0965f69c Merge pull request #1293 from Athou/dependabot/github_actions/softprops/action-gh-release-2
Bump softprops/action-gh-release from 1 to 2
2024-03-11 10:49:02 +01:00
Jérémie Panzer
8e2fa3e153 Merge pull request #1289 from Athou/dependabot/maven/io.github.git-commit-id-git-commit-id-maven-plugin-8.0.1
Bump io.github.git-commit-id:git-commit-id-maven-plugin from 8.0.0 to 8.0.1
2024-03-11 10:34:27 +01:00
dependabot[bot]
df10bd7351 Bump softprops/action-gh-release from 1 to 2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 09:31:04 +00:00
dependabot[bot]
080289ca4e Bump @fontsource/open-sans from 5.0.25 to 5.0.26 in /commafeed-client
Bumps [@fontsource/open-sans](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/open-sans) from 5.0.25 to 5.0.26.
- [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-11 09:26:09 +00:00
dependabot[bot]
28b821f085 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.1.0 to 7.1.1.
- [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.1/packages/eslint-plugin)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 09:25:55 +00:00
dependabot[bot]
700f3ec029 Bump monaco-editor from 0.46.0 to 0.47.0 in /commafeed-client
Bumps [monaco-editor](https://github.com/microsoft/monaco-editor) from 0.46.0 to 0.47.0.
- [Changelog](https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/microsoft/monaco-editor/compare/v0.46.0...v0.47.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-03-11 09:25:15 +00:00
dependabot[bot]
d7b3ed0baa Bump io.github.git-commit-id:git-commit-id-maven-plugin
Bumps [io.github.git-commit-id:git-commit-id-maven-plugin](https://github.com/git-commit-id/git-commit-id-maven-plugin) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/git-commit-id/git-commit-id-maven-plugin/releases)
- [Commits](https://github.com/git-commit-id/git-commit-id-maven-plugin/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: io.github.git-commit-id:git-commit-id-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 09:25:07 +00:00
dependabot[bot]
f1711014e5 Bump react-router-dom from 6.22.2 to 6.22.3 in /commafeed-client
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.2 to 6.22.3.
- [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.3/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-11 09:24:27 +00:00
dependabot[bot]
29f2270443 Bump typescript from 5.3.3 to 5.4.2 in /commafeed-client
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.3.3 to 5.4.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.3.3...v5.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 09:24:02 +00:00
dependabot[bot]
39ee4e771c Bump @types/react from 18.2.61 to 18.2.64 in /commafeed-client
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.61 to 18.2.64.
- [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-11 09:23:25 +00:00
Jérémie Panzer
6e4d2d57fa Merge pull request #1283 from Athou/dependabot/maven/io.github.git-commit-id-git-commit-id-maven-plugin-8.0.0
Bump io.github.git-commit-id:git-commit-id-maven-plugin from 7.0.0 to 8.0.0
2024-03-07 20:02:15 +01:00
Jérémie Panzer
7cd850a2e8 Merge pull request #1282 from Athou/dependabot/maven/io.dropwizard-dropwizard-dependencies-4.0.7
Bump io.dropwizard:dropwizard-dependencies from 4.0.6 to 4.0.7
2024-03-07 20:01:32 +01:00
Jérémie Panzer
3280023823 Merge pull request #1281 from Athou/dependabot/maven/com.google.apis-google-api-services-youtube-v3-rev20240303-2.0.0
Bump com.google.apis:google-api-services-youtube from v3-rev20240225-2.0.0 to v3-rev20240303-2.0.0
2024-03-07 20:01:19 +01:00
Jérémie Panzer
3646a9610e Merge pull request #1280 from Athou/dependabot/maven/redis.clients-jedis-5.1.2
Bump redis.clients:jedis from 5.1.1 to 5.1.2
2024-03-07 20:00:51 +01:00
dependabot[bot]
ab639b3ee6 Bump io.github.git-commit-id:git-commit-id-maven-plugin
Bumps [io.github.git-commit-id:git-commit-id-maven-plugin](https://github.com/git-commit-id/git-commit-id-maven-plugin) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/git-commit-id/git-commit-id-maven-plugin/releases)
- [Commits](https://github.com/git-commit-id/git-commit-id-maven-plugin/compare/v7.0.0...v8.0.0)

---
updated-dependencies:
- dependency-name: io.github.git-commit-id:git-commit-id-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-07 18:55:21 +00:00
dependabot[bot]
c85ba3fa75 Bump io.dropwizard:dropwizard-dependencies from 4.0.6 to 4.0.7
Bumps io.dropwizard:dropwizard-dependencies from 4.0.6 to 4.0.7.

---
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-03-07 18:55:15 +00:00
dependabot[bot]
193c2aecfb Bump com.google.apis:google-api-services-youtube
Bumps com.google.apis:google-api-services-youtube from v3-rev20240225-2.0.0 to v3-rev20240303-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-03-07 18:55:10 +00:00
dependabot[bot]
336de875ca Bump redis.clients:jedis from 5.1.1 to 5.1.2
Bumps [redis.clients:jedis](https://github.com/redis/jedis) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/redis/jedis/releases)
- [Commits](https://github.com/redis/jedis/compare/v5.1.1...v5.1.2)

---
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-03-07 18:54:08 +00:00
Athou
eb5012f67e accept .opml extension for opml import 2024-03-05 20:57:08 +01:00
Athou
5c764b9b25 use .opml extension for opml file export 2024-03-05 20:56:51 +01:00
Athou
5da94a7ed0 release 4.3.3 2024-03-05 18:33:44 +01:00
Athou
dfb3006c47 fix OMPL import (#1279) 2024-03-05 18:32:52 +01:00
Athou
e626f36c0a workaround no longer needed 2024-03-05 17:56:52 +01:00
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
218 changed files with 19334 additions and 17725 deletions

View File

@@ -11,28 +11,52 @@ 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"
cache: "maven" cache: "maven"
# Build # Build & Test
- name: Build with Maven - name: Build with Maven
run: mvn --batch-mode --update-snapshots verify run: mvn --batch-mode --no-transfer-progress install
env:
TEST_DATABASE: h2
- name: Run integration tests on PostgreSQL
run: mvn --batch-mode --no-transfer-progress failsafe:integration-test failsafe:verify
env:
TEST_DATABASE: postgresql
- name: Run integration tests on MySQL
run: mvn --batch-mode --no-transfer-progress failsafe:integration-test failsafe:verify
env:
TEST_DATABASE: mysql
- name: Run integration tests on MariaDB
run: mvn --batch-mode --no-transfer-progress failsafe:integration-test failsafe:verify
env:
TEST_DATABASE: mariadb
- name: Run integration tests with Redis cache enabled
run: mvn --batch-mode --no-transfer-progress failsafe:integration-test failsafe:verify
env:
TEST_DATABASE: h2
REDIS: true
# Upload artifacts
- name: Upload JAR - name: Upload JAR
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: ${{ matrix.java == '17' }} if: ${{ matrix.java == '17' }}
@@ -40,32 +64,40 @@ jobs:
name: commafeed.jar name: commafeed.jar
path: commafeed-server/target/commafeed.jar path: commafeed-server/target/commafeed.jar
- name: Upload Playwright artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts
path: |
**/target/playwright-artifacts/
# 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@v6
if: ${{ matrix.java == '17' && github.ref_type == 'tag' }} if: ${{ matrix.java == '17' && github.ref_type == 'tag' }}
with: with:
context: . context: .
push: true push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8 platforms: linux/amd64,linux/arm64/v8
tags: | tags: |
athou/commafeed:latest athou/commafeed:latest
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@v6
if: ${{ matrix.java == '17' && github.ref_name == 'master' }} if: ${{ matrix.java == '17' && github.ref_name == 'master' }}
with: with:
context: . context: .
push: true push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8 platforms: linux/amd64,linux/arm64/v8
tags: athou/commafeed:master tags: athou/commafeed:master
# Create GitHub release after Docker image has been published # Create GitHub release after Docker image has been published
@@ -77,7 +109,7 @@ jobs:
version: ${{ github.ref_name }} version: ${{ github.ref_name }}
- name: Create GitHub release - name: Create GitHub release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
if: ${{ matrix.java == '17' && github.ref_type == 'tag' }} if: ${{ matrix.java == '17' && github.ref_type == 'tag' }}
with: with:
name: CommaFeed ${{ github.ref_name }} name: CommaFeed ${{ github.ref_name }}

Binary file not shown.

View File

@@ -6,7 +6,7 @@
# "License"); you may not use this file except in compliance # "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at # with the License. You may obtain a copy of the License at
# #
# https://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, # Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an # software distributed under the License is distributed on an
@@ -14,5 +14,5 @@
# KIND, either express or implied. See the License for the # KIND, either express or implied. See the License for the
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip distributionType=only-script
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip

View File

@@ -1,5 +1,59 @@
# Changelog # Changelog
## [4.5.0]
- significantly reduce the time needed to retrieve entries or mark them as read, especially when there are a lot of
entries (#1452)
- fix a race condition where a feed could be refreshed before it was created in the database
- fix an issue that could cause the websocket notification to contain the wrong number of unread entries when using
mysql/mariadb
- fix an error when trying to mark all starred entries as read
- remove the `onlyIds` parameter from REST endpoints since retrieving all the entries is now just as fast
- remove support for microsoft sqlserver because it's not covered with integration tests (please open an issue if you'd
like it back)
## [4.4.1]
- fix vertical scrolling issues with Safari (#1168)
- the default value for new users for the "star entry" button and the "open in new tab" button in the entry headers is
now "on desktop" instead of "always"
- the "keyboard shortcuts" help page now shows "Cmd" instead of "Ctrl" on macOS (#1389)
- remove a superfluous feed fetch when subscribing to a feed (#1431)
- the Docker image now uses Java 21
## [4.4.0]
- add support for sharing using the browser native capabilities if available (#1255)
- add a button in the entry headers to star an entry (#1025)
- add a button in the entry headers to open links in a new tab (#1333)
- add two options in the settings to toggle those buttons
- accept .opml file extension when importing and export with the .opml extension
- the "mark as read" option is no longer shown in the context menu for entries that are too old to be marked as read (
older than `keepStatusDays`) (#1303)
## [4.3.3]
- fix OPML import (#1279)
## [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] ## [4.2.1]
- fix an issue that caused the tree to show an incorrect unread count after a websocket notification because entries - fix an issue that caused the tree to show an incorrect unread count after a websocket notification because entries

View File

@@ -1,4 +1,4 @@
FROM eclipse-temurin:17-jre FROM eclipse-temurin:21.0.3_9-jre
EXPOSE 8082 EXPOSE 8082

View File

@@ -1,8 +0,0 @@
dist
node_modules
vite.config.ts
# compiled linguijs locales
# they no longer exist but we keep this to avoid issues with people still having those files on disk
src/locales/**/*.ts

View File

@@ -1,41 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true
},
extends: ["standard-with-typescript", "plugin:react/recommended", "plugin:react-hooks/recommended", "plugin:prettier/recommended"],
settings: {
react: {
version: "detect"
}
},
overrides: [
{
env: {
node: true
},
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script"
}
}
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module"
},
plugins: ["react"],
rules: {
"@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-floating-promises": "off",
"@typescript-eslint/no-misused-promises": "off",
"@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/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "error"
}
}

View File

@@ -1,8 +0,0 @@
{
"printWidth": 140,
"semi": false,
"tabWidth": 4,
"arrowParens": "avoid",
"endOfLine": "auto",
"trailingComma": "es5"
}

View File

@@ -0,0 +1,19 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"formatter": {
"indentStyle": "space",
"indentWidth": 4,
"lineEnding": "lf",
"lineWidth": 140
},
"javascript": {
"formatter": {
"trailingCommas": "es5",
"semicolons": "asNeeded",
"arrowParentheses": "asNeeded"
}
},
"files": {
"ignore": ["dist", "node_modules", "target", "target-ide"]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,85 +1,79 @@
{ {
"name": "commafeed-client", "name": "commafeed-client",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"dev:typescript": "tsc --watch", "dev:typescript": "tsc --watch",
"build": "tsc && vite build", "build": "tsc && vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest", "test": "vitest",
"test:ci": "vitest run", "test:ci": "vitest run",
"eslint": "eslint --ext=.js,.jsx,.ts,.tsx src", "lint": "biome check ./src",
"i18n:extract": "lingui extract --clean" "lint:fix": "biome check --write ./src",
}, "i18n:extract": "lingui extract --clean"
"dependencies": { },
"@emotion/react": "^11.11.3", "dependencies": {
"@fontsource/open-sans": "^5.0.20", "@emotion/react": "^11.11.4",
"@lingui/core": "^4.7.0", "@fontsource/open-sans": "^5.0.28",
"@lingui/macro": "^4.7.0", "@lingui/core": "^4.11.2",
"@lingui/react": "^4.7.0", "@lingui/macro": "^4.11.2",
"@mantine/core": "^7.3.2", "@lingui/react": "^4.11.2",
"@mantine/form": "^7.3.2", "@mantine/core": "^7.11.1",
"@mantine/hooks": "^7.3.2", "@mantine/form": "^7.11.1",
"@mantine/modals": "^7.3.2", "@mantine/hooks": "^7.11.1",
"@mantine/notifications": "^7.3.2", "@mantine/modals": "^7.11.1",
"@mantine/spotlight": "^7.3.2", "@mantine/notifications": "^7.11.1",
"@monaco-editor/react": "^4.6.0", "@mantine/spotlight": "^7.11.1",
"@reduxjs/toolkit": "^2.0.1", "@monaco-editor/react": "^4.6.0",
"axios": "^1.6.3", "@reduxjs/toolkit": "^2.2.6",
"dayjs": "^1.11.10", "axios": "^1.7.2",
"escape-string-regexp": "^5.0.0", "dayjs": "^1.11.11",
"interweave": "^13.1.0", "escape-string-regexp": "^5.0.0",
"monaco-editor": "^0.45.0", "interweave": "^13.1.0",
"mousetrap": "^1.6.5", "monaco-editor": "^0.50.0",
"react": "^18.2.0", "mousetrap": "^1.6.5",
"react-async-hook": "^4.0.0", "react": "^18.3.1",
"react-contexify": "^6.0.0", "react-async-hook": "^4.0.0",
"react-dom": "^18.2.0", "react-contexify": "^6.0.0",
"react-draggable": "^4.4.6", "react-device-detect": "^2.2.3",
"react-ga4": "^2.1.0", "react-dom": "^18.3.1",
"react-icons": "^4.12.0", "react-draggable": "^4.4.6",
"react-infinite-scroller": "^1.2.6", "react-ga4": "^2.1.0",
"react-redux": "^9.0.4", "react-helmet": "^6.1.0",
"react-router-dom": "^6.21.1", "react-icons": "^5.2.1",
"react-swipeable": "^7.0.1", "react-infinite-scroller": "^1.2.6",
"redoc": "^2.1.3", "react-redux": "^9.1.2",
"throttle-debounce": "^5.0.0", "react-router-dom": "^6.24.1",
"tinycon": "^0.6.8", "react-swipeable": "^7.0.1",
"tss-react": "^4.9.3", "redoc": "^2.1.5",
"use-local-storage": "^3.0.0", "throttle-debounce": "^5.0.2",
"websocket-heartbeat-js": "^1.1.3" "tinycon": "^0.6.8",
}, "tss-react": "^4.9.10",
"devDependencies": { "use-local-storage": "^3.0.0",
"@lingui/cli": "^4.7.0", "vite-plugin-biome": "^1.0.12",
"@lingui/vite-plugin": "^4.7.0", "websocket-heartbeat-js": "^1.1.3"
"@types/mousetrap": "^1.6.15", },
"@types/react": "^18.2.46", "devDependencies": {
"@types/react-dom": "^18.2.18", "@biomejs/biome": "^1.8.3",
"@types/react-infinite-scroller": "^1.2.5", "@lingui/cli": "^4.11.2",
"@types/swagger-ui-react": "^4.18.3", "@lingui/vite-plugin": "^4.11.2",
"@types/throttle-debounce": "^5.0.2", "@types/mousetrap": "^1.6.15",
"@types/tinycon": "^0.6.5", "@types/react": "^18.3.3",
"@typescript-eslint/eslint-plugin": "^6.16.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.2.1", "@types/react-helmet": "^6.1.11",
"babel-plugin-macros": "^3.1.0", "@types/react-infinite-scroller": "^1.2.5",
"eslint": "^8.56.0", "@types/swagger-ui-react": "^4.18.3",
"eslint-config-prettier": "^9.1.0", "@types/throttle-debounce": "^5.0.2",
"eslint-config-standard-with-typescript": "^43.0.0", "@types/tinycon": "^0.6.5",
"eslint-plugin-import": "^2.29.1", "@vitejs/plugin-react": "^4.3.1",
"eslint-plugin-n": "^16.5.0", "babel-plugin-macros": "^3.1.0",
"eslint-plugin-prettier": "^5.1.2", "rollup-plugin-visualizer": "^5.12.0",
"eslint-plugin-promise": "^6.1.1", "typescript": "^5.5.3",
"eslint-plugin-react": "^7.33.2", "vite": "^5.3.3",
"eslint-plugin-react-hooks": "^4.6.0", "vite-tsconfig-paths": "^4.3.2",
"prettier": "^3.1.1", "vitest": "^1.6.0",
"rollup-plugin-visualizer": "^5.12.0", "vitest-mock-extended": "^1.3.1"
"typescript": "^5.3.3", }
"vite": "^5.0.12",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.3",
"vitest": "^1.1.3",
"vitest-mock-extended": "^1.3.1"
}
} }

View File

@@ -1,15 +1,23 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>4.2.1</version> <version>4.5.0</version>
</parent> </parent>
<artifactId>commafeed-client</artifactId> <artifactId>commafeed-client</artifactId>
<name>CommaFeed Client</name> <name>CommaFeed Client</name>
<properties>
<!-- renovate: datasource=node-version depName=node -->
<node.version>v20.15.0</node.version>
<!-- renovate: datasource=npm depName=npm -->
<npm.version>10.8.1</npm.version>
</properties>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@@ -25,8 +33,8 @@
</goals> </goals>
<phase>compile</phase> <phase>compile</phase>
<configuration> <configuration>
<nodeVersion>v20.10.0</nodeVersion> <nodeVersion>${node.version}</nodeVersion>
<npmVersion>10.2.5</npmVersion> <npmVersion>${npm.version}</npmVersion>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>

View File

@@ -1,7 +1,6 @@
import { i18n } from "@lingui/core" import { i18n } from "@lingui/core"
import { I18nProvider } from "@lingui/react" import { I18nProvider } from "@lingui/react"
import { MantineProvider } from "@mantine/core" import { MantineProvider } from "@mantine/core"
import { useDidUpdate } from "@mantine/hooks"
import { ModalsProvider } from "@mantine/modals" import { ModalsProvider } from "@mantine/modals"
import { Notifications } from "@mantine/notifications" import { Notifications } from "@mantine/notifications"
import { Constants } from "app/constants" import { Constants } from "app/constants"
@@ -9,11 +8,13 @@ import { redirectTo } from "app/redirect/slice"
import { reloadServerInfos } from "app/server/thunks" import { reloadServerInfos } from "app/server/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { categoryUnreadCount } from "app/utils" import { categoryUnreadCount } from "app/utils"
import { DisablePullToRefresh } from "components/DisablePullToRefresh"
import { ErrorBoundary } from "components/ErrorBoundary" import { ErrorBoundary } from "components/ErrorBoundary"
import { Header } from "components/header/Header" import { Header } from "components/header/Header"
import { Tree } from "components/sidebar/Tree" import { Tree } from "components/sidebar/Tree"
import { useBrowserExtension } from "hooks/useBrowserExtension" import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useI18n } from "i18n" import { useI18n } from "i18n"
import { WelcomePage } from "pages/WelcomePage"
import { AdminUsersPage } from "pages/admin/AdminUsersPage" import { AdminUsersPage } from "pages/admin/AdminUsersPage"
import { MetricsPage } from "pages/admin/MetricsPage" import { MetricsPage } from "pages/admin/MetricsPage"
import { AboutPage } from "pages/app/AboutPage" import { AboutPage } from "pages/app/AboutPage"
@@ -28,9 +29,10 @@ import { TagDetailsPage } from "pages/app/TagDetailsPage"
import { LoginPage } from "pages/auth/LoginPage" import { LoginPage } from "pages/auth/LoginPage"
import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage" import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage"
import { RegistrationPage } from "pages/auth/RegistrationPage" import { RegistrationPage } from "pages/auth/RegistrationPage"
import { WelcomePage } from "pages/WelcomePage" import React, { useEffect } from "react"
import React, { useEffect, useRef } from "react" import { isSafari } from "react-device-detect"
import ReactGA from "react-ga4" import ReactGA from "react-ga4"
import { Helmet } from "react-helmet"
import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom" import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
import Tinycon from "tinycon" import Tinycon from "tinycon"
@@ -166,38 +168,13 @@ function BrowserExtensionBadgeUnreadCountHandler() {
return null return null
} }
function CustomJs() { function CustomCode() {
const scriptLoaded = useRef(false) return (
<Helmet>
// useDidUpdate is used instead of useEffect because we want to skip the first render <link rel="stylesheet" type="text/css" href="custom_css.css" />
// the first render is the render of react-router, the routes are actually loaded in a second render <script type="text/javascript" src="custom_js.js" />
// we want the script to be executed when the first route is done loading </Helmet>
useDidUpdate(() => { )
if (scriptLoaded.current) {
return
}
const script = document.createElement("script")
script.src = "custom_js.js"
script.async = true
document.body.appendChild(script)
scriptLoaded.current = true
})
return null
}
function CustomCss() {
useEffect(() => {
const link = document.createElement("link")
link.rel = "stylesheet"
link.type = "text/css"
link.href = "custom_css.css"
document.head.appendChild(link)
}, [])
return null
} }
export function App() { export function App() {
@@ -217,8 +194,12 @@ export function App() {
<GoogleAnalyticsHandler /> <GoogleAnalyticsHandler />
<RedirectHandler /> <RedirectHandler />
<AppRoutes /> <AppRoutes />
<CustomJs /> <CustomCode />
<CustomCss /> {/* disable pull-to-refresh as it messes with vertical scrolling
safari behaves weirdly when overscroll-behavior is set to none so we disable it only for other browsers
https://github.com/Athou/commafeed/issues/1168
*/}
{!isSafari && <DisablePullToRefresh />}
</HashRouter> </HashRouter>
</> </>
</Providers> </Providers>

View File

@@ -1,7 +1,7 @@
import { createAsyncThunk } from "@reduxjs/toolkit" import { createAsyncThunk } from "@reduxjs/toolkit"
import { type AppDispatch, type RootState } from "app/store" import type { AppDispatch, RootState } from "app/store"
export const createAppAsyncThunk = createAsyncThunk.withTypes<{ export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState state: RootState
dispatch: AppDispatch dispatch: AppDispatch
}>() }>()

View File

@@ -1,120 +1,127 @@
import axios from "axios" import axios, { type AxiosError } from "axios"
import { import type {
type AddCategoryRequest, AddCategoryRequest,
type AdminSaveUserRequest, AdminSaveUserRequest,
type Category, AuthenticationError,
type CategoryModificationRequest, Category,
type CollapseRequest, CategoryModificationRequest,
type Entries, CollapseRequest,
type FeedInfo, Entries,
type FeedInfoRequest, FeedInfo,
type FeedModificationRequest, FeedInfoRequest,
type GetEntriesPaginatedRequest, FeedModificationRequest,
type IDRequest, GetEntriesPaginatedRequest,
type LoginRequest, IDRequest,
type MarkRequest, LoginRequest,
type Metrics, MarkRequest,
type MultipleMarkRequest, Metrics,
type PasswordResetRequest, MultipleMarkRequest,
type ProfileModificationRequest, PasswordResetRequest,
type RegistrationRequest, ProfileModificationRequest,
type ServerInfo, RegistrationRequest,
type Settings, ServerInfo,
type StarRequest, Settings,
type SubscribeRequest, StarRequest,
type Subscription, SubscribeRequest,
type TagRequest, Subscription,
type UserModel, TagRequest,
} from "./types" UserModel,
} from "./types"
const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })
axiosInstance.interceptors.response.use( const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })
response => response, axiosInstance.interceptors.response.use(
error => { response => response,
const { status, data } = error.response error => {
if ( if (isAuthenticationError(error)) {
(status === 401 && data?.message === "Credentials are required to access this resource.") || const data = error.response?.data
(status === 403 && data?.message === "You don't have the required role to access this resource.") window.location.hash = data?.allowRegistrations ? "/welcome" : "/login"
) { }
window.location.hash = data?.allowRegistrations ? "/welcome" : "/login" 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 = { }
category: {
getRoot: async () => await axiosInstance.get<Category>("category/get"), export const client = {
modify: async (req: CategoryModificationRequest) => await axiosInstance.post("category/modify", req), category: {
collapse: async (req: CollapseRequest) => await axiosInstance.post("category/collapse", req), getRoot: async () => await axiosInstance.get<Category>("category/get"),
getEntries: async (req: GetEntriesPaginatedRequest) => await axiosInstance.get<Entries>("category/entries", { params: req }), modify: async (req: CategoryModificationRequest) => await axiosInstance.post("category/modify", req),
markEntries: async (req: MarkRequest) => await axiosInstance.post("category/mark", req), collapse: async (req: CollapseRequest) => await axiosInstance.post("category/collapse", req),
add: async (req: AddCategoryRequest) => await axiosInstance.post("category/add", req), getEntries: async (req: GetEntriesPaginatedRequest) => await axiosInstance.get<Entries>("category/entries", { params: req }),
delete: async (req: IDRequest) => await axiosInstance.post("category/delete", req), markEntries: async (req: MarkRequest) => await axiosInstance.post("category/mark", req),
}, add: async (req: AddCategoryRequest) => await axiosInstance.post("category/add", req),
entry: { delete: async (req: IDRequest) => await axiosInstance.post("category/delete", req),
mark: async (req: MarkRequest) => await axiosInstance.post("entry/mark", req), },
markMultiple: async (req: MultipleMarkRequest) => await axiosInstance.post("entry/markMultiple", req), entry: {
star: async (req: StarRequest) => await axiosInstance.post("entry/star", req), mark: async (req: MarkRequest) => await axiosInstance.post("entry/mark", req),
getTags: async () => await axiosInstance.get<string[]>("entry/tags"), markMultiple: async (req: MultipleMarkRequest) => await axiosInstance.post("entry/markMultiple", req),
tag: async (req: TagRequest) => await axiosInstance.post("entry/tag", req), star: async (req: StarRequest) => await axiosInstance.post("entry/star", req),
}, getTags: async () => await axiosInstance.get<string[]>("entry/tags"),
feed: { tag: async (req: TagRequest) => await axiosInstance.post("entry/tag", req),
get: async (id: string) => await axiosInstance.get<Subscription>(`feed/get/${id}`), },
modify: async (req: FeedModificationRequest) => await axiosInstance.post("feed/modify", req), feed: {
getEntries: async (req: GetEntriesPaginatedRequest) => await axiosInstance.get<Entries>("feed/entries", { params: req }), get: async (id: string) => await axiosInstance.get<Subscription>(`feed/get/${id}`),
markEntries: async (req: MarkRequest) => await axiosInstance.post("feed/mark", req), modify: async (req: FeedModificationRequest) => await axiosInstance.post("feed/modify", req),
fetchFeed: async (req: FeedInfoRequest) => await axiosInstance.post<FeedInfo>("feed/fetch", req), getEntries: async (req: GetEntriesPaginatedRequest) => await axiosInstance.get<Entries>("feed/entries", { params: req }),
refreshAll: async () => await axiosInstance.get("feed/refreshAll"), markEntries: async (req: MarkRequest) => await axiosInstance.post("feed/mark", req),
subscribe: async (req: SubscribeRequest) => await axiosInstance.post<number>("feed/subscribe", req), fetchFeed: async (req: FeedInfoRequest) => await axiosInstance.post<FeedInfo>("feed/fetch", req),
unsubscribe: async (req: IDRequest) => await axiosInstance.post("feed/unsubscribe", req), refreshAll: async () => await axiosInstance.get("feed/refreshAll"),
importOpml: async (req: File) => { subscribe: async (req: SubscribeRequest) => await axiosInstance.post<number>("feed/subscribe", req),
const formData = new FormData() unsubscribe: async (req: IDRequest) => await axiosInstance.post("feed/unsubscribe", req),
formData.append("file", req) importOpml: async (req: File) => {
return await axiosInstance.post("feed/import", formData, { const formData = new FormData()
headers: { formData.append("file", req)
"Content-Type": "multipart/form-data", return await axiosInstance.post("feed/import", formData, {
}, headers: {
}) "Content-Type": "multipart/form-data",
}, },
}, })
user: { },
login: async (req: LoginRequest) => await axiosInstance.post("user/login", req), },
register: async (req: RegistrationRequest) => await axiosInstance.post("user/register", req), user: {
passwordReset: async (req: PasswordResetRequest) => await axiosInstance.post("user/passwordReset", req), login: async (req: LoginRequest) => await axiosInstance.post("user/login", req),
getSettings: async () => await axiosInstance.get<Settings>("user/settings"), register: async (req: RegistrationRequest) => await axiosInstance.post("user/register", req),
saveSettings: async (settings: Settings) => await axiosInstance.post("user/settings", settings), passwordReset: async (req: PasswordResetRequest) => await axiosInstance.post("user/passwordReset", req),
getProfile: async () => await axiosInstance.get<UserModel>("user/profile"), getSettings: async () => await axiosInstance.get<Settings>("user/settings"),
saveProfile: async (req: ProfileModificationRequest) => await axiosInstance.post("user/profile", req), saveSettings: async (settings: Settings) => await axiosInstance.post("user/settings", settings),
deleteProfile: async () => await axiosInstance.post("user/profile/deleteAccount"), getProfile: async () => await axiosInstance.get<UserModel>("user/profile"),
}, saveProfile: async (req: ProfileModificationRequest) => await axiosInstance.post("user/profile", req),
server: { deleteProfile: async () => await axiosInstance.post("user/profile/deleteAccount"),
getServerInfos: async () => await axiosInstance.get<ServerInfo>("server/get"), },
}, server: {
admin: { getServerInfos: async () => await axiosInstance.get<ServerInfo>("server/get"),
getAllUsers: async () => await axiosInstance.get<UserModel[]>("admin/user/getAll"), },
saveUser: async (req: AdminSaveUserRequest) => await axiosInstance.post("admin/user/save", req), admin: {
deleteUser: async (req: IDRequest) => await axiosInstance.post("admin/user/delete", req), getAllUsers: async () => await axiosInstance.get<UserModel[]>("admin/user/getAll"),
getMetrics: async () => await axiosInstance.get<Metrics>("admin/metrics"), saveUser: async (req: AdminSaveUserRequest) => await axiosInstance.post("admin/user/save", req),
}, deleteUser: async (req: IDRequest) => await axiosInstance.post("admin/user/delete", req),
} getMetrics: async () => await axiosInstance.get<Metrics>("admin/metrics"),
},
/** }
* transform an error object to an array of strings that can be displayed to the user
* @param err an error object (e.g. from axios) /**
* @returns an array of messages to show the user * transform an error object to an array of strings that can be displayed to the user
*/ * @param err an error object (e.g. from axios)
export const errorToStrings = (err: unknown) => { * @returns an array of messages to show the user
let strings: string[] = [] */
export const errorToStrings = (err: unknown) => {
if (axios.isAxiosError(err)) { let strings: string[] = []
if (err.response) {
const { data } = err.response if (axios.isAxiosError(err) && err.response) {
if (typeof data === "string") strings.push(data) if (typeof err.response.data === "string") strings.push(err.response.data)
if (typeof data === "object" && data.message) strings.push(data.message as string) if (isMessageError(err)) strings.push(err.response.data.message)
if (typeof data === "object" && data.errors) strings = [...strings, ...data.errors] if (isMessageArrayError(err)) strings = [...strings, ...err.response.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

@@ -1,109 +1,112 @@
import { t } from "@lingui/macro" import { t } from "@lingui/macro"
import { type IconType } from "react-icons" import type { IconType } from "react-icons"
import { FaAt } from "react-icons/fa" import { FaAt } from "react-icons/fa"
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiTwitter } from "react-icons/si" import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiTwitter } from "react-icons/si"
import { type Category, type Entry, type SharingSettings } from "./types" import type { Category, Entry, SharingSettings } from "./types"
const categories: Record<string, Category> = { const categories: Record<string, Category> = {
all: { all: {
id: "all", id: "all",
name: t`All`, name: t`All`,
expanded: false, expanded: false,
children: [], children: [],
feeds: [], feeds: [],
position: 0, position: 0,
}, },
starred: { starred: {
id: "starred", id: "starred",
name: t`Starred`, name: t`Starred`,
expanded: false, expanded: false,
children: [], children: [],
feeds: [], feeds: [],
position: 1, position: 1,
}, },
} }
const sharing: { const sharing: {
[key in keyof SharingSettings]: { [key in keyof SharingSettings]: {
label: string label: string
icon: IconType icon: IconType
color: `#${string}` color: `#${string}`
url: (url: string, description: string) => string url: (url: string, description: string) => string
} }
} = { } = {
email: { email: {
label: "Email", label: "Email",
icon: FaAt, icon: FaAt,
color: "#000000", color: "#000000",
url: (url, desc) => `mailto:?subject=${desc}&body=${url}`, url: (url, desc) => `mailto:?subject=${desc}&body=${url}`,
}, },
gmail: { gmail: {
label: "Gmail", label: "Gmail",
icon: SiGmail, icon: SiGmail,
color: "#EA4335", color: "#EA4335",
url: (url, desc) => `https://mail.google.com/mail/?view=cm&fs=1&tf=1&source=mailto&su=${desc}&body=${url}`, url: (url, desc) => `https://mail.google.com/mail/?view=cm&fs=1&tf=1&source=mailto&su=${desc}&body=${url}`,
}, },
facebook: { facebook: {
label: "Facebook", label: "Facebook",
icon: SiFacebook, icon: SiFacebook,
color: "#1B74E4", color: "#1B74E4",
url: url => `https://www.facebook.com/sharer/sharer.php?u=${url}`, url: url => `https://www.facebook.com/sharer/sharer.php?u=${url}`,
}, },
twitter: { twitter: {
label: "Twitter", label: "Twitter",
icon: SiTwitter, icon: SiTwitter,
color: "#1D9BF0", color: "#1D9BF0",
url: (url, desc) => `https://twitter.com/share?text=${desc}&url=${url}`, url: (url, desc) => `https://twitter.com/share?text=${desc}&url=${url}`,
}, },
tumblr: { tumblr: {
label: "Tumblr", label: "Tumblr",
icon: SiTumblr, icon: SiTumblr,
color: "#375672", color: "#375672",
url: (url, desc) => `https://www.tumblr.com/share/link?url=${url}&name=${desc}`, url: (url, desc) => `https://www.tumblr.com/share/link?url=${url}&name=${desc}`,
}, },
pocket: { pocket: {
label: "Pocket", label: "Pocket",
icon: SiPocket, icon: SiPocket,
color: "#EF4154", color: "#EF4154",
url: (url, desc) => `https://getpocket.com/save?url=${url}&title=${desc}`, url: (url, desc) => `https://getpocket.com/save?url=${url}&title=${desc}`,
}, },
instapaper: { instapaper: {
label: "Instapaper", label: "Instapaper",
icon: SiInstapaper, icon: SiInstapaper,
color: "#010101", color: "#010101",
url: (url, desc) => `https://www.instapaper.com/hello2?url=${url}&title=${desc}`, url: (url, desc) => `https://www.instapaper.com/hello2?url=${url}&title=${desc}`,
}, },
buffer: { buffer: {
label: "Buffer", label: "Buffer",
icon: SiBuffer, icon: SiBuffer,
color: "#000000", color: "#000000",
url: (url, desc) => `https://bufferapp.com/add?url=${url}&text=${desc}`, url: (url, desc) => `https://bufferapp.com/add?url=${url}&text=${desc}`,
}, },
} }
export const Constants = { export const Constants = {
categories, categories,
sharing, sharing,
layout: { layout: {
mobileBreakpoint: 992, mobileBreakpoint: 992,
mobileBreakpointName: "md", mobileBreakpointName: "md",
headerHeight: 60, headerHeight: 60,
entryMaxWidth: 650, entryMaxWidth: 650,
isTopVisible: (div: HTMLElement) => { isTopVisible: (div: HTMLElement) => {
const header = document.getElementById(Constants.dom.headerId)?.getBoundingClientRect() const header = document.getElementById(Constants.dom.headerId)?.getBoundingClientRect()
return div.getBoundingClientRect().top >= (header?.bottom ?? 0) return div.getBoundingClientRect().top >= (header?.bottom ?? 0)
}, },
isBottomVisible: (div: HTMLElement) => { isBottomVisible: (div: HTMLElement) => {
const footer = document.getElementById(Constants.dom.footerId)?.getBoundingClientRect() const footer = document.getElementById(Constants.dom.footerId)?.getBoundingClientRect()
return div.getBoundingClientRect().bottom <= (footer?.top ?? window.innerHeight) return div.getBoundingClientRect().bottom <= (footer?.top ?? window.innerHeight)
}, },
}, },
dom: { dom: {
headerId: "header", headerId: "header",
footerId: "footer", 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,
}, },
browserExtensionUrl: "https://github.com/Athou/commafeed-browser-extension", tooltip: {
bitcoinWalletAddress: "1dymfUxqCWpyD7a6rQSqNy4rLVDBsAr5e", delay: 500,
} },
browserExtensionUrl: "https://github.com/Athou/commafeed-browser-extension",
bitcoinWalletAddress: "1dymfUxqCWpyD7a6rQSqNy4rLVDBsAr5e",
}

View File

@@ -1,146 +1,145 @@
/* 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" import { type RootState, reducers } from "app/store"
import { reducers, type RootState } from "app/store" import type { Entries, Entry } from "app/types"
import { type Entries, type Entry } from "app/types" import type { AxiosResponse } from "axios"
import { type AxiosResponse } from "axios" import { beforeEach, describe, expect, it, vi } from "vitest"
import { beforeEach, describe, expect, it, vi } from "vitest" import { mockReset } from "vitest-mock-extended"
import { mockReset } from "vitest-mock-extended"
const mockClient = await vi.hoisted(async () => {
const mockClient = await vi.hoisted(async () => { const mockModule = await import("vitest-mock-extended")
const mockModule = await import("vitest-mock-extended") return mockModule.mockDeep<typeof client>()
return mockModule.mockDeep<typeof client>() })
}) vi.mock("app/client", () => ({ client: mockClient }))
vi.mock("app/client", () => ({ client: mockClient }))
describe("entries", () => {
describe("entries", () => { beforeEach(() => {
beforeEach(() => { mockReset(mockClient)
mockReset(mockClient) })
})
it("loads entries", async () => {
it("loads entries", async () => { mockClient.feed.getEntries.mockResolvedValue({
mockClient.feed.getEntries.mockResolvedValue({ data: {
data: { entries: [{ id: "3" } as Entry],
entries: [{ id: "3" } as Entry], hasMore: false,
hasMore: false, name: "my-feed",
name: "my-feed", errorCount: 3,
errorCount: 3, feedLink: "https://mysite.com/feed",
feedLink: "https://mysite.com/feed", timestamp: 123,
timestamp: 123, ignoredReadStatus: false,
ignoredReadStatus: false, },
}, } as AxiosResponse<Entries>)
} as AxiosResponse<Entries>)
const store = configureStore({ reducer: reducers })
const store = configureStore({ reducer: reducers }) const promise = store.dispatch(loadEntries({ source: { type: "feed", id: "feed-id" }, clearSearch: true }))
const promise = store.dispatch(loadEntries({ source: { type: "feed", id: "feed-id" }, clearSearch: true }))
expect(store.getState().entries.source.type).toBe("feed")
expect(store.getState().entries.source.type).toBe("feed") expect(store.getState().entries.source.id).toBe("feed-id")
expect(store.getState().entries.source.id).toBe("feed-id") expect(store.getState().entries.entries).toStrictEqual([])
expect(store.getState().entries.entries).toStrictEqual([]) expect(store.getState().entries.hasMore).toBe(true)
expect(store.getState().entries.hasMore).toBe(true) expect(store.getState().entries.sourceLabel).toBe("")
expect(store.getState().entries.sourceLabel).toBe("") expect(store.getState().entries.sourceWebsiteUrl).toBe("")
expect(store.getState().entries.sourceWebsiteUrl).toBe("") expect(store.getState().entries.timestamp).toBeUndefined()
expect(store.getState().entries.timestamp).toBeUndefined()
await promise
await promise expect(store.getState().entries.source.type).toBe("feed")
expect(store.getState().entries.source.type).toBe("feed") expect(store.getState().entries.source.id).toBe("feed-id")
expect(store.getState().entries.source.id).toBe("feed-id") expect(store.getState().entries.entries).toStrictEqual([{ id: "3" }])
expect(store.getState().entries.entries).toStrictEqual([{ id: "3" }]) expect(store.getState().entries.hasMore).toBe(false)
expect(store.getState().entries.hasMore).toBe(false) expect(store.getState().entries.sourceLabel).toBe("my-feed")
expect(store.getState().entries.sourceLabel).toBe("my-feed") expect(store.getState().entries.sourceWebsiteUrl).toBe("https://mysite.com/feed")
expect(store.getState().entries.sourceWebsiteUrl).toBe("https://mysite.com/feed") expect(store.getState().entries.timestamp).toBe(123)
expect(store.getState().entries.timestamp).toBe(123) })
})
it("loads more entries", async () => {
it("loads more entries", async () => { mockClient.category.getEntries.mockResolvedValue({
mockClient.category.getEntries.mockResolvedValue({ data: {
data: { entries: [{ id: "4" } as Entry],
entries: [{ id: "4" } as Entry], hasMore: false,
hasMore: false, name: "my-feed",
name: "my-feed", errorCount: 3,
errorCount: 3, feedLink: "https://mysite.com/feed",
feedLink: "https://mysite.com/feed", timestamp: 123,
timestamp: 123, ignoredReadStatus: false,
ignoredReadStatus: false, },
}, } as AxiosResponse<Entries>)
} as AxiosResponse<Entries>)
const store = configureStore({
const store = configureStore({ reducer: reducers,
reducer: reducers, preloadedState: {
preloadedState: { entries: {
entries: { source: {
source: { type: "category",
type: "category", id: "category-id",
id: "category-id", },
}, sourceLabel: "",
sourceLabel: "", sourceWebsiteUrl: "",
sourceWebsiteUrl: "", entries: [{ id: "3" } as Entry],
entries: [{ id: "3" } as Entry], hasMore: true,
hasMore: true, loading: false,
loading: false, scrollingToEntry: false,
scrollingToEntry: false, },
}, } as RootState,
} as RootState, })
}) const promise = store.dispatch(loadMoreEntries())
const promise = store.dispatch(loadMoreEntries())
await promise
await promise expect(store.getState().entries.entries).toStrictEqual([{ id: "3" }, { id: "4" }])
expect(store.getState().entries.entries).toStrictEqual([{ id: "3" }, { id: "4" }]) expect(store.getState().entries.hasMore).toBe(false)
expect(store.getState().entries.hasMore).toBe(false) })
})
it("marks an entry as read", () => {
it("marks an entry as read", async () => { const store = configureStore({
const store = configureStore({ reducer: reducers,
reducer: reducers, preloadedState: {
preloadedState: { entries: {
entries: { source: {
source: { type: "category",
type: "category", id: "category-id",
id: "category-id", },
}, sourceLabel: "",
sourceLabel: "", sourceWebsiteUrl: "",
sourceWebsiteUrl: "", entries: [{ id: "3", read: false } as Entry, { id: "4", read: false } as Entry],
entries: [{ id: "3", read: false } as Entry, { id: "4", read: false } as Entry], hasMore: true,
hasMore: true, loading: false,
loading: false, scrollingToEntry: false,
scrollingToEntry: false, },
}, } as RootState,
} as RootState, })
})
store.dispatch(markEntry({ entry: { id: "3" } as Entry, read: true }))
store.dispatch(markEntry({ entry: { id: "3" } as Entry, read: true })) expect(store.getState().entries.entries).toStrictEqual([
expect(store.getState().entries.entries).toStrictEqual([ { id: "3", read: true },
{ id: "3", read: true }, { id: "4", read: false },
{ id: "4", read: false }, ])
]) expect(mockClient.entry.mark).toHaveBeenCalledWith({ id: "3", read: true })
expect(mockClient.entry.mark).toHaveBeenCalledWith({ id: "3", read: true }) })
})
it("marks all entries as read", () => {
it("marks all entries as read", async () => { const store = configureStore({
const store = configureStore({ reducer: reducers,
reducer: reducers, preloadedState: {
preloadedState: { entries: {
entries: { source: {
source: { type: "category",
type: "category", id: "category-id",
id: "category-id", },
}, sourceLabel: "",
sourceLabel: "", sourceWebsiteUrl: "",
sourceWebsiteUrl: "", entries: [{ id: "3", read: false } as Entry, { id: "4", read: false } as Entry],
entries: [{ id: "3", read: false } as Entry, { id: "4", read: false } as Entry], hasMore: true,
hasMore: true, loading: false,
loading: false, scrollingToEntry: false,
scrollingToEntry: false, },
}, } as RootState,
} as RootState, })
})
store.dispatch(markAllEntries({ sourceType: "category", req: { id: "all", read: true } }))
store.dispatch(markAllEntries({ sourceType: "category", req: { id: "all", read: true } })) expect(store.getState().entries.entries).toStrictEqual([
expect(store.getState().entries.entries).toStrictEqual([ { id: "3", read: true },
{ id: "3", read: true }, { id: "4", read: true },
{ id: "4", read: true }, ])
]) expect(mockClient.category.markEntries).toHaveBeenCalledWith({ id: "all", read: true })
expect(mockClient.category.markEntries).toHaveBeenCalledWith({ id: "all", read: true }) })
}) })
})

View File

@@ -1,134 +1,122 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit" import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { loadEntries, loadMoreEntries, markAllEntries, markEntry, markMultipleEntries, starEntry, tagEntry } from "app/entries/thunks" import { loadEntries, loadMoreEntries, markAllEntries, markEntry, markMultipleEntries, starEntry, tagEntry } from "app/entries/thunks"
import { type Entry } from "app/types" import type { Entry } from "app/types"
export type EntrySourceType = "category" | "feed" | "tag" export type EntrySourceType = "category" | "feed" | "tag"
export interface EntrySource { export interface EntrySource {
type: EntrySourceType type: EntrySourceType
id: string id: string
} }
export type ExpendableEntry = Entry & { expanded?: boolean } export type ExpendableEntry = Entry & { expanded?: boolean }
interface EntriesState { interface EntriesState {
/** selected source */ /** selected source */
source: EntrySource source: EntrySource
sourceLabel: string sourceLabel: string
sourceWebsiteUrl: string sourceWebsiteUrl: string
entries: ExpendableEntry[] entries: ExpendableEntry[]
/** stores when the first batch of entries were retrieved /** stores when the first batch of entries were retrieved
* *
* this is used when marking all entries of a feed/category to only mark entries up to that timestamp as newer entries were potentially never shown * this is used when marking all entries of a feed/category to only mark entries up to that timestamp as newer entries were potentially never shown
*/ */
timestamp?: number timestamp?: number
selectedEntryId?: string selectedEntryId?: string
hasMore: boolean hasMore: boolean
loading: boolean loading: boolean
search?: string search?: string
scrollingToEntry: boolean scrollingToEntry: boolean
} }
const initialState: EntriesState = { const initialState: EntriesState = {
source: { source: {
type: "category", type: "category",
id: Constants.categories.all.id, id: Constants.categories.all.id,
}, },
sourceLabel: "", sourceLabel: "",
sourceWebsiteUrl: "", sourceWebsiteUrl: "",
entries: [], entries: [],
hasMore: true, hasMore: true,
loading: false, loading: false,
scrollingToEntry: false, scrollingToEntry: false,
} }
export const entriesSlice = createSlice({ export const entriesSlice = createSlice({
name: "entries", name: "entries",
initialState, initialState,
reducers: { reducers: {
setSelectedEntry: (state, action: PayloadAction<Entry>) => { setSelectedEntry: (state, action: PayloadAction<Entry>) => {
state.selectedEntryId = action.payload.id state.selectedEntryId = action.payload.id
}, },
setEntryExpanded: (state, action: PayloadAction<{ entry: Entry; expanded: boolean }>) => { setEntryExpanded: (state, action: PayloadAction<{ entry: Entry; expanded: boolean }>) => {
state.entries for (const e of state.entries.filter(e => e.id === action.payload.entry.id)) {
.filter(e => e.id === action.payload.entry.id) e.expanded = action.payload.expanded
.forEach(e => { }
e.expanded = action.payload.expanded },
}) setScrollingToEntry: (state, action: PayloadAction<boolean>) => {
}, state.scrollingToEntry = action.payload
setScrollingToEntry: (state, action: PayloadAction<boolean>) => { },
state.scrollingToEntry = action.payload setSearch: (state, action: PayloadAction<string>) => {
}, state.search = action.payload
setSearch: (state, action: PayloadAction<string>) => { },
state.search = action.payload },
}, extraReducers: builder => {
}, builder.addCase(markEntry.pending, (state, action) => {
extraReducers: builder => { for (const e of state.entries.filter(e => e.id === action.meta.arg.entry.id)) {
builder.addCase(markEntry.pending, (state, action) => { e.read = action.meta.arg.read
state.entries }
.filter(e => e.id === action.meta.arg.entry.id) })
.forEach(e => { builder.addCase(markMultipleEntries.pending, (state, action) => {
e.read = action.meta.arg.read for (const e of state.entries.filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id))) {
}) e.read = action.meta.arg.read
}) }
builder.addCase(markMultipleEntries.pending, (state, action) => { })
state.entries builder.addCase(markAllEntries.pending, (state, action) => {
.filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id)) for (const e of state.entries.filter(e => (action.meta.arg.req.olderThan ? e.date < action.meta.arg.req.olderThan : true))) {
.forEach(e => { e.read = true
e.read = action.meta.arg.read }
}) })
}) builder.addCase(starEntry.pending, (state, action) => {
builder.addCase(markAllEntries.pending, (state, action) => { for (const e of state.entries.filter(e => action.meta.arg.entry.id === e.id && action.meta.arg.entry.feedId === e.feedId)) {
state.entries e.starred = action.meta.arg.starred
.filter(e => (action.meta.arg.req.olderThan ? e.date < action.meta.arg.req.olderThan : true)) }
.forEach(e => { })
e.read = true builder.addCase(loadEntries.pending, (state, action) => {
}) state.source = action.meta.arg.source
}) state.entries = []
builder.addCase(starEntry.pending, (state, action) => { state.timestamp = undefined
state.entries state.sourceLabel = ""
.filter(e => action.meta.arg.entry.id === e.id && action.meta.arg.entry.feedId === e.feedId) state.sourceWebsiteUrl = ""
.forEach(e => { state.hasMore = true
e.starred = action.meta.arg.starred state.selectedEntryId = undefined
}) state.loading = true
}) })
builder.addCase(loadEntries.pending, (state, action) => { builder.addCase(loadMoreEntries.pending, state => {
state.source = action.meta.arg.source state.loading = true
state.entries = [] })
state.timestamp = undefined builder.addCase(loadEntries.fulfilled, (state, action) => {
state.sourceLabel = "" state.entries = action.payload.entries
state.sourceWebsiteUrl = "" state.timestamp = action.payload.timestamp
state.hasMore = true state.sourceLabel = action.payload.name
state.selectedEntryId = undefined state.sourceWebsiteUrl = action.payload.feedLink
state.loading = true state.hasMore = action.payload.hasMore
}) state.loading = false
builder.addCase(loadMoreEntries.pending, state => { })
state.loading = true builder.addCase(loadMoreEntries.fulfilled, (state, action) => {
}) // remove already existing entries
builder.addCase(loadEntries.fulfilled, (state, action) => { const entriesToAdd = action.payload.entries.filter(e => !state.entries.some(e2 => e.id === e2.id))
state.entries = action.payload.entries state.entries = [...state.entries, ...entriesToAdd]
state.timestamp = action.payload.timestamp state.hasMore = action.payload.hasMore
state.sourceLabel = action.payload.name state.loading = false
state.sourceWebsiteUrl = action.payload.feedLink })
state.hasMore = action.payload.hasMore builder.addCase(tagEntry.pending, (state, action) => {
state.loading = false for (const e of state.entries.filter(e => +e.id === action.meta.arg.entryId)) {
}) e.tags = action.meta.arg.tags
builder.addCase(loadMoreEntries.fulfilled, (state, action) => { }
// remove already existing entries })
const entriesToAdd = action.payload.entries.filter(e => !state.entries.some(e2 => e.id === e2.id)) },
state.entries = [...state.entries, ...entriesToAdd] })
state.hasMore = action.payload.hasMore
state.loading = false export const { setSearch } = entriesSlice.actions
})
builder.addCase(tagEntry.pending, (state, action) => {
state.entries
.filter(e => +e.id === action.meta.arg.entryId)
.forEach(e => {
e.tags = action.meta.arg.tags
})
})
},
})
export const { setSearch } = entriesSlice.actions

View File

@@ -1,241 +1,247 @@
import { createAppAsyncThunk } from "app/async-thunk" import { createAppAsyncThunk } from "app/async-thunk"
import { client } from "app/client" import { client } from "app/client"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { entriesSlice, type EntrySource, type EntrySourceType, setSearch } from "app/entries/slice" import { type EntrySource, type EntrySourceType, entriesSlice, setSearch } from "app/entries/slice"
import type { RootState } from "app/store" import type { RootState } from "app/store"
import { reloadTree } from "app/tree/thunks" import { reloadTree } from "app/tree/thunks"
import type { Entry, MarkRequest, TagRequest } from "app/types" import type { Entry, MarkRequest, TagRequest } from "app/types"
import { reloadTags } from "app/user/thunks" import { reloadTags } from "app/user/thunks"
import { scrollToWithCallback } from "app/utils" import { scrollToWithCallback } from "app/utils"
import { flushSync } from "react-dom" import { flushSync } from "react-dom"
const getEndpoint = (sourceType: EntrySourceType) => const getEndpoint = (sourceType: EntrySourceType) =>
sourceType === "category" || sourceType === "tag" ? client.category.getEntries : client.feed.getEntries sourceType === "category" || sourceType === "tag" ? client.category.getEntries : client.feed.getEntries
export const loadEntries = createAppAsyncThunk( export const loadEntries = createAppAsyncThunk(
"entries/load", "entries/load",
async ( async (
arg: { arg: {
source: EntrySource source: EntrySource
clearSearch: boolean clearSearch: boolean
}, },
thunkApi thunkApi
) => { ) => {
if (arg.clearSearch) thunkApi.dispatch(setSearch("")) if (arg.clearSearch) thunkApi.dispatch(setSearch(""))
const state = thunkApi.getState() const state = thunkApi.getState()
const endpoint = getEndpoint(arg.source.type) const endpoint = getEndpoint(arg.source.type)
const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0)) const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0))
return result.data return result.data
} }
) )
export const loadMoreEntries = createAppAsyncThunk("entries/loadMore", async (_, thunkApi) => { export const loadMoreEntries = createAppAsyncThunk("entries/loadMore", async (_, thunkApi) => {
const state = thunkApi.getState() const state = thunkApi.getState()
const { source } = state.entries const { source } = state.entries
const offset = const offset =
state.user.settings?.readingMode === "all" ? state.entries.entries.length : state.entries.entries.filter(e => !e.read).length state.user.settings?.readingMode === "all" ? state.entries.entries.length : state.entries.entries.filter(e => !e.read).length
const endpoint = getEndpoint(state.entries.source.type) const endpoint = getEndpoint(state.entries.source.type)
const result = await endpoint(buildGetEntriesPaginatedRequest(state, source, offset)) const result = await endpoint(buildGetEntriesPaginatedRequest(state, source, offset))
return result.data return result.data
}) })
const buildGetEntriesPaginatedRequest = (state: RootState, source: EntrySource, offset: number) => ({ const buildGetEntriesPaginatedRequest = (state: RootState, source: EntrySource, offset: number) => ({
id: source.type === "tag" ? Constants.categories.all.id : source.id, id: source.type === "tag" ? Constants.categories.all.id : source.id,
order: state.user.settings?.readingOrder, order: state.user.settings?.readingOrder,
readType: state.user.settings?.readingMode, readType: state.user.settings?.readingMode,
offset, offset,
limit: 50, limit: 50,
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 }))
}) })
export const markEntry = createAppAsyncThunk( export const markEntry = createAppAsyncThunk(
"entries/entry/mark", "entries/entry/mark",
(arg: { entry: Entry; read: boolean }) => { (arg: { entry: Entry; read: boolean }) => {
client.entry.mark({ client.entry.mark({
id: arg.entry.id, id: arg.entry.id,
read: arg.read, read: arg.read,
}) })
}, },
{ {
condition: arg => arg.entry.read !== arg.read, condition: arg => arg.entry.markable && arg.entry.read !== arg.read,
} }
) )
export const markMultipleEntries = createAppAsyncThunk( export const markMultipleEntries = createAppAsyncThunk(
"entries/entry/markMultiple", "entries/entry/markMultiple",
async ( async (
arg: { arg: {
entries: Entry[] entries: Entry[]
read: boolean read: boolean
}, },
thunkApi thunkApi
) => { ) => {
const requests: MarkRequest[] = arg.entries.map(e => ({ const requests: MarkRequest[] = arg.entries.map(e => ({
id: e.id, id: e.id,
read: arg.read, read: arg.read,
})) }))
await client.entry.markMultiple({ requests }) await client.entry.markMultiple({ requests })
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
const index = entries.findIndex(e => e.id === arg.id) const index = entries.findIndex(e => e.id === arg.id)
if (index === -1) return if (index === -1) return
thunkApi.dispatch( thunkApi.dispatch(
markMultipleEntries({ markMultipleEntries({
entries: entries.slice(0, index + 1), entries: entries.slice(0, index + 1),
read: true, read: true,
}) })
) )
}) })
export const markAllEntries = createAppAsyncThunk( export const markAllEntries = createAppAsyncThunk(
"entries/entry/markAll", "entries/entry/markAll",
async ( async (
arg: { arg: {
sourceType: EntrySourceType sourceType: EntrySourceType
req: MarkRequest req: MarkRequest
}, },
thunkApi thunkApi
) => { ) => {
const endpoint = arg.sourceType === "category" ? client.category.markEntries : client.feed.markEntries const endpoint = arg.sourceType === "category" ? client.category.markEntries : client.feed.markEntries
await endpoint(arg.req) await endpoint(arg.req)
thunkApi.dispatch(reloadEntries()) thunkApi.dispatch(reloadEntries())
thunkApi.dispatch(reloadTree()) thunkApi.dispatch(reloadTree())
} }
) )
export const starEntry = createAppAsyncThunk("entries/entry/star", (arg: { entry: Entry; starred: boolean }) => { export const starEntry = createAppAsyncThunk(
client.entry.star({ "entries/entry/star",
id: arg.entry.id, (arg: { entry: Entry; starred: boolean }) => {
feedId: +arg.entry.feedId, client.entry.star({
starred: arg.starred, id: arg.entry.id,
}) feedId: +arg.entry.feedId,
}) starred: arg.starred,
export const selectEntry = createAppAsyncThunk( })
"entries/entry/select", },
( {
arg: { condition: arg => arg.entry.markable && arg.entry.starred !== arg.starred,
entry: Entry }
expand: boolean )
markAsRead: boolean export const selectEntry = createAppAsyncThunk(
scrollToEntry: boolean "entries/entry/select",
}, (
thunkApi arg: {
) => { entry: Entry
const state = thunkApi.getState() expand: boolean
const entry = state.entries.entries.find(e => e.id === arg.entry.id) markAsRead: boolean
if (!entry) return scrollToEntry: boolean
},
// flushSync is required because we need the newly selected entry to be expanded thunkApi
// and the previously selected entry to be collapsed to be able to scroll to the right position ) => {
flushSync(() => { const state = thunkApi.getState()
// mark as read if requested const entry = state.entries.entries.find(e => e.id === arg.entry.id)
if (arg.markAsRead) { if (!entry) return
thunkApi.dispatch(markEntry({ entry, read: true }))
} // flushSync is required because we need the newly selected entry to be expanded
// and the previously selected entry to be collapsed to be able to scroll to the right position
// set entry as selected flushSync(() => {
thunkApi.dispatch(entriesSlice.actions.setSelectedEntry(entry)) // mark as read if requested
if (arg.markAsRead) {
// expand if requested thunkApi.dispatch(markEntry({ entry, read: true }))
const previouslySelectedEntry = state.entries.entries.find(e => e.id === state.entries.selectedEntryId) }
if (previouslySelectedEntry) {
thunkApi.dispatch( // set entry as selected
entriesSlice.actions.setEntryExpanded({ thunkApi.dispatch(entriesSlice.actions.setSelectedEntry(entry))
entry: previouslySelectedEntry,
expanded: false, // expand if requested
}) const previouslySelectedEntry = state.entries.entries.find(e => e.id === state.entries.selectedEntryId)
) if (previouslySelectedEntry) {
} thunkApi.dispatch(
thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry, expanded: arg.expand })) entriesSlice.actions.setEntryExpanded({
}) entry: previouslySelectedEntry,
expanded: false,
if (arg.scrollToEntry) { })
const entryElement = document.getElementById(Constants.dom.entryId(entry)) )
if (entryElement) { }
const alwaysScrollToEntry = state.user.settings?.alwaysScrollToEntry thunkApi.dispatch(entriesSlice.actions.setEntryExpanded({ entry, expanded: arg.expand }))
const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement) })
if (alwaysScrollToEntry || !entryEntirelyVisible) {
const scrollSpeed = state.user.settings?.scrollSpeed if (arg.scrollToEntry) {
thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true)) const entryElement = document.getElementById(Constants.dom.entryId(entry))
scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false))) if (entryElement) {
} const scrollMode = state.user.settings?.scrollMode
} const entryEntirelyVisible = Constants.layout.isTopVisible(entryElement) && Constants.layout.isBottomVisible(entryElement)
} if (scrollMode === "always" || (scrollMode === "if_needed" && !entryEntirelyVisible)) {
} const scrollSpeed = state.user.settings?.scrollSpeed
) thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(true))
const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefined, onScrollEnded: () => void) => { scrollToEntry(entryElement, scrollSpeed, () => thunkApi.dispatch(entriesSlice.actions.setScrollingToEntry(false)))
const header = document.getElementById(Constants.dom.headerId)?.getBoundingClientRect() }
const offset = (header?.bottom ?? 0) + 3 }
scrollToWithCallback({ }
options: { }
top: entryElement.offsetTop - offset, )
behavior: scrollSpeed && scrollSpeed > 0 ? "smooth" : "auto", const scrollToEntry = (entryElement: HTMLElement, scrollSpeed: number | undefined, onScrollEnded: () => void) => {
}, const header = document.getElementById(Constants.dom.headerId)?.getBoundingClientRect()
onScrollEnded, const offset = (header?.bottom ?? 0) + 3
}) scrollToWithCallback({
} options: {
top: entryElement.offsetTop - offset,
export const selectPreviousEntry = createAppAsyncThunk( behavior: scrollSpeed && scrollSpeed > 0 ? "smooth" : "auto",
"entries/entry/selectPrevious", },
( onScrollEnded,
arg: { })
expand: boolean }
markAsRead: boolean
scrollToEntry: boolean export const selectPreviousEntry = createAppAsyncThunk(
}, "entries/entry/selectPrevious",
thunkApi (
) => { arg: {
const state = thunkApi.getState() expand: boolean
const { entries } = state.entries markAsRead: boolean
const previousIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) - 1 scrollToEntry: boolean
if (previousIndex >= 0) { },
thunkApi.dispatch( thunkApi
selectEntry({ ) => {
entry: entries[previousIndex], const state = thunkApi.getState()
expand: arg.expand, const { entries } = state.entries
markAsRead: arg.markAsRead, const previousIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) - 1
scrollToEntry: arg.scrollToEntry, if (previousIndex >= 0) {
}) thunkApi.dispatch(
) selectEntry({
} entry: entries[previousIndex],
} expand: arg.expand,
) markAsRead: arg.markAsRead,
export const selectNextEntry = createAppAsyncThunk( scrollToEntry: arg.scrollToEntry,
"entries/entry/selectNext", })
( )
arg: { }
expand: boolean }
markAsRead: boolean )
scrollToEntry: boolean export const selectNextEntry = createAppAsyncThunk(
}, "entries/entry/selectNext",
thunkApi (
) => { arg: {
const state = thunkApi.getState() expand: boolean
const { entries } = state.entries markAsRead: boolean
const nextIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) + 1 scrollToEntry: boolean
if (nextIndex < entries.length) { },
thunkApi.dispatch( thunkApi
selectEntry({ ) => {
entry: entries[nextIndex], const state = thunkApi.getState()
expand: arg.expand, const { entries } = state.entries
markAsRead: arg.markAsRead, const nextIndex = entries.findIndex(e => e.id === state.entries.selectedEntryId) + 1
scrollToEntry: arg.scrollToEntry, if (nextIndex < entries.length) {
}) thunkApi.dispatch(
) selectEntry({
} entry: entries[nextIndex],
} expand: arg.expand,
) markAsRead: arg.markAsRead,
export const tagEntry = createAppAsyncThunk("entries/entry/tag", async (arg: TagRequest, thunkApi) => { scrollToEntry: arg.scrollToEntry,
await client.entry.tag(arg) })
thunkApi.dispatch(reloadTags()) )
}) }
}
)
export const tagEntry = createAppAsyncThunk("entries/entry/tag", async (arg: TagRequest, thunkApi) => {
await client.entry.tag(arg)
thunkApi.dispatch(reloadTags())
})

View File

@@ -1,10 +1,10 @@
import { redirectToCategory } from "app/redirect/thunks" import { redirectToCategory } from "app/redirect/thunks"
import { store } from "app/store" import { store } from "app/store"
import { describe, expect, it } from "vitest" import { describe, expect, it } from "vitest"
describe("redirects", () => { describe("redirects", () => {
it("redirects to category", async () => { it("redirects to category", async () => {
await store.dispatch(redirectToCategory("1")) await store.dispatch(redirectToCategory("1"))
expect(store.getState().redirect.to).toBe("/app/category/1") expect(store.getState().redirect.to).toBe("/app/category/1")
}) })
}) })

View File

@@ -1,19 +1,19 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit" import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
interface RedirectState { interface RedirectState {
to?: string to?: string
} }
const initialState: RedirectState = {} const initialState: RedirectState = {}
export const redirectSlice = createSlice({ export const redirectSlice = createSlice({
name: "redirect", name: "redirect",
initialState, initialState,
reducers: { reducers: {
redirectTo: (state, action: PayloadAction<string | undefined>) => { redirectTo: (state, action: PayloadAction<string | undefined>) => {
state.to = action.payload state.to = action.payload
}, },
}, },
}) })
export const { redirectTo } = redirectSlice.actions export const { redirectTo } = redirectSlice.actions

View File

@@ -1,45 +1,45 @@
import { createAppAsyncThunk } from "app/async-thunk" import { createAppAsyncThunk } from "app/async-thunk"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { redirectTo } from "app/redirect/slice" import { redirectTo } from "app/redirect/slice"
export const redirectToLogin = createAppAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login"))) export const redirectToLogin = createAppAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login")))
export const redirectToRegistration = createAppAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register"))) export const redirectToRegistration = createAppAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register")))
export const redirectToPasswordRecovery = createAppAsyncThunk("redirect/passwordRecovery", (_, thunkApi) => export const redirectToPasswordRecovery = createAppAsyncThunk("redirect/passwordRecovery", (_, thunkApi) =>
thunkApi.dispatch(redirectTo("/passwordRecovery")) thunkApi.dispatch(redirectTo("/passwordRecovery"))
) )
export const redirectToApiDocumentation = createAppAsyncThunk("redirect/api", (_, thunkApi) => thunkApi.dispatch(redirectTo("/api"))) export const redirectToApiDocumentation = createAppAsyncThunk("redirect/api", (_, thunkApi) => thunkApi.dispatch(redirectTo("/api")))
export const redirectToSelectedSource = createAppAsyncThunk("redirect/selectedSource", (_, thunkApi) => { export const redirectToSelectedSource = createAppAsyncThunk("redirect/selectedSource", (_, thunkApi) => {
const { source } = thunkApi.getState().entries const { source } = thunkApi.getState().entries
thunkApi.dispatch(redirectTo(`/app/${source.type}/${source.id}`)) thunkApi.dispatch(redirectTo(`/app/${source.type}/${source.id}`))
}) })
export const redirectToCategory = createAppAsyncThunk("redirect/category", (id: string, thunkApi) => export const redirectToCategory = createAppAsyncThunk("redirect/category", (id: string, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/category/${id}`)) thunkApi.dispatch(redirectTo(`/app/category/${id}`))
) )
export const redirectToRootCategory = createAppAsyncThunk( export const redirectToRootCategory = createAppAsyncThunk(
"redirect/category/root", "redirect/category/root",
async (_, thunkApi) => await thunkApi.dispatch(redirectToCategory(Constants.categories.all.id)) async (_, thunkApi) => await thunkApi.dispatch(redirectToCategory(Constants.categories.all.id))
) )
export const redirectToCategoryDetails = createAppAsyncThunk("redirect/category/details", (id: string, thunkApi) => export const redirectToCategoryDetails = createAppAsyncThunk("redirect/category/details", (id: string, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/category/${id}/details`)) thunkApi.dispatch(redirectTo(`/app/category/${id}/details`))
) )
export const redirectToFeed = createAppAsyncThunk("redirect/feed", (id: string | number, thunkApi) => export const redirectToFeed = createAppAsyncThunk("redirect/feed", (id: string | number, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/feed/${id}`)) thunkApi.dispatch(redirectTo(`/app/feed/${id}`))
) )
export const redirectToFeedDetails = createAppAsyncThunk("redirect/feed/details", (id: string, thunkApi) => export const redirectToFeedDetails = createAppAsyncThunk("redirect/feed/details", (id: string, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/feed/${id}/details`)) thunkApi.dispatch(redirectTo(`/app/feed/${id}/details`))
) )
export const redirectToTag = createAppAsyncThunk("redirect/tag", (id: string, thunkApi) => thunkApi.dispatch(redirectTo(`/app/tag/${id}`))) export const redirectToTag = createAppAsyncThunk("redirect/tag", (id: string, thunkApi) => thunkApi.dispatch(redirectTo(`/app/tag/${id}`)))
export const redirectToTagDetails = createAppAsyncThunk("redirect/tag/details", (id: string, thunkApi) => export const redirectToTagDetails = createAppAsyncThunk("redirect/tag/details", (id: string, thunkApi) =>
thunkApi.dispatch(redirectTo(`/app/tag/${id}/details`)) thunkApi.dispatch(redirectTo(`/app/tag/${id}/details`))
) )
export const redirectToAdd = createAppAsyncThunk("redirect/add", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/add"))) export const redirectToAdd = createAppAsyncThunk("redirect/add", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/add")))
export const redirectToSettings = createAppAsyncThunk("redirect/settings", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/settings"))) export const redirectToSettings = createAppAsyncThunk("redirect/settings", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/settings")))
export const redirectToAdminUsers = createAppAsyncThunk("redirect/admin/users", (_, thunkApi) => export const redirectToAdminUsers = createAppAsyncThunk("redirect/admin/users", (_, thunkApi) =>
thunkApi.dispatch(redirectTo("/app/admin/users")) thunkApi.dispatch(redirectTo("/app/admin/users"))
) )
export const redirectToMetrics = createAppAsyncThunk("redirect/admin/metrics", (_, thunkApi) => export const redirectToMetrics = createAppAsyncThunk("redirect/admin/metrics", (_, thunkApi) =>
thunkApi.dispatch(redirectTo("/app/admin/metrics")) thunkApi.dispatch(redirectTo("/app/admin/metrics"))
) )
export const redirectToDonate = createAppAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate"))) export const redirectToDonate = createAppAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate")))
export const redirectToAbout = createAppAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about"))) export const redirectToAbout = createAppAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about")))

View File

@@ -1,29 +1,29 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit" import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
import { reloadServerInfos } from "app/server/thunks" import { reloadServerInfos } from "app/server/thunks"
import { type ServerInfo } from "app/types" import type { ServerInfo } from "app/types"
interface ServerState { interface ServerState {
serverInfos?: ServerInfo serverInfos?: ServerInfo
webSocketConnected: boolean webSocketConnected: boolean
} }
const initialState: ServerState = { const initialState: ServerState = {
webSocketConnected: false, webSocketConnected: false,
} }
export const serverSlice = createSlice({ export const serverSlice = createSlice({
name: "server", name: "server",
initialState, initialState,
reducers: { reducers: {
setWebSocketConnected: (state, action: PayloadAction<boolean>) => { setWebSocketConnected: (state, action: PayloadAction<boolean>) => {
state.webSocketConnected = action.payload state.webSocketConnected = action.payload
}, },
}, },
extraReducers: builder => { extraReducers: builder => {
builder.addCase(reloadServerInfos.fulfilled, (state, action) => { builder.addCase(reloadServerInfos.fulfilled, (state, action) => {
state.serverInfos = action.payload state.serverInfos = action.payload
}) })
}, },
}) })
export const { setWebSocketConnected } = serverSlice.actions export const { setWebSocketConnected } = serverSlice.actions

View File

@@ -1,4 +1,4 @@
import { createAppAsyncThunk } from "app/async-thunk" import { createAppAsyncThunk } from "app/async-thunk"
import { client } from "app/client" import { client } from "app/client"
export const reloadServerInfos = createAppAsyncThunk("server/infos", async () => await client.server.getServerInfos().then(r => r.data)) export const reloadServerInfos = createAppAsyncThunk("server/infos", async () => await client.server.getServerInfos().then(r => r.data))

View File

@@ -1,23 +1,23 @@
import { configureStore } from "@reduxjs/toolkit" import { configureStore } from "@reduxjs/toolkit"
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"
import { treeSlice } from "app/tree/slice" import { treeSlice } from "app/tree/slice"
import { userSlice } from "app/user/slice" import { userSlice } from "app/user/slice"
import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux" import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
export const reducers = { export const reducers = {
entries: entriesSlice.reducer, entries: entriesSlice.reducer,
redirect: redirectSlice.reducer, redirect: redirectSlice.reducer,
tree: treeSlice.reducer, tree: treeSlice.reducer,
server: serverSlice.reducer, server: serverSlice.reducer,
user: userSlice.reducer, user: userSlice.reducer,
} }
export const store = configureStore({ reducer: reducers }) export const store = configureStore({ reducer: reducers })
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
export const useAppDispatch: () => AppDispatch = useDispatch export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

View File

@@ -1,72 +1,68 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit" import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
import { markEntry } from "app/entries/thunks" import { markEntry } from "app/entries/thunks"
import { redirectTo } from "app/redirect/slice" import { redirectTo } from "app/redirect/slice"
import { collapseTreeCategory, reloadTree } from "app/tree/thunks" import { collapseTreeCategory, reloadTree } from "app/tree/thunks"
import { type Category } from "app/types" import type { Category } from "app/types"
import { visitCategoryTree } from "app/utils" import { visitCategoryTree } from "app/utils"
interface TreeState { interface TreeState {
rootCategory?: Category rootCategory?: Category
mobileMenuOpen: boolean mobileMenuOpen: boolean
sidebarVisible: boolean sidebarVisible: boolean
} }
const initialState: TreeState = { const initialState: TreeState = {
mobileMenuOpen: false, mobileMenuOpen: false,
sidebarVisible: true, sidebarVisible: true,
} }
export const treeSlice = createSlice({ export const treeSlice = createSlice({
name: "tree", name: "tree",
initialState, initialState,
reducers: { reducers: {
setMobileMenuOpen: (state, action: PayloadAction<boolean>) => { setMobileMenuOpen: (state, action: PayloadAction<boolean>) => {
state.mobileMenuOpen = action.payload state.mobileMenuOpen = action.payload
}, },
toggleSidebar: state => { toggleSidebar: state => {
state.sidebarVisible = !state.sidebarVisible state.sidebarVisible = !state.sidebarVisible
}, },
incrementUnreadCount: ( incrementUnreadCount: (
state, state,
action: PayloadAction<{ action: PayloadAction<{
feedId: number feedId: number
amount: number amount: number
}> }>
) => { ) => {
if (!state.rootCategory) return if (!state.rootCategory) return
visitCategoryTree(state.rootCategory, c => visitCategoryTree(state.rootCategory, c => {
c.feeds for (const f of c.feeds.filter(f => f.id === action.payload.feedId)) {
.filter(f => f.id === action.payload.feedId) f.unread += action.payload.amount
.forEach(f => { }
f.unread += action.payload.amount })
}) },
) },
}, extraReducers: builder => {
}, builder.addCase(reloadTree.fulfilled, (state, action) => {
extraReducers: builder => { state.rootCategory = action.payload
builder.addCase(reloadTree.fulfilled, (state, action) => { })
state.rootCategory = action.payload builder.addCase(collapseTreeCategory.pending, (state, action) => {
}) if (!state.rootCategory) return
builder.addCase(collapseTreeCategory.pending, (state, action) => { visitCategoryTree(state.rootCategory, c => {
if (!state.rootCategory) return if (+c.id === action.meta.arg.id) c.expanded = !action.meta.arg.collapse
visitCategoryTree(state.rootCategory, c => { })
if (+c.id === action.meta.arg.id) c.expanded = !action.meta.arg.collapse })
}) builder.addCase(markEntry.pending, (state, action) => {
}) if (!state.rootCategory) return
builder.addCase(markEntry.pending, (state, action) => { visitCategoryTree(state.rootCategory, c => {
if (!state.rootCategory) return for (const f of c.feeds.filter(f => f.id === +action.meta.arg.entry.feedId)) {
visitCategoryTree(state.rootCategory, c => f.unread = action.meta.arg.read ? f.unread - 1 : f.unread + 1
c.feeds }
.filter(f => f.id === +action.meta.arg.entry.feedId) })
.forEach(f => { })
f.unread = action.meta.arg.read ? f.unread - 1 : f.unread + 1 builder.addCase(redirectTo, state => {
}) state.mobileMenuOpen = false
) })
}) },
builder.addCase(redirectTo, state => { })
state.mobileMenuOpen = false
}) export const { setMobileMenuOpen, toggleSidebar, incrementUnreadCount } = treeSlice.actions
},
})
export const { setMobileMenuOpen, toggleSidebar, incrementUnreadCount } = treeSlice.actions

View File

@@ -1,9 +1,9 @@
import { createAppAsyncThunk } from "app/async-thunk" import { createAppAsyncThunk } from "app/async-thunk"
import { client } from "app/client" import { client } from "app/client"
import type { CollapseRequest } from "app/types" import type { CollapseRequest } from "app/types"
export const reloadTree = createAppAsyncThunk("tree/reload", async () => await client.category.getRoot().then(r => r.data)) export const reloadTree = createAppAsyncThunk("tree/reload", async () => await client.category.getRoot().then(r => r.data))
export const collapseTreeCategory = createAppAsyncThunk( export const collapseTreeCategory = createAppAsyncThunk(
"tree/category/collapse", "tree/category/collapse",
async (req: CollapseRequest) => await client.category.collapse(req) async (req: CollapseRequest) => await client.category.collapse(req)
) )

View File

@@ -1,291 +1,295 @@
export interface AddCategoryRequest { export type ReadingMode = "all" | "unread"
name: string
parentId?: string export type ReadingOrder = "asc" | "desc"
}
export type ViewMode = "title" | "cozy" | "detailed" | "expanded"
export interface Category {
id: string export type ScrollMode = "always" | "never" | "if_needed"
parentId?: string
parentName?: string export type IconDisplayMode = "always" | "never" | "on_desktop" | "on_mobile"
name: string
children: Category[] export interface AddCategoryRequest {
feeds: Subscription[] name: string
expanded: boolean parentId?: string
position: number }
}
export interface Subscription {
export interface CategoryModificationRequest { id: number
id: number name: string
name?: string message?: string
parentId?: string errorCount: number
position?: number lastRefresh?: number
} nextRefresh?: number
feedUrl: string
export interface CollapseRequest { feedLink: string
id: number iconUrl: string
collapse: boolean unread: number
} categoryId?: string
position: number
export interface Entries { newestItemTime?: number
name: string filter?: string
message?: string }
errorCount: number
feedLink: string export interface Category {
timestamp: number id: string
hasMore: boolean parentId?: string
offset?: number parentName?: string
limit?: number name: string
entries: Entry[] children: Category[]
ignoredReadStatus: boolean feeds: Subscription[]
} expanded: boolean
position: number
export interface Entry { }
id: string
guid: string export interface CategoryModificationRequest {
title: string id: number
content: string name?: string
categories?: string parentId?: string
rtl: boolean position?: number
author?: string }
enclosureUrl?: string
enclosureType?: string export interface CollapseRequest {
mediaDescription?: string id: number
mediaThumbnailUrl?: string collapse: boolean
mediaThumbnailWidth?: number }
mediaThumbnailHeight?: number
date: number export interface Entry {
insertedDate: number id: string
feedId: string guid: string
feedName: string title: string
feedUrl: string content: string
feedLink: string categories?: string
iconUrl: string rtl: boolean
url: string author?: string
read: boolean enclosureUrl?: string
starred: boolean enclosureType?: string
markable: boolean mediaDescription?: string
tags: string[] mediaThumbnailUrl?: string
} mediaThumbnailWidth?: number
mediaThumbnailHeight?: number
export interface FeedInfo { date: number
url: string insertedDate: number
title: string feedId: string
} feedName: string
feedUrl: string
export interface FeedInfoRequest { feedLink: string
url: string iconUrl: string
} url: string
read: boolean
export interface FeedModificationRequest { starred: boolean
id: number markable: boolean
name?: string tags: string[]
categoryId?: string }
position?: number
filter?: string export interface Entries {
} name: string
message?: string
export interface GetEntriesRequest { errorCount: number
id: string feedLink: string
readType?: ReadingMode timestamp: number
newerThan?: number hasMore: boolean
order?: ReadingOrder offset?: number
keywords?: string limit?: number
onlyIds?: boolean entries: Entry[]
excludedSubscriptionIds?: string ignoredReadStatus: boolean
tag?: string }
}
export interface FeedInfo {
export interface GetEntriesPaginatedRequest extends GetEntriesRequest { url: string
offset: number title: string
limit: number }
}
export interface FeedInfoRequest {
export interface IDRequest { url: string
id: number }
}
export interface FeedModificationRequest {
export interface LoginRequest { id: number
name: string name?: string
password: string categoryId?: string
} position?: number
filter?: string
export interface MarkRequest { }
id: string
read: boolean export interface GetEntriesRequest {
olderThan?: number id: string
insertedBefore?: number readType?: ReadingMode
keywords?: string newerThan?: number
excludedSubscriptions?: number[] order?: ReadingOrder
} keywords?: string
excludedSubscriptionIds?: string
export interface MetricCounter { tag?: string
count: number }
}
export interface GetEntriesPaginatedRequest extends GetEntriesRequest {
export interface MetricGauge { offset: number
value: number limit: number
} }
export interface MetricMeter { export interface IDRequest {
count: number id: number
m15_rate: number }
m1_rate: number
m5_rate: number export interface LoginRequest {
mean_rate: number name: string
units: string password: string
} }
export interface MetricTimer { export interface MarkRequest {
count: number id: string
max: number read: boolean
mean: number olderThan?: number
min: number insertedBefore?: number
p50: number keywords?: string
p75: number excludedSubscriptions?: number[]
p95: number }
p98: number
p99: number export interface MetricCounter {
p999: number count: number
stddev: number }
m15_rate: number
m1_rate: number export interface MetricGauge {
m5_rate: number value: number
mean_rate: number }
duration_units: string
rate_units: string export interface MetricMeter {
} count: number
m15_rate: number
export interface Metrics { m1_rate: number
counters: Record<string, MetricCounter> m5_rate: number
gauges: Record<string, MetricGauge> mean_rate: number
meters: Record<string, MetricMeter> units: string
timers: Record<string, MetricTimer> }
}
export interface MetricTimer {
export interface MultipleMarkRequest { count: number
requests: MarkRequest[] max: number
} mean: number
min: number
export interface PasswordResetRequest { p50: number
email: string p75: number
} p95: number
p98: number
export interface ProfileModificationRequest { p99: number
currentPassword: string p999: number
email: string stddev: number
newPassword?: string m15_rate: number
newApiKey?: boolean m1_rate: number
} m5_rate: number
mean_rate: number
export interface RegistrationRequest { duration_units: string
name: string rate_units: string
password: string }
email: string
} export interface Metrics {
counters: Record<string, MetricCounter>
export interface ServerInfo { gauges: Record<string, MetricGauge>
announcement?: string meters: Record<string, MetricMeter>
version: string timers: Record<string, MetricTimer>
gitCommit: string }
allowRegistrations: boolean
googleAnalyticsCode?: string export interface MultipleMarkRequest {
smtpEnabled: boolean requests: MarkRequest[]
demoAccountEnabled: boolean }
websocketEnabled: boolean
websocketPingInterval: number export interface PasswordResetRequest {
treeReloadInterval: number email: string
} }
export interface Settings { export interface ProfileModificationRequest {
language: string currentPassword: string
readingMode: ReadingMode email: string
readingOrder: ReadingOrder newPassword?: string
showRead: boolean newApiKey?: boolean
scrollMarks: boolean }
customCss?: string
customJs?: string export interface RegistrationRequest {
scrollSpeed: number name: string
alwaysScrollToEntry: boolean password: string
markAllAsReadConfirmation: boolean email: string
customContextMenu: boolean }
mobileFooter: boolean
sharingSettings: SharingSettings export interface ServerInfo {
} announcement?: string
version: string
export interface SharingSettings { gitCommit: string
email: boolean allowRegistrations: boolean
gmail: boolean googleAnalyticsCode?: string
facebook: boolean smtpEnabled: boolean
twitter: boolean demoAccountEnabled: boolean
tumblr: boolean websocketEnabled: boolean
pocket: boolean websocketPingInterval: number
instapaper: boolean treeReloadInterval: number
buffer: boolean }
}
export interface SharingSettings {
export interface StarRequest { email: boolean
id: string gmail: boolean
feedId: number facebook: boolean
starred: boolean twitter: boolean
} tumblr: boolean
pocket: boolean
export interface SubscribeRequest { instapaper: boolean
url: string buffer: boolean
title: string }
categoryId?: string
} export interface Settings {
language: string
export interface Subscription { readingMode: ReadingMode
id: number readingOrder: ReadingOrder
name: string showRead: boolean
message?: string scrollMarks: boolean
errorCount: number customCss?: string
lastRefresh?: number customJs?: string
nextRefresh?: number scrollSpeed: number
feedUrl: string scrollMode: ScrollMode
feedLink: string starIconDisplayMode: IconDisplayMode
iconUrl: string externalLinkIconDisplayMode: IconDisplayMode
unread: number markAllAsReadConfirmation: boolean
categoryId?: string customContextMenu: boolean
position: number mobileFooter: boolean
newestItemTime?: number sharingSettings: SharingSettings
filter?: string }
}
export interface StarRequest {
export interface TagRequest { id: string
entryId: number feedId: number
tags: string[] starred: boolean
} }
export interface UnreadCount { export interface SubscribeRequest {
feedId?: number url: string
unreadCount?: number title: string
newestItemTime?: number categoryId?: string
} }
export interface UserModel { export interface TagRequest {
id: number entryId: number
name: string tags: string[]
email?: string }
apiKey?: string
password?: string export interface UserModel {
enabled: boolean id: number
created: number name: string
lastLogin?: number email?: string
admin: boolean apiKey?: string
} password?: string
enabled: boolean
export interface AdminSaveUserRequest { created: number
id?: number lastLogin?: number
name: string admin: boolean
email?: string }
password?: string
enabled: boolean export interface AdminSaveUserRequest {
admin: boolean id?: number
} name: string
email?: string
export type ReadingMode = "all" | "unread" password?: string
enabled: boolean
export type ReadingOrder = "asc" | "desc" admin: boolean
}
export type ViewMode = "title" | "cozy" | "detailed" | "expanded"
export interface AuthenticationError {
message: string
allowRegistrations: boolean
}

View File

@@ -1,108 +1,120 @@
import { t } from "@lingui/macro" import { t } from "@lingui/macro"
import { showNotification } from "@mantine/notifications" 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, UserModel } from "app/types"
import { import {
changeAlwaysScrollToEntry, changeCustomContextMenu,
changeCustomContextMenu, changeExternalLinkIconDisplayMode,
changeLanguage, changeLanguage,
changeMarkAllAsReadConfirmation, changeMarkAllAsReadConfirmation,
changeMobileFooter, changeMobileFooter,
changeReadingMode, changeReadingMode,
changeReadingOrder, changeReadingOrder,
changeScrollMarks, changeScrollMarks,
changeScrollSpeed, changeScrollMode,
changeSharingSetting, changeScrollSpeed,
changeShowRead, changeSharingSetting,
reloadProfile, changeShowRead,
reloadSettings, changeStarIconDisplayMode,
reloadTags, reloadProfile,
} from "./thunks" reloadSettings,
reloadTags,
interface UserState { } from "./thunks"
settings?: Settings
profile?: UserModel interface UserState {
tags?: string[] settings?: Settings
} profile?: UserModel
tags?: string[]
const initialState: UserState = {} }
export const userSlice = createSlice({ const initialState: UserState = {}
name: "user",
initialState, export const userSlice = createSlice({
reducers: {}, name: "user",
extraReducers: builder => { initialState,
builder.addCase(reloadSettings.fulfilled, (state, action) => { reducers: {},
state.settings = action.payload extraReducers: builder => {
}) builder.addCase(reloadSettings.fulfilled, (state, action) => {
builder.addCase(reloadProfile.fulfilled, (state, action) => { state.settings = action.payload
state.profile = action.payload })
}) builder.addCase(reloadProfile.fulfilled, (state, action) => {
builder.addCase(reloadTags.fulfilled, (state, action) => { state.profile = action.payload
state.tags = action.payload })
}) builder.addCase(reloadTags.fulfilled, (state, action) => {
builder.addCase(changeReadingMode.pending, (state, action) => { state.tags = action.payload
if (!state.settings) return })
state.settings.readingMode = action.meta.arg builder.addCase(changeReadingMode.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeReadingOrder.pending, (state, action) => { state.settings.readingMode = action.meta.arg
if (!state.settings) return })
state.settings.readingOrder = action.meta.arg builder.addCase(changeReadingOrder.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeLanguage.pending, (state, action) => { state.settings.readingOrder = action.meta.arg
if (!state.settings) return })
state.settings.language = action.meta.arg builder.addCase(changeLanguage.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeScrollSpeed.pending, (state, action) => { state.settings.language = action.meta.arg
if (!state.settings) return })
state.settings.scrollSpeed = action.meta.arg ? 400 : 0 builder.addCase(changeScrollSpeed.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeShowRead.pending, (state, action) => { state.settings.scrollSpeed = action.meta.arg ? 400 : 0
if (!state.settings) return })
state.settings.showRead = action.meta.arg builder.addCase(changeShowRead.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeScrollMarks.pending, (state, action) => { state.settings.showRead = action.meta.arg
if (!state.settings) return })
state.settings.scrollMarks = action.meta.arg builder.addCase(changeScrollMarks.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeAlwaysScrollToEntry.pending, (state, action) => { state.settings.scrollMarks = action.meta.arg
if (!state.settings) return })
state.settings.alwaysScrollToEntry = action.meta.arg builder.addCase(changeScrollMode.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => { state.settings.scrollMode = action.meta.arg
if (!state.settings) return })
state.settings.markAllAsReadConfirmation = action.meta.arg builder.addCase(changeStarIconDisplayMode.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeCustomContextMenu.pending, (state, action) => { state.settings.starIconDisplayMode = action.meta.arg
if (!state.settings) return })
state.settings.customContextMenu = action.meta.arg builder.addCase(changeExternalLinkIconDisplayMode.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeMobileFooter.pending, (state, action) => { state.settings.externalLinkIconDisplayMode = action.meta.arg
if (!state.settings) return })
state.settings.mobileFooter = action.meta.arg builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => {
}) if (!state.settings) return
builder.addCase(changeSharingSetting.pending, (state, action) => { state.settings.markAllAsReadConfirmation = action.meta.arg
if (!state.settings) return })
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value builder.addCase(changeCustomContextMenu.pending, (state, action) => {
}) if (!state.settings) return
builder.addMatcher( state.settings.customContextMenu = action.meta.arg
isAnyOf( })
changeLanguage.fulfilled, builder.addCase(changeMobileFooter.pending, (state, action) => {
changeScrollSpeed.fulfilled, if (!state.settings) return
changeShowRead.fulfilled, state.settings.mobileFooter = action.meta.arg
changeScrollMarks.fulfilled, })
changeAlwaysScrollToEntry.fulfilled, builder.addCase(changeSharingSetting.pending, (state, action) => {
changeMarkAllAsReadConfirmation.fulfilled, if (!state.settings) return
changeCustomContextMenu.fulfilled, state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
changeMobileFooter.fulfilled, })
changeSharingSetting.fulfilled builder.addMatcher(
), isAnyOf(
() => { changeLanguage.fulfilled,
showNotification({ changeScrollSpeed.fulfilled,
message: t`Settings saved.`, changeShowRead.fulfilled,
color: "green", changeScrollMarks.fulfilled,
}) changeScrollMode.fulfilled,
} changeStarIconDisplayMode.fulfilled,
) changeExternalLinkIconDisplayMode.fulfilled,
}, changeMarkAllAsReadConfirmation.fulfilled,
}) changeCustomContextMenu.fulfilled,
changeMobileFooter.fulfilled,
changeSharingSetting.fulfilled
),
() => {
showNotification({
message: t`Settings saved.`,
color: "green",
})
}
)
},
})

View File

@@ -1,83 +1,99 @@
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 { IconDisplayMode, 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))
export const reloadTags = createAppAsyncThunk("entries/tags", async () => await client.entry.getTags().then(r => r.data)) export const reloadTags = createAppAsyncThunk("entries/tags", async () => await client.entry.getTags().then(r => r.data))
export const changeReadingMode = createAppAsyncThunk("settings/readingMode", (readingMode: ReadingMode, thunkApi) => { export const changeReadingMode = createAppAsyncThunk("settings/readingMode", (readingMode: ReadingMode, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, readingMode }) client.user.saveSettings({ ...settings, readingMode })
thunkApi.dispatch(reloadEntries()) thunkApi.dispatch(reloadEntries())
}) })
export const changeReadingOrder = createAppAsyncThunk("settings/readingOrder", (readingOrder: ReadingOrder, thunkApi) => { export const changeReadingOrder = createAppAsyncThunk("settings/readingOrder", (readingOrder: ReadingOrder, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, readingOrder }) client.user.saveSettings({ ...settings, readingOrder })
thunkApi.dispatch(reloadEntries()) thunkApi.dispatch(reloadEntries())
}) })
export const changeLanguage = createAppAsyncThunk("settings/language", (language: string, thunkApi) => { export const changeLanguage = createAppAsyncThunk("settings/language", (language: string, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, language }) client.user.saveSettings({ ...settings, language })
}) })
export const changeScrollSpeed = createAppAsyncThunk("settings/scrollSpeed", (speed: boolean, thunkApi) => { export const changeScrollSpeed = createAppAsyncThunk("settings/scrollSpeed", (speed: boolean, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 }) client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 })
}) })
export const changeShowRead = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => { export const changeShowRead = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, showRead }) client.user.saveSettings({ ...settings, showRead })
}) })
export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (scrollMarks: boolean, thunkApi) => { export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (scrollMarks: boolean, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
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 changeStarIconDisplayMode = createAppAsyncThunk(
"settings/markAllAsReadConfirmation", "settings/starIconDisplayMode",
(markAllAsReadConfirmation: boolean, thunkApi) => { (starIconDisplayMode: IconDisplayMode, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, markAllAsReadConfirmation }) client.user.saveSettings({ ...settings, starIconDisplayMode })
} }
) )
export const changeCustomContextMenu = createAppAsyncThunk("settings/customContextMenu", (customContextMenu: boolean, thunkApi) => { export const changeExternalLinkIconDisplayMode = createAppAsyncThunk(
const { settings } = thunkApi.getState().user "settings/externalLinkIconDisplayMode",
if (!settings) return (externalLinkIconDisplayMode: IconDisplayMode, thunkApi) => {
client.user.saveSettings({ ...settings, customContextMenu }) const { settings } = thunkApi.getState().user
}) if (!settings) return
export const changeMobileFooter = createAppAsyncThunk("settings/mobileFooter", (mobileFooter: boolean, thunkApi) => { client.user.saveSettings({ ...settings, externalLinkIconDisplayMode })
const { settings } = thunkApi.getState().user }
if (!settings) return )
client.user.saveSettings({ ...settings, mobileFooter }) export const changeMarkAllAsReadConfirmation = createAppAsyncThunk(
}) "settings/markAllAsReadConfirmation",
export const changeSharingSetting = createAppAsyncThunk( (markAllAsReadConfirmation: boolean, thunkApi) => {
"settings/sharingSetting", const { settings } = thunkApi.getState().user
( if (!settings) return
sharingSetting: { client.user.saveSettings({ ...settings, markAllAsReadConfirmation })
site: keyof SharingSettings }
value: boolean )
}, export const changeCustomContextMenu = createAppAsyncThunk("settings/customContextMenu", (customContextMenu: boolean, thunkApi) => {
thunkApi const { settings } = thunkApi.getState().user
) => { if (!settings) return
const { settings } = thunkApi.getState().user client.user.saveSettings({ ...settings, customContextMenu })
if (!settings) return })
client.user.saveSettings({ export const changeMobileFooter = createAppAsyncThunk("settings/mobileFooter", (mobileFooter: boolean, thunkApi) => {
...settings, const { settings } = thunkApi.getState().user
sharingSettings: { if (!settings) return
...settings.sharingSettings, client.user.saveSettings({ ...settings, mobileFooter })
[sharingSetting.site]: sharingSetting.value, })
}, export const changeSharingSetting = createAppAsyncThunk(
}) "settings/sharingSetting",
} (
) sharingSetting: {
site: keyof SharingSettings
value: boolean
},
thunkApi
) => {
const { settings } = thunkApi.getState().user
if (!settings) return
client.user.saveSettings({
...settings,
sharingSettings: {
...settings.sharingSettings,
[sharingSetting.site]: sharingSetting.value,
},
})
}
)

View File

@@ -1,47 +1,49 @@
import { throttle } from "throttle-debounce" import { throttle } from "throttle-debounce"
import { type Category } from "./types" import type { Category } from "./types"
export function visitCategoryTree(category: Category, visitor: (category: Category) => void): void { export function visitCategoryTree(category: Category, visitor: (category: Category) => void): void {
visitor(category) visitor(category)
category.children.forEach(child => visitCategoryTree(child, visitor)) for (const child of category.children) {
} visitCategoryTree(child, visitor)
}
export function flattenCategoryTree(category: Category): Category[] { }
const categories: Category[] = []
visitCategoryTree(category, c => categories.push(c)) export function flattenCategoryTree(category: Category): Category[] {
return categories const categories: Category[] = []
} visitCategoryTree(category, c => categories.push(c))
return categories
export function categoryUnreadCount(category?: Category): number { }
if (!category) return 0
export function categoryUnreadCount(category?: Category): number {
return flattenCategoryTree(category) if (!category) return 0
.flatMap(c => c.feeds)
.map(f => f.unread) return flattenCategoryTree(category)
.reduce((total, current) => total + current, 0) .flatMap(c => c.feeds)
} .map(f => f.unread)
.reduce((total, current) => total + current, 0)
export const calculatePlaceholderSize = ({ width, height, maxWidth }: { width?: number; height?: number; maxWidth: number }) => { }
const placeholderWidth = width && Math.min(width, maxWidth)
const placeholderHeight = height && width && width > maxWidth ? height * (maxWidth / width) : height export const calculatePlaceholderSize = ({ width, height, maxWidth }: { width?: number; height?: number; maxWidth: number }) => {
return { width: placeholderWidth, height: placeholderHeight } const placeholderWidth = width && Math.min(width, maxWidth)
} const placeholderHeight = height && width && width > maxWidth ? height * (maxWidth / width) : height
return { width: placeholderWidth, height: placeholderHeight }
export const scrollToWithCallback = ({ options, onScrollEnded }: { options: ScrollToOptions; onScrollEnded: () => void }) => { }
const offset = (options.top ?? 0).toFixed()
export const scrollToWithCallback = ({ options, onScrollEnded }: { options: ScrollToOptions; onScrollEnded: () => void }) => {
const onScroll = throttle(100, () => { const offset = (options.top ?? 0).toFixed()
if (window.scrollY.toFixed() === offset) {
window.removeEventListener("scroll", onScroll) const onScroll = throttle(100, () => {
onScrollEnded() if (window.scrollY.toFixed() === offset) {
} window.removeEventListener("scroll", onScroll)
}) onScrollEnded()
window.addEventListener("scroll", onScroll) }
})
// scrollTo does not trigger if there's nothing to do, trigger it manually window.addEventListener("scroll", onScroll)
onScroll()
// scrollTo does not trigger if there's nothing to do, trigger it manually
window.scrollTo(options) onScroll()
}
window.scrollTo(options)
export const truncate = (str: string, n: number) => (str.length > n ? `${str.slice(0, n - 1)}\u2026` : str) }
export const truncate = (str: string, n: number) => (str.length > n ? `${str.slice(0, n - 1)}\u2026` : str)

View File

@@ -1,36 +1,37 @@
import { ActionIcon, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core" import { ActionIcon, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core"
import { type ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon" import type { ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon"
import { useActionButton } from "hooks/useActionButton" import { Constants } from "app/constants"
import { forwardRef, type MouseEventHandler, type ReactNode } from "react" import { useActionButton } from "hooks/useActionButton"
import { type MouseEventHandler, type ReactNode, forwardRef } from "react"
interface ActionButtonProps {
className?: string interface ActionButtonProps {
icon?: ReactNode className?: string
label: ReactNode icon?: ReactNode
onClick?: MouseEventHandler label: ReactNode
variant?: ActionIconVariant & ButtonVariant onClick?: MouseEventHandler
hideLabelOnDesktop?: boolean variant?: ActionIconVariant & ButtonVariant
showLabelOnMobile?: boolean hideLabelOnDesktop?: boolean
} showLabelOnMobile?: boolean
}
/**
* Switches between Button with label (desktop) and ActionIcon (mobile) /**
*/ * Switches between Button with label (desktop) and ActionIcon (mobile)
export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((props: ActionButtonProps, ref) => { */
const { mobile } = useActionButton() export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((props: ActionButtonProps, ref) => {
const theme = useMantineTheme() const { mobile } = useActionButton()
const variant = props.variant ?? "subtle" const theme = useMantineTheme()
const iconOnly = (mobile && !props.showLabelOnMobile) || (!mobile && props.hideLabelOnDesktop) const variant = props.variant ?? "subtle"
return iconOnly ? ( const iconOnly = (mobile && !props.showLabelOnMobile) || (!mobile && props.hideLabelOnDesktop)
<Tooltip label={props.label} openDelay={500}> return iconOnly ? (
<ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}> <Tooltip label={props.label} openDelay={Constants.tooltip.delay}>
{props.icon} <ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}>
</ActionIcon> {props.icon}
</Tooltip> </ActionIcon>
) : ( </Tooltip>
<Button ref={ref} variant={variant} size="xs" className={props.className} leftSection={props.icon} onClick={props.onClick}> ) : (
{props.label} <Button ref={ref} variant={variant} size="xs" className={props.className} leftSection={props.icon} onClick={props.onClick}>
</Button> {props.label}
) </Button>
}) )
ActionButton.displayName = "HeaderButton" })
ActionButton.displayName = "HeaderButton"

View File

@@ -1,47 +1,47 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Alert as MantineAlert, Box } from "@mantine/core" import { Box, Alert as MantineAlert } from "@mantine/core"
import { Fragment } from "react" import { Fragment } from "react"
import { TbAlertCircle, TbAlertTriangle, TbCircleCheck } from "react-icons/tb" import { TbAlertCircle, TbAlertTriangle, TbCircleCheck } from "react-icons/tb"
type Level = "error" | "warning" | "success" type Level = "error" | "warning" | "success"
export interface ErrorsAlertProps { export interface ErrorsAlertProps {
level?: Level level?: Level
messages: string[] messages: string[]
} }
export function Alert(props: ErrorsAlertProps) { export function Alert(props: ErrorsAlertProps) {
let title: React.ReactNode let title: React.ReactNode
let color: string let color: string
let icon: React.ReactNode let icon: React.ReactNode
const level = props.level ?? "error" const level = props.level ?? "error"
switch (level) { switch (level) {
case "error": case "error":
title = <Trans>Error</Trans> title = <Trans>Error</Trans>
color = "red" color = "red"
icon = <TbAlertCircle /> icon = <TbAlertCircle />
break break
case "warning": case "warning":
title = <Trans>Warning</Trans> title = <Trans>Warning</Trans>
color = "orange" color = "orange"
icon = <TbAlertTriangle /> icon = <TbAlertTriangle />
break break
case "success": case "success":
title = <Trans>Success</Trans> title = <Trans>Success</Trans>
color = "green" color = "green"
icon = <TbCircleCheck /> icon = <TbCircleCheck />
break break
} }
return ( return (
<MantineAlert title={title} color={color} icon={icon}> <MantineAlert title={title} color={color} icon={icon}>
{props.messages.map((m, i) => ( {props.messages.map((m, i) => (
<Fragment key={m}> <Fragment key={m}>
<Box>{m}</Box> <Box>{m}</Box>
{i !== props.messages.length - 1 && <br />} {i !== props.messages.length - 1 && <br />}
</Fragment> </Fragment>
))} ))}
</MantineAlert> </MantineAlert>
) )
} }

View File

@@ -0,0 +1,15 @@
import { Helmet } from "react-helmet"
export const DisablePullToRefresh = () => {
return (
<Helmet>
<style type="text/css">
{`
html, body {
overscroll-behavior: none;
}
`}
</style>
</Helmet>
)
}

View File

@@ -1,26 +1,26 @@
import { ErrorPage } from "pages/ErrorPage" import { ErrorPage } from "pages/ErrorPage"
import React, { type ReactNode } from "react" import React, { type ReactNode } from "react"
interface ErrorBoundaryProps { interface ErrorBoundaryProps {
children?: ReactNode children?: ReactNode
} }
interface ErrorBoundaryState { interface ErrorBoundaryState {
error?: Error error?: Error
} }
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> { export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) { constructor(props: ErrorBoundaryProps) {
super(props) super(props)
this.state = {} this.state = {}
} }
componentDidCatch(error: Error) { componentDidCatch(error: Error) {
this.setState({ error }) this.setState({ error })
} }
render() { render() {
if (this.state.error) return <ErrorPage error={this.state.error} /> if (this.state.error) return <ErrorPage error={this.state.error} />
return this.props.children return this.props.children
} }
} }

View File

@@ -1,82 +1,75 @@
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"
interface ImageWithPlaceholderWhileLoadingProps {
interface ImageWithPlaceholderWhileLoadingProps { src: string
src: string alt: string
alt: string title?: string
title?: string width?: number
width?: number height?: number | "auto"
height?: number | "auto" placeholderWidth?: number
placeholderWidth?: number placeholderHeight?: number
placeholderHeight?: number placeholderBackgroundColor?: string
placeholderBackgroundColor?: string placeholderIconSize?: number
placeholderIconSize?: number }
}
const useStyles = tss
const useStyles = tss .withParams<{
.withParams<{ placeholderWidth?: number
theme: MantineTheme placeholderHeight?: number
colorScheme: "light" | "dark" placeholderBackgroundColor?: string
placeholderWidth?: number }>()
placeholderHeight?: number .create(props => ({
placeholderBackgroundColor?: string placeholder: {
}>() width: props.placeholderWidth ?? 400,
.create(props => ({ height: props.placeholderHeight ?? 600,
placeholder: { maxWidth: "100%",
width: props.placeholderWidth ?? 400, backgroundColor:
height: props.placeholderHeight ?? 600, props.placeholderBackgroundColor ??
maxWidth: "100%", (props.colorScheme === "dark" ? props.theme.colors.dark[5] : props.theme.colors.gray[1]),
backgroundColor: },
props.placeholderBackgroundColor ?? }))
(props.colorScheme === "dark" ? props.theme.colors.dark[5] : props.theme.colors.gray[1]),
}, export function ImageWithPlaceholderWhileLoading({
})) alt,
height,
export function ImageWithPlaceholderWhileLoading({ placeholderBackgroundColor,
alt, placeholderHeight,
height, placeholderIconSize,
placeholderBackgroundColor, placeholderWidth,
placeholderHeight, src,
placeholderIconSize, title,
placeholderWidth, width,
src, }: ImageWithPlaceholderWhileLoadingProps) {
title, const { classes } = useStyles({
width, placeholderWidth,
}: ImageWithPlaceholderWhileLoadingProps) { placeholderHeight,
const theme = useMantineTheme() placeholderBackgroundColor,
const colorScheme = useColorScheme() })
const { classes } = useStyles({ const [loading, setLoading] = useState(true)
theme,
colorScheme, return (
placeholderWidth, <>
placeholderHeight, {loading && (
placeholderBackgroundColor, <Box>
}) <Center className={classes.placeholder}>
const [loading, setLoading] = useState(true) <div>
<TbPhoto size={placeholderIconSize ?? 48} />
return ( </div>
<> </Center>
{loading && ( </Box>
<Box> )}
<Center className={classes.placeholder}> <img
<div> src={src}
<TbPhoto size={placeholderIconSize ?? 48} /> alt={alt}
</div> title={title}
</Center> width={width}
</Box> height={height}
)} onLoad={() => setLoading(false)}
<img style={{ display: loading ? "none" : "block" }}
src={src} />
alt={alt} </>
title={title} )
width={width} }
height={height}
onLoad={() => setLoading(false)}
style={{ display: loading ? "none" : "block" }}
/>
</>
)
}

View File

@@ -1,222 +1,224 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Anchor, Box, Kbd, Stack, Table } from "@mantine/core" import { Anchor, Box, Kbd, Stack, Table } from "@mantine/core"
import { Constants } from "app/constants" import { useOs } from "@mantine/hooks"
import { Constants } from "app/constants"
export function KeyboardShortcutsHelp() {
return ( export function KeyboardShortcutsHelp() {
<Stack gap="xs"> const isMacOS = useOs() === "macos"
<Table striped highlightOnHover> return (
<Table.Tbody> <Stack gap="xs">
<Table.Tr> <Table striped highlightOnHover>
<Table.Td> <Table.Tbody>
<Trans>Refresh</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Refresh</Trans>
<Kbd>R</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>R</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Open next entry</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Open next entry</Trans>
<Kbd>J</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>J</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Open previous entry</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Open previous entry</Trans>
<Kbd>K</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>K</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Set focus on next entry without opening it</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Set focus on next entry without opening it</Trans>
<Kbd>N</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>N</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Set focus on previous entry without opening it</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Set focus on previous entry without opening it</Trans>
<Kbd>P</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>P</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Move the page down</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Move the page down</Trans>
<Kbd> </Table.Td>
<Trans>Space</Trans> <Table.Td>
</Kbd> <Kbd>
</Table.Td> <Trans>Space</Trans>
</Table.Tr> </Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Move the page up</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Move the page up</Trans>
<Kbd> </Table.Td>
<Trans>Shift</Trans> <Table.Td>
</Kbd> <Kbd>
<span> + </span> <Trans>Shift</Trans>
<Kbd> </Kbd>
<Trans>Space</Trans> <span> + </span>
</Kbd> <Kbd>
</Table.Td> <Trans>Space</Trans>
</Table.Tr> </Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Open/close current entry</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Open/close current entry</Trans>
<Kbd>O</Kbd> </Table.Td>
<span>, </span> <Table.Td>
<Kbd> <Kbd>O</Kbd>
<Trans>Enter</Trans> <span>, </span>
</Kbd> <Kbd>
</Table.Td> <Trans>Enter</Trans>
</Table.Tr> </Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Open current entry in a new tab</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Open current entry in a new tab</Trans>
<Kbd>V</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>V</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Open current entry in a new tab in the background</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Open current entry in a new tab in the background</Trans>
<Kbd>B</Kbd> </Table.Td>
<span>*, </span> <Table.Td>
<Kbd> <Kbd>B</Kbd>
<Trans>Middle click</Trans> <span>*, </span>
</Kbd> <Kbd>
</Table.Td> <Trans>Middle click</Trans>
</Table.Tr> </Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Toggle read status of current entry</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Toggle read status of current entry</Trans>
<Kbd>M</Kbd> </Table.Td>
<span>, </span> <Table.Td>
<Trans>Swipe header to the left</Trans> <Kbd>M</Kbd>
</Table.Td> <span>, </span>
</Table.Tr> <Trans>Swipe header to the left</Trans>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Toggle starred status of current entry</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Toggle starred status of current entry</Trans>
<Kbd>S</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>S</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Mark all entries as read</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Mark all entries as read</Trans>
<Kbd> </Table.Td>
<Trans>Shift</Trans> <Table.Td>
</Kbd> <Kbd>
<span> + </span> <Trans>Shift</Trans>
<Kbd>A</Kbd> </Kbd>
</Table.Td> <span> + </span>
</Table.Tr> <Kbd>A</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Go to the All view</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Go to the All view</Trans>
<Kbd>G</Kbd> </Table.Td>
<span> </span> <Table.Td>
<Kbd>A</Kbd> <Kbd>G</Kbd>
</Table.Td> <span> </span>
</Table.Tr> <Kbd>A</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Navigate to a subscription by entering its name</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Navigate to a subscription by entering its name</Trans>
<Kbd> </Table.Td>
<Trans>Ctrl</Trans> <Table.Td>
</Kbd> <Kbd>
<span> + </span> <Trans>{isMacOS ? "Cmd" : "Ctrl"}</Trans>
<Kbd>K</Kbd> </Kbd>
<span>, </span> <span> + </span>
<Kbd>G</Kbd> <Kbd>K</Kbd>
<span> </span> <span>, </span>
<Kbd>U</Kbd> <Kbd>G</Kbd>
</Table.Td> <span> </span>
</Table.Tr> <Kbd>U</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Show entry menu (desktop)</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Show entry menu (desktop)</Trans>
<Kbd> </Table.Td>
<Trans>Right click</Trans> <Table.Td>
</Kbd> <Kbd>
</Table.Td> <Trans>Right click</Trans>
</Table.Tr> </Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Show native menu (desktop)</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Show native menu (desktop)</Trans>
<Kbd> </Table.Td>
<Trans>Shift</Trans> <Table.Td>
</Kbd> <Kbd>
<span> + </span> <Trans>Shift</Trans>
<Kbd> </Kbd>
<Trans>Right click</Trans> <span> + </span>
</Kbd> <Kbd>
</Table.Td> <Trans>Right click</Trans>
</Table.Tr> </Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Show entry menu (mobile)</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Show entry menu (mobile)</Trans>
<Kbd> </Table.Td>
<Trans>Long press</Trans> <Table.Td>
</Kbd> <Kbd>
</Table.Td> <Trans>Long press</Trans>
</Table.Tr> </Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Toggle sidebar</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Toggle sidebar</Trans>
<Kbd>F</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>F</Kbd>
<Table.Tr> </Table.Td>
<Table.Td> </Table.Tr>
<Trans>Show keyboard shortcut help</Trans> <Table.Tr>
</Table.Td> <Table.Td>
<Table.Td> <Trans>Show keyboard shortcut help</Trans>
<Kbd>?</Kbd> </Table.Td>
</Table.Td> <Table.Td>
</Table.Tr> <Kbd>?</Kbd>
</Table.Tbody> </Table.Td>
</Table> </Table.Tr>
<Box> </Table.Tbody>
<span>* </span> </Table>
<Anchor href={Constants.browserExtensionUrl} target="_blank" rel="noreferrer"> <Box>
<Trans>Browser extension required for Chrome</Trans> <span>* </span>
</Anchor> <Anchor href={Constants.browserExtensionUrl} target="_blank" rel="noreferrer">
</Box> <Trans>Browser extension required for Chrome</Trans>
</Stack> </Anchor>
) </Box>
} </Stack>
)
}

View File

@@ -1,9 +1,9 @@
import { Center, Loader as MantineLoader } from "@mantine/core" import { Center, Loader as MantineLoader } from "@mantine/core"
export function Loader() { export function Loader() {
return ( return (
<Center> <Center>
<MantineLoader size="lg" type="bars" /> <MantineLoader size="lg" type="bars" />
</Center> </Center>
) )
} }

View File

@@ -1,10 +1,10 @@
import { Image } from "@mantine/core" import { Image } from "@mantine/core"
import logo from "assets/logo.svg" import logo from "assets/logo.svg"
export interface LogoProps { export interface LogoProps {
size: number size: number
} }
export function Logo(props: LogoProps) { export function Logo(props: LogoProps) {
return <Image src={logo} w={props.size} /> return <Image src={logo} w={props.size} />
} }

View File

@@ -1,20 +1,21 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Tooltip } from "@mantine/core" import { Tooltip } from "@mantine/core"
import dayjs from "dayjs" import { Constants } from "app/constants"
import { useEffect, useState } from "react" import dayjs from "dayjs"
import { useEffect, useState } from "react"
export function RelativeDate(props: { date: Date | number | undefined }) {
const [now, setNow] = useState(new Date()) export function RelativeDate(props: { date: Date | number | undefined }) {
useEffect(() => { const [now, setNow] = useState(new Date())
const interval = setInterval(() => setNow(new Date()), 60 * 1000) useEffect(() => {
return () => clearInterval(interval) const interval = setInterval(() => setNow(new Date()), 60 * 1000)
}, []) return () => clearInterval(interval)
}, [])
if (!props.date) return <Trans>N/A</Trans>
const date = dayjs(props.date) if (!props.date) return <Trans>N/A</Trans>
return ( const date = dayjs(props.date)
<Tooltip label={date.toDate().toLocaleString()} openDelay={500}> return (
<span>{date.from(dayjs(now))}</span> <Tooltip label={date.toDate().toLocaleString()} openDelay={Constants.tooltip.delay}>
</Tooltip> <span>{date.from(dayjs(now))}</span>
) </Tooltip>
} )
}

View File

@@ -1,54 +1,54 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box, Button, Checkbox, Group, PasswordInput, Stack, TextInput } from "@mantine/core" import { Box, Button, Checkbox, Group, PasswordInput, Stack, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client" import { client, errorToStrings } from "app/client"
import { type AdminSaveUserRequest, type UserModel } from "app/types" import type { AdminSaveUserRequest, UserModel } from "app/types"
import { Alert } from "components/Alert" import { Alert } from "components/Alert"
import { useAsyncCallback } from "react-async-hook" import { useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy } from "react-icons/tb" import { TbDeviceFloppy } from "react-icons/tb"
interface UserEditProps { interface UserEditProps {
user?: UserModel user?: UserModel
onCancel: () => void onCancel: () => void
onSave: () => void onSave: () => void
} }
export function UserEdit(props: UserEditProps) { export function UserEdit(props: UserEditProps) {
const form = useForm<AdminSaveUserRequest>({ const form = useForm<AdminSaveUserRequest>({
initialValues: props.user ?? { initialValues: props.user ?? {
name: "", name: "",
enabled: true, enabled: true,
admin: false, admin: false,
}, },
}) })
const saveUser = useAsyncCallback(client.admin.saveUser, { onSuccess: props.onSave }) const saveUser = useAsyncCallback(client.admin.saveUser, { onSuccess: props.onSave })
return ( return (
<> <>
{saveUser.error && ( {saveUser.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={errorToStrings(saveUser.error)} /> <Alert messages={errorToStrings(saveUser.error)} />
</Box> </Box>
)} )}
<form onSubmit={form.onSubmit(saveUser.execute)}> <form onSubmit={form.onSubmit(saveUser.execute)}>
<Stack> <Stack>
<TextInput label={<Trans>Name</Trans>} {...form.getInputProps("name")} required /> <TextInput label={<Trans>Name</Trans>} {...form.getInputProps("name")} required />
<PasswordInput label={<Trans>Password</Trans>} {...form.getInputProps("password")} required={!props.user} /> <PasswordInput label={<Trans>Password</Trans>} {...form.getInputProps("password")} required={!props.user} />
<TextInput type="email" label={<Trans>E-mail</Trans>} {...form.getInputProps("email")} /> <TextInput type="email" label={<Trans>E-mail</Trans>} {...form.getInputProps("email")} />
<Checkbox label={<Trans>Admin</Trans>} {...form.getInputProps("admin", { type: "checkbox" })} /> <Checkbox label={<Trans>Admin</Trans>} {...form.getInputProps("admin", { type: "checkbox" })} />
<Checkbox label={<Trans>Enabled</Trans>} {...form.getInputProps("enabled", { type: "checkbox" })} /> <Checkbox label={<Trans>Enabled</Trans>} {...form.getInputProps("enabled", { type: "checkbox" })} />
<Group justify="right"> <Group justify="right">
<Button variant="default" onClick={props.onCancel}> <Button variant="default" onClick={props.onCancel}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveUser.loading}> <Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveUser.loading}>
<Trans>Save</Trans> <Trans>Save</Trans>
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</form> </form>
</> </>
) )
} }

View File

@@ -1,36 +1,36 @@
import { Input, Textarea } from "@mantine/core" import { Input, Textarea } from "@mantine/core"
import RichCodeEditor from "components/code/RichCodeEditor" import RichCodeEditor from "components/code/RichCodeEditor"
import { useMobile } from "hooks/useMobile" import { useMobile } from "hooks/useMobile"
import { type ReactNode } from "react" 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
} }
export function CodeEditor(props: CodeEditorProps) { export function CodeEditor(props: CodeEditorProps) {
const mobile = useMobile() const mobile = useMobile()
return mobile ? ( return mobile ? (
// monaco mobile support is poor, fallback to textarea // monaco mobile support is poor, fallback to textarea
<Textarea <Textarea
autosize autosize
minRows={4} minRows={4}
maxRows={15} maxRows={15}
description={props.description} description={props.description}
styles={{ styles={{
input: { input: {
fontFamily: "monospace", fontFamily: "monospace",
}, },
}} }}
value={props.value} value={props.value}
onChange={e => props.onChange(e.currentTarget.value)} onChange={e => props.onChange(e.currentTarget.value)}
/> />
) : ( ) : (
<Input.Wrapper description={props.description}> <Input.Wrapper description={props.description}>
<RichCodeEditor height="30vh" language={props.language} value={props.value} onChange={props.onChange} /> <RichCodeEditor height="30vh" language={props.language} value={props.value} onChange={props.onChange} />
</Input.Wrapper> </Input.Wrapper>
) )
} }

View File

@@ -1,52 +1,51 @@
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { useColorScheme } from "hooks/useColorScheme" import { useColorScheme } from "hooks/useColorScheme"
import { useAsync } from "react-async-hook" import { useAsync } from "react-async-hook"
const init = async () => { const init = async () => {
window.MonacoEnvironment = { window.MonacoEnvironment = {
async getWorker(_, label) { async getWorker(_, label) {
let worker let worker: typeof import("*?worker")
if (label === "css") { if (label === "css") {
worker = await import("monaco-editor/esm/vs/language/css/css.worker?worker") worker = await import("monaco-editor/esm/vs/language/css/css.worker?worker")
} else if (label === "javascript") { } else if (label === "javascript") {
worker = await import("monaco-editor/esm/vs/language/typescript/ts.worker?worker") worker = await import("monaco-editor/esm/vs/language/typescript/ts.worker?worker")
} else { } else {
worker = await import("monaco-editor/esm/vs/editor/editor.worker?worker") worker = await import("monaco-editor/esm/vs/editor/editor.worker?worker")
} }
// eslint-disable-next-line new-cap return new worker.default()
return new worker.default() },
}, }
}
const monacoReact = await import("@monaco-editor/react")
const monacoReact = await import("@monaco-editor/react") const monaco = await import("monaco-editor")
const monaco = await import("monaco-editor") monacoReact.loader.config({ monaco })
monacoReact.loader.config({ monaco }) return monacoReact.Editor
return monacoReact.Editor }
}
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 }
}
function RichCodeEditor(props: RichCodeEditorProps) {
function RichCodeEditor(props: RichCodeEditorProps) { const colorScheme = useColorScheme()
const colorScheme = useColorScheme() const editorTheme = colorScheme === "dark" ? "vs-dark" : "light"
const editorTheme = colorScheme === "dark" ? "vs-dark" : "light"
const { result: Editor } = useAsync(init, [])
const { result: Editor } = useAsync(init, []) if (!Editor) return <Loader />
if (!Editor) return <Loader /> return (
return ( <Editor
<Editor height={props.height}
height={props.height} defaultLanguage={props.language}
defaultLanguage={props.language} theme={editorTheme}
theme={editorTheme} options={{ minimap: { enabled: false } }}
options={{ minimap: { enabled: false } }} value={props.value}
value={props.value} onChange={props.onChange}
onChange={props.onChange} />
/> )
) }
}
export default RichCodeEditor
export default RichCodeEditor

View File

@@ -1,11 +1,11 @@
import { TypographyStylesProvider } from "@mantine/core" import { TypographyStylesProvider } from "@mantine/core"
import { type ReactNode } from "react" import type { ReactNode } from "react"
/** /**
* This component is used to provide basic styles to html typography elements. * This component is used to provide basic styles to html typography elements.
* *
* see https://mantine.dev/core/typography-styles-provider/ * see https://mantine.dev/core/typography-styles-provider/
*/ */
export const BasicHtmlStyles = (props: { children: ReactNode }) => { export const BasicHtmlStyles = (props: { children: ReactNode }) => {
return <TypographyStylesProvider pl={0}>{props.children}</TypographyStylesProvider> return <TypographyStylesProvider pl={0}>{props.children}</TypographyStylesProvider>
} }

View File

@@ -1,103 +1,103 @@
import { Box, Mark } from "@mantine/core" import { Box, Mark } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { calculatePlaceholderSize } from "app/utils" import { calculatePlaceholderSize } from "app/utils"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import escapeStringRegexp from "escape-string-regexp" import escapeStringRegexp from "escape-string-regexp"
import { type ChildrenNode, Interweave, Matcher, type MatchResponse, type Node, type TransformCallback } from "interweave" import { type ChildrenNode, Interweave, type MatchResponse, Matcher, type Node, type TransformCallback } from "interweave"
import React from "react" import React from "react"
import { tss } from "tss" import { tss } from "tss"
export interface ContentProps { export interface ContentProps {
content: string content: string
highlight?: string highlight?: string
} }
const useStyles = tss.create(() => ({ const useStyles = tss.create(() => ({
content: { content: {
// break long links or long words // break long links or long words
overflowWrap: "anywhere", overflowWrap: "anywhere",
"& a": { "& a": {
color: "inherit", color: "inherit",
textDecoration: "underline", textDecoration: "underline",
}, },
"& iframe": { "& iframe": {
maxWidth: "100%", maxWidth: "100%",
}, },
"& pre, & code": { "& pre, & code": {
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
}, },
}, },
})) }))
const transform: TransformCallback = node => { const transform: TransformCallback = node => {
if (node.tagName === "IMG") { if (node.tagName === "IMG") {
// show placeholders for loading img tags, this allows the entry to have its final height immediately // show placeholders for loading img tags, this allows the entry to have its final height immediately
const src = node.getAttribute("src") ?? undefined const src = node.getAttribute("src") ?? undefined
if (!src) return undefined if (!src) return undefined
const alt = node.getAttribute("alt") ?? "image" const alt = node.getAttribute("alt") ?? "image"
const title = node.getAttribute("title") ?? undefined const title = node.getAttribute("title") ?? undefined
const nodeWidth = node.getAttribute("width") const nodeWidth = node.getAttribute("width")
const nodeHeight = node.getAttribute("height") const nodeHeight = node.getAttribute("height")
const width = nodeWidth ? parseInt(nodeWidth, 10) : undefined const width = nodeWidth ? Number.parseInt(nodeWidth, 10) : undefined
const height = nodeHeight ? parseInt(nodeHeight, 10) : undefined const height = nodeHeight ? Number.parseInt(nodeHeight, 10) : undefined
const placeholderSize = calculatePlaceholderSize({ const placeholderSize = calculatePlaceholderSize({
width, width,
height, height,
maxWidth: Constants.layout.entryMaxWidth, maxWidth: Constants.layout.entryMaxWidth,
}) })
return ( return (
<ImageWithPlaceholderWhileLoading <ImageWithPlaceholderWhileLoading
src={src} src={src}
alt={alt} alt={alt}
title={title} title={title}
width={width} width={width}
height="auto" height="auto"
placeholderWidth={placeholderSize.width} placeholderWidth={placeholderSize.width}
placeholderHeight={placeholderSize.height} placeholderHeight={placeholderSize.height}
/> />
) )
} }
return undefined return undefined
} }
class HighlightMatcher extends Matcher { class HighlightMatcher extends Matcher {
private readonly search: string private readonly search: string
constructor(search: string) { constructor(search: string) {
super("highlight") super("highlight")
this.search = escapeStringRegexp(search) this.search = escapeStringRegexp(search)
} }
match(string: string): MatchResponse<unknown> | null { match(string: string): MatchResponse<unknown> | null {
const pattern = this.search.split(" ").join("|") const pattern = this.search.split(" ").join("|")
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>
} }
asTag(): string { asTag(): string {
return "span" return "span"
} }
} }
// memoize component because Interweave is costly // memoize component because Interweave is costly
const Content = React.memo((props: ContentProps) => { const Content = React.memo((props: ContentProps) => {
const { classes } = useStyles() const { classes } = useStyles()
const matchers = props.highlight ? [new HighlightMatcher(props.highlight)] : [] const matchers = props.highlight ? [new HighlightMatcher(props.highlight)] : []
return ( return (
<BasicHtmlStyles> <BasicHtmlStyles>
<Box className={classes.content}> <Box className={classes.content}>
<Interweave content={props.content} transform={transform} matchers={matchers} /> <Interweave content={props.content} transform={transform} matchers={matchers} />
</Box> </Box>
</BasicHtmlStyles> </BasicHtmlStyles>
) )
}) })
Content.displayName = "Content" Content.displayName = "Content"
export { Content } export { Content }

View File

@@ -1,24 +1,29 @@
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) { export function Enclosure(props: {
const hasVideo = props.enclosureType?.startsWith("video") enclosureType: string
const hasAudio = props.enclosureType?.startsWith("audio") enclosureUrl: string
const hasImage = props.enclosureType?.startsWith("image") }) {
const hasVideo = props.enclosureType.startsWith("video")
return ( const hasAudio = props.enclosureType.startsWith("audio")
<BasicHtmlStyles> const hasImage = props.enclosureType.startsWith("image")
{hasVideo && (
<video controls> return (
<source src={props.enclosureUrl} type={props.enclosureType} /> <BasicHtmlStyles>
</video> {hasVideo && (
)} // biome-ignore lint/a11y/useMediaCaption: we don't have any captions for videos
{hasAudio && ( <video controls width="100%">
<audio controls> <source src={props.enclosureUrl} type={props.enclosureType} />
<source src={props.enclosureUrl} type={props.enclosureType} /> </video>
</audio> )}
)} {hasAudio && (
{hasImage && <ImageWithPlaceholderWhileLoading src={props.enclosureUrl} alt="enclosure" />} // biome-ignore lint/a11y/useMediaCaption: we don't have any captions for audio
</BasicHtmlStyles> <audio controls>
) <source src={props.enclosureUrl} type={props.enclosureType} />
} </audio>
)}
{hasImage && <ImageWithPlaceholderWhileLoading src={props.enclosureUrl} alt="enclosure" />}
</BasicHtmlStyles>
)
}

View File

@@ -1,329 +1,329 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box } from "@mantine/core" import { Box } from "@mantine/core"
import { openModal } from "@mantine/modals" import { openModal } from "@mantine/modals"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { type ExpendableEntry } from "app/entries/slice" import type { ExpendableEntry } from "app/entries/slice"
import { import {
loadMoreEntries, loadMoreEntries,
markAllEntries, markAllEntries,
markEntry, markEntry,
reloadEntries, reloadEntries,
selectEntry, selectEntry,
selectNextEntry, selectNextEntry,
selectPreviousEntry, selectPreviousEntry,
starEntry, starEntry,
} from "app/entries/thunks" } from "app/entries/thunks"
import { redirectToRootCategory } from "app/redirect/thunks" import { redirectToRootCategory } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { toggleSidebar } from "app/tree/slice" import { toggleSidebar } from "app/tree/slice"
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp" import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { useBrowserExtension } from "hooks/useBrowserExtension" import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useMousetrap } from "hooks/useMousetrap" import { useMousetrap } from "hooks/useMousetrap"
import { useViewMode } from "hooks/useViewMode" import { useViewMode } from "hooks/useViewMode"
import { useEffect } from "react" import { useEffect } from "react"
import { useContextMenu } from "react-contexify" import { useContextMenu } from "react-contexify"
import InfiniteScroll from "react-infinite-scroller" import InfiniteScroll from "react-infinite-scroller"
import { throttle } from "throttle-debounce" import { throttle } from "throttle-debounce"
import { FeedEntry } from "./FeedEntry" import { FeedEntry } from "./FeedEntry"
export function FeedEntries() { export function FeedEntries() {
const source = useAppSelector(state => state.entries.source) const source = useAppSelector(state => state.entries.source)
const entries = useAppSelector(state => state.entries.entries) const entries = useAppSelector(state => state.entries.entries)
const entriesTimestamp = useAppSelector(state => state.entries.timestamp) const entriesTimestamp = useAppSelector(state => state.entries.timestamp)
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId) const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
const hasMore = useAppSelector(state => state.entries.hasMore) const hasMore = useAppSelector(state => state.entries.hasMore)
const loading = useAppSelector(state => state.entries.loading) const loading = useAppSelector(state => state.entries.loading)
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks) const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry) const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry)
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible) const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu) const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu)
const { viewMode } = useViewMode() const { viewMode } = useViewMode()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { openLinkInBackgroundTab } = useBrowserExtension() const { openLinkInBackgroundTab } = useBrowserExtension()
const selectedEntry = entries.find(e => e.id === selectedEntryId) const selectedEntry = entries.find(e => e.id === selectedEntryId)
const headerClicked = (entry: ExpendableEntry, event: React.MouseEvent) => { const headerClicked = (entry: ExpendableEntry, event: React.MouseEvent) => {
const middleClick = event.button === 1 || event.ctrlKey || event.metaKey const middleClick = event.button === 1 || event.ctrlKey || event.metaKey
if (middleClick || viewMode === "expanded") { if (middleClick || viewMode === "expanded") {
dispatch(markEntry({ entry, read: true })) dispatch(markEntry({ entry, read: true }))
} else if (event.button === 0) { } else if (event.button === 0) {
// main click // main click
// don't trigger the link // don't trigger the link
event.preventDefault() event.preventDefault()
dispatch( dispatch(
selectEntry({ selectEntry({
entry, entry,
expand: !entry.expanded, expand: !entry.expanded,
markAsRead: !entry.expanded, markAsRead: !entry.expanded,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} }
} }
const contextMenu = useContextMenu() const contextMenu = useContextMenu()
const headerRightClicked = (entry: ExpendableEntry, event: React.MouseEvent) => { const headerRightClicked = (entry: ExpendableEntry, event: React.MouseEvent) => {
if (event.shiftKey || !customContextMenu) return if (event.shiftKey || !customContextMenu) return
event.preventDefault() event.preventDefault()
contextMenu.show({ contextMenu.show({
id: Constants.dom.entryContextMenuId(entry), id: Constants.dom.entryContextMenuId(entry),
event, event,
}) })
} }
const bodyClicked = (entry: ExpendableEntry) => { const bodyClicked = (entry: ExpendableEntry) => {
if (viewMode !== "expanded") return if (viewMode !== "expanded") return
// entry is already selected // entry is already selected
if (entry.id === selectedEntryId) return if (entry.id === selectedEntryId) return
dispatch( dispatch(
selectEntry({ selectEntry({
entry, entry,
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} }
const swipedLeft = 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(() => {
const listener = throttle(100, () => contextMenu.hideAll()) const listener = throttle(100, () => contextMenu.hideAll())
window.addEventListener("scroll", listener) window.addEventListener("scroll", listener)
return () => window.removeEventListener("scroll", listener) return () => window.removeEventListener("scroll", listener)
}, [contextMenu]) }, [contextMenu])
useEffect(() => { useEffect(() => {
const listener = throttle(100, () => { const listener = throttle(100, () => {
if (viewMode !== "expanded") return if (viewMode !== "expanded") return
if (scrollingToEntry) return if (scrollingToEntry) return
const currentEntry = entries const currentEntry = entries
// use slice to get a copy of the array because reverse mutates the array in-place // use slice to get a copy of the array because reverse mutates the array in-place
.slice() .slice()
.reverse() .reverse()
.find(e => { .find(e => {
const el = document.getElementById(Constants.dom.entryId(e)) const el = document.getElementById(Constants.dom.entryId(e))
return el && !Constants.layout.isTopVisible(el) return el && !Constants.layout.isTopVisible(el)
}) })
if (currentEntry) { if (currentEntry) {
dispatch( dispatch(
selectEntry({ selectEntry({
entry: currentEntry, entry: currentEntry,
expand: false, expand: false,
markAsRead: !!scrollMarks, markAsRead: !!scrollMarks,
scrollToEntry: false, scrollToEntry: false,
}) })
) )
} }
}) })
window.addEventListener("scroll", listener) window.addEventListener("scroll", listener)
return () => window.removeEventListener("scroll", listener) return () => window.removeEventListener("scroll", listener)
}, [dispatch, contextMenu, entries, viewMode, scrollMarks, scrollingToEntry]) }, [dispatch, entries, viewMode, scrollMarks, scrollingToEntry])
useMousetrap("r", async () => await dispatch(reloadEntries())) useMousetrap("r", async () => await dispatch(reloadEntries()))
useMousetrap( useMousetrap(
"j", "j",
async () => async () =>
await dispatch( await dispatch(
selectNextEntry({ selectNextEntry({
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
) )
useMousetrap( useMousetrap(
"n", "n",
async () => async () =>
await dispatch( await dispatch(
selectNextEntry({ selectNextEntry({
expand: false, expand: false,
markAsRead: false, markAsRead: false,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
) )
useMousetrap( useMousetrap(
"k", "k",
async () => async () =>
await dispatch( await dispatch(
selectPreviousEntry({ selectPreviousEntry({
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
) )
useMousetrap( useMousetrap(
"p", "p",
async () => async () =>
await dispatch( await dispatch(
selectPreviousEntry({ selectPreviousEntry({
expand: false, expand: false,
markAsRead: false, markAsRead: false,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
) )
useMousetrap("space", () => { useMousetrap("space", () => {
if (selectedEntry) { if (selectedEntry) {
if (selectedEntry.expanded) { if (selectedEntry.expanded) {
const entryElement = document.getElementById(Constants.dom.entryId(selectedEntry)) const entryElement = document.getElementById(Constants.dom.entryId(selectedEntry))
if (entryElement && Constants.layout.isBottomVisible(entryElement)) { if (entryElement && Constants.layout.isBottomVisible(entryElement)) {
dispatch( dispatch(
selectNextEntry({ selectNextEntry({
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} else { } else {
window.scrollTo({ window.scrollTo({
top: window.scrollY + document.documentElement.clientHeight * 0.8, top: window.scrollY + document.documentElement.clientHeight * 0.8,
behavior: "smooth", behavior: "smooth",
}) })
} }
} else { } else {
dispatch( dispatch(
selectEntry({ selectEntry({
entry: selectedEntry, entry: selectedEntry,
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} }
} else { } else {
dispatch( dispatch(
selectNextEntry({ selectNextEntry({
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} }
}) })
useMousetrap("shift+space", () => { useMousetrap("shift+space", () => {
if (selectedEntry) { if (selectedEntry) {
if (selectedEntry.expanded) { if (selectedEntry.expanded) {
const entryElement = document.getElementById(Constants.dom.entryId(selectedEntry)) const entryElement = document.getElementById(Constants.dom.entryId(selectedEntry))
if (entryElement && Constants.layout.isTopVisible(entryElement)) { if (entryElement && Constants.layout.isTopVisible(entryElement)) {
dispatch( dispatch(
selectPreviousEntry({ selectPreviousEntry({
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} else { } else {
window.scrollTo({ window.scrollTo({
top: window.scrollY - document.documentElement.clientHeight * 0.8, top: window.scrollY - document.documentElement.clientHeight * 0.8,
behavior: "smooth", behavior: "smooth",
}) })
} }
} else { } else {
dispatch( dispatch(
selectPreviousEntry({ selectPreviousEntry({
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} }
} }
}) })
useMousetrap(["o", "enter"], () => { useMousetrap(["o", "enter"], () => {
// toggle expanded status // toggle expanded status
if (!selectedEntry) return if (!selectedEntry) return
dispatch( dispatch(
selectEntry({ selectEntry({
entry: selectedEntry, entry: selectedEntry,
expand: !selectedEntry.expanded, expand: !selectedEntry.expanded,
markAsRead: !selectedEntry.expanded, markAsRead: !selectedEntry.expanded,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
}) })
useMousetrap("v", () => { useMousetrap("v", () => {
// open tab in foreground // open tab in foreground
if (!selectedEntry) return if (!selectedEntry) return
window.open(selectedEntry.url, "_blank", "noreferrer") window.open(selectedEntry.url, "_blank", "noreferrer")
}) })
useMousetrap("b", () => { useMousetrap("b", () => {
if (!selectedEntry) return if (!selectedEntry) return
openLinkInBackgroundTab(selectedEntry.url) openLinkInBackgroundTab(selectedEntry.url)
}) })
useMousetrap("m", () => { useMousetrap("m", () => {
// toggle read status // toggle read status
if (!selectedEntry) return if (!selectedEntry) return
dispatch(markEntry({ entry: selectedEntry, read: !selectedEntry.read })) dispatch(markEntry({ entry: selectedEntry, read: !selectedEntry.read }))
}) })
useMousetrap("s", () => { useMousetrap("s", () => {
// toggle starred status // toggle starred status
if (!selectedEntry) return if (!selectedEntry) return
dispatch(starEntry({ entry: selectedEntry, starred: !selectedEntry.starred })) dispatch(starEntry({ entry: selectedEntry, starred: !selectedEntry.starred }))
}) })
useMousetrap("shift+a", () => { useMousetrap("shift+a", () => {
// mark all entries as read // mark all entries as read
dispatch( dispatch(
markAllEntries({ markAllEntries({
sourceType: source.type, sourceType: source.type,
req: { req: {
id: source.id, id: source.id,
read: true, read: true,
olderThan: Date.now(), olderThan: Date.now(),
insertedBefore: entriesTimestamp, insertedBefore: entriesTimestamp,
}, },
}) })
) )
}) })
useMousetrap("g a", async () => await dispatch(redirectToRootCategory())) useMousetrap("g a", async () => await dispatch(redirectToRootCategory()))
useMousetrap("f", () => dispatch(toggleSidebar())) useMousetrap("f", () => dispatch(toggleSidebar()))
useMousetrap("?", () => useMousetrap("?", () =>
openModal({ openModal({
title: <Trans>Keyboard shortcuts</Trans>, title: <Trans>Keyboard shortcuts</Trans>,
size: "xl", size: "xl",
children: <KeyboardShortcutsHelp />, children: <KeyboardShortcutsHelp />,
}) })
) )
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}
loader={<Box key={0}>{loading && <Loader />}</Box>} loader={<Box key={0}>{loading && <Loader />}</Box>}
> >
{entries.map(entry => ( {entries.map(entry => (
<div <div
key={entry.id} key={entry.id}
ref={el => { ref={el => {
if (el) el.id = Constants.dom.entryId(entry) if (el) el.id = Constants.dom.entryId(entry)
}} }}
> >
<FeedEntry <FeedEntry
entry={entry} entry={entry}
expanded={!!entry.expanded || viewMode === "expanded"} expanded={!!entry.expanded || viewMode === "expanded"}
selected={entry.id === selectedEntryId} selected={entry.id === selectedEntryId}
showSelectionIndicator={entry.id === selectedEntryId && (!entry.expanded || viewMode === "expanded")} showSelectionIndicator={entry.id === selectedEntryId && (!entry.expanded || viewMode === "expanded")}
maxWidth={sidebarVisible ? Constants.layout.entryMaxWidth : undefined} maxWidth={sidebarVisible ? Constants.layout.entryMaxWidth : undefined}
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)}
onSwipedLeft={async () => await swipedLeft(entry)} onSwipedLeft={async () => await swipedLeft(entry)}
/> />
</div> </div>
))} ))}
</InfiniteScroll> </InfiniteScroll>
) )
} }

View File

@@ -1,174 +1,191 @@
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 { useAppSelector } from "app/store"
import { useColorScheme } from "hooks/useColorScheme" import type { Entry, ViewMode } from "app/types"
import { useViewMode } from "hooks/useViewMode" import { FeedEntryCompactHeader } from "components/content/header/FeedEntryCompactHeader"
import React from "react" import { FeedEntryHeader } from "components/content/header/FeedEntryHeader"
import { useSwipeable } from "react-swipeable" import { useMobile } from "hooks/useMobile"
import { tss } from "tss" import { useViewMode } from "hooks/useViewMode"
import { FeedEntryBody } from "./FeedEntryBody" import type React from "react"
import { FeedEntryCompactHeader } from "./FeedEntryCompactHeader" import { useSwipeable } from "react-swipeable"
import { FeedEntryContextMenu } from "./FeedEntryContextMenu" import { tss } from "tss"
import { FeedEntryFooter } from "./FeedEntryFooter" import { FeedEntryBody } from "./FeedEntryBody"
import { FeedEntryHeader } from "./FeedEntryHeader" import { FeedEntryContextMenu } from "./FeedEntryContextMenu"
import { FeedEntryFooter } from "./FeedEntryFooter"
interface FeedEntryProps {
entry: Entry interface FeedEntryProps {
expanded: boolean entry: Entry
selected: boolean expanded: boolean
showSelectionIndicator: boolean selected: boolean
maxWidth?: number showSelectionIndicator: boolean
onHeaderClick: (e: React.MouseEvent) => void maxWidth?: number
onHeaderRightClick: (e: React.MouseEvent) => void onHeaderClick: (e: React.MouseEvent) => void
onBodyClick: (e: React.MouseEvent) => void onHeaderRightClick: (e: React.MouseEvent) => void
onSwipedLeft: () => void onBodyClick: (e: React.MouseEvent) => void
} onSwipedLeft: () => void
}
const useStyles = tss
.withParams<{ const useStyles = tss
theme: MantineTheme .withParams<{
colorScheme: "light" | "dark" read: boolean
read: boolean expanded: boolean
expanded: boolean viewMode: ViewMode
viewMode: ViewMode rtl: boolean
rtl: boolean showSelectionIndicator: boolean
showSelectionIndicator: boolean maxWidth?: number
maxWidth?: number }>()
}>() .create(({ theme, colorScheme, read, expanded, viewMode, rtl, showSelectionIndicator, maxWidth }) => {
.create(({ theme, colorScheme, read, expanded, viewMode, rtl, showSelectionIndicator, maxWidth }) => { let backgroundColor: string
let backgroundColor if (colorScheme === "dark") {
if (colorScheme === "dark") { backgroundColor = read ? "inherit" : theme.colors.dark[5]
backgroundColor = read ? "inherit" : theme.colors.dark[5] } else {
} else { backgroundColor = read && !expanded ? theme.colors.gray[0] : "inherit"
backgroundColor = read && !expanded ? theme.colors.gray[0] : "inherit" }
}
let marginY = 10
let marginY = 10 if (viewMode === "title") {
if (viewMode === "title") { marginY = 2
marginY = 2 } else if (viewMode === "cozy") {
} else if (viewMode === "cozy") { marginY = 6
marginY = 6 }
}
let mobileMarginY = 6
let mobileMarginY = 6 if (viewMode === "title") {
if (viewMode === "title") { mobileMarginY = 2
mobileMarginY = 2 } else if (viewMode === "cozy") {
} else if (viewMode === "cozy") { mobileMarginY = 4
mobileMarginY = 4 }
}
let backgroundHoverColor = backgroundColor
let backgroundHoverColor = backgroundColor if (!expanded && !read) {
if (!expanded && !read) { backgroundHoverColor = colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[1]
backgroundHoverColor = colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[1] }
}
let paperBorderLeftColor = ""
let paperBorderLeftColor if (showSelectionIndicator) {
if (showSelectionIndicator) { const borderLeftColor = colorScheme === "dark" ? theme.colors[theme.primaryColor][4] : theme.colors[theme.primaryColor][6]
const borderLeftColor = colorScheme === "dark" ? theme.colors[theme.primaryColor][4] : theme.colors[theme.primaryColor][6] paperBorderLeftColor = `${borderLeftColor} !important`
paperBorderLeftColor = `${borderLeftColor} !important` }
}
return {
return { paper: {
paper: { backgroundColor,
backgroundColor, borderLeftColor: paperBorderLeftColor,
borderLeftColor: paperBorderLeftColor, marginTop: marginY,
marginTop: marginY, marginBottom: marginY,
marginBottom: marginY, [`@media (max-width: ${Constants.layout.mobileBreakpoint}px)`]: {
[`@media (max-width: ${Constants.layout.mobileBreakpoint}px)`]: { marginTop: mobileMarginY,
marginTop: mobileMarginY, marginBottom: mobileMarginY,
marginBottom: mobileMarginY, },
}, "@media (hover: hover)": {
"@media (hover: hover)": { "&:hover": {
"&:hover": { backgroundColor: backgroundHoverColor,
backgroundColor: backgroundHoverColor, },
}, },
}, },
}, headerLink: {
headerLink: { color: "inherit",
color: "inherit", textDecoration: "none",
textDecoration: "none", },
}, body: {
body: { direction: rtl ? "rtl" : "ltr",
direction: rtl ? "rtl" : "ltr", maxWidth: maxWidth ?? "100%",
maxWidth: maxWidth ?? "100%", },
}, }
} })
})
export function FeedEntry(props: FeedEntryProps) {
export function FeedEntry(props: FeedEntryProps) { const { viewMode } = useViewMode()
const theme = useMantineTheme() const { classes, cx } = useStyles({
const colorScheme = useColorScheme() read: props.entry.read,
const { viewMode } = useViewMode() expanded: props.expanded,
const { classes, cx } = useStyles({ viewMode,
theme, rtl: props.entry.rtl,
colorScheme, showSelectionIndicator: props.showSelectionIndicator,
read: props.entry.read, maxWidth: props.maxWidth,
expanded: props.expanded, })
viewMode,
rtl: props.entry.rtl, const externalLinkDisplayMode = useAppSelector(state => state.user.settings?.externalLinkIconDisplayMode)
showSelectionIndicator: props.showSelectionIndicator, const starIconDisplayMode = useAppSelector(state => state.user.settings?.starIconDisplayMode)
maxWidth: props.maxWidth, const mobile = useMobile()
})
const showExternalLinkIcon =
const swipeHandlers = useSwipeable({ externalLinkDisplayMode && ["always", mobile ? "on_mobile" : "on_desktop"].includes(externalLinkDisplayMode)
onSwipedLeft: props.onSwipedLeft, const showStarIcon =
}) props.entry.markable && starIconDisplayMode && ["always", mobile ? "on_mobile" : "on_desktop"].includes(starIconDisplayMode)
let paddingX: MantineSpacing = "xs" const swipeHandlers = useSwipeable({
if (viewMode === "title" || viewMode === "cozy") paddingX = 6 onSwipedLeft: props.onSwipedLeft,
})
let paddingY: MantineSpacing = "xs"
if (viewMode === "title") { let paddingX: MantineSpacing = "xs"
paddingY = 4 if (viewMode === "title" || viewMode === "cozy") paddingX = 6
} else if (viewMode === "cozy") {
paddingY = 8 let paddingY: MantineSpacing = "xs"
} if (viewMode === "title") {
paddingY = 4
let borderRadius: MantineRadius = "sm" } else if (viewMode === "cozy") {
if (viewMode === "title") { paddingY = 8
borderRadius = 0 }
} else if (viewMode === "cozy") {
borderRadius = "xs" let borderRadius: MantineRadius = "sm"
} if (viewMode === "title") {
borderRadius = 0
const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy") } else if (viewMode === "cozy") {
return ( borderRadius = "xs"
<Paper }
withBorder
radius={borderRadius} const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy")
className={cx(classes.paper, { return (
read: props.entry.read, <Paper
unread: !props.entry.read, withBorder
expanded: props.expanded, radius={borderRadius}
selected: props.selected, className={cx(classes.paper, {
"show-selection-indicator": props.showSelectionIndicator, read: props.entry.read,
})} unread: !props.entry.read,
> expanded: props.expanded,
<a selected: props.selected,
className={classes.headerLink} "show-selection-indicator": props.showSelectionIndicator,
href={props.entry.url} })}
target="_blank" >
rel="noreferrer" <a
onClick={props.onHeaderClick} className={classes.headerLink}
onAuxClick={props.onHeaderClick} href={props.entry.url}
onContextMenu={props.onHeaderRightClick} target="_blank"
> rel="noreferrer"
<Box px={paddingX} py={paddingY} {...swipeHandlers}> onClick={props.onHeaderClick}
{compactHeader && <FeedEntryCompactHeader entry={props.entry} />} onAuxClick={props.onHeaderClick}
{!compactHeader && <FeedEntryHeader entry={props.entry} expanded={props.expanded} />} onContextMenu={props.onHeaderRightClick}
</Box> >
</a> <Box px={paddingX} py={paddingY} {...swipeHandlers}>
{props.expanded && ( {compactHeader && (
<Box px={paddingX} pb={paddingY} onClick={props.onBodyClick}> <FeedEntryCompactHeader
<Box className={classes.body}> entry={props.entry}
<FeedEntryBody entry={props.entry} /> showStarIcon={showStarIcon}
</Box> showExternalLinkIcon={showExternalLinkIcon}
<Divider variant="dashed" my={paddingY} /> />
<FeedEntryFooter entry={props.entry} /> )}
</Box> {!compactHeader && (
)} <FeedEntryHeader
entry={props.entry}
<FeedEntryContextMenu entry={props.entry} /> expanded={props.expanded}
</Paper> showStarIcon={showStarIcon}
) showExternalLinkIcon={showExternalLinkIcon}
} />
)}
</Box>
</a>
{props.expanded && (
<Box px={paddingX} pb={paddingY} onClick={props.onBodyClick}>
<Box className={classes.body}>
<FeedEntryBody entry={props.entry} />
</Box>
<Divider variant="dashed" my={paddingY} />
<FeedEntryFooter entry={props.entry} />
</Box>
)}
<FeedEntryContextMenu entry={props.entry} />
</Paper>
)
}

View File

@@ -1,37 +1,37 @@
import { Box } from "@mantine/core" import { Box } from "@mantine/core"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
import { type Entry } from "app/types" import type { Entry } from "app/types"
import { Content } from "./Content" import { Content } from "./Content"
import { Enclosure } from "./Enclosure" import { Enclosure } from "./Enclosure"
import { Media } from "./Media" import { Media } from "./Media"
export interface FeedEntryBodyProps { export interface FeedEntryBodyProps {
entry: Entry entry: Entry
} }
export function FeedEntryBody(props: FeedEntryBodyProps) { export function FeedEntryBody(props: FeedEntryBodyProps) {
const search = useAppSelector(state => state.entries.search) const search = useAppSelector(state => state.entries.search)
return ( return (
<Box> <Box>
<Box> <Box>
<Content content={props.entry.content} highlight={search} /> <Content content={props.entry.content} highlight={search} />
</Box> </Box>
{props.entry.enclosureType && props.entry.enclosureUrl && ( {props.entry.enclosureType && props.entry.enclosureUrl && (
<Box pt="md"> <Box pt="md">
<Enclosure enclosureType={props.entry.enclosureType} enclosureUrl={props.entry.enclosureUrl} /> <Enclosure enclosureType={props.entry.enclosureType} enclosureUrl={props.entry.enclosureUrl} />
</Box> </Box>
)} )}
{/* show media only if we don't have content to avoid duplicate content */} {/* show media only if we don't have content to avoid duplicate content */}
{!props.entry.content && props.entry.mediaThumbnailUrl && ( {!props.entry.content && props.entry.mediaThumbnailUrl && (
<Box pt="md"> <Box pt="md">
<Media <Media
thumbnailUrl={props.entry.mediaThumbnailUrl} thumbnailUrl={props.entry.mediaThumbnailUrl}
thumbnailWidth={props.entry.mediaThumbnailWidth} thumbnailWidth={props.entry.mediaThumbnailWidth}
thumbnailHeight={props.entry.mediaThumbnailHeight} thumbnailHeight={props.entry.mediaThumbnailHeight}
description={props.entry.mediaDescription} description={props.entry.mediaDescription}
/> />
</Box> </Box>
)} )}
</Box> </Box>
) )
} }

View File

@@ -1,110 +1,103 @@
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"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { type Entry } from "app/types" import type { Entry } from "app/types"
import { truncate } from "app/utils" import { truncate } from "app/utils"
import { useBrowserExtension } from "hooks/useBrowserExtension" import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useColorScheme } from "hooks/useColorScheme" import { useColorScheme } from "hooks/useColorScheme"
import { Item, Menu, Separator } from "react-contexify" import { Item, Menu, Separator } from "react-contexify"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb" import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb"
import { tss } from "tss" import { tss } from "tss"
interface FeedEntryContextMenuProps { interface FeedEntryContextMenuProps {
entry: Entry entry: Entry
} }
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`, export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
"--contexify-activeItem-bgColor": `${colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]} !important`, const colorScheme = useColorScheme()
}, const { classes } = useStyles()
})) const sourceType = useAppSelector(state => state.entries.source.type)
const dispatch = useAppDispatch()
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) { const { openLinkInBackgroundTab } = useBrowserExtension()
const theme = useMantineTheme()
const colorScheme = useColorScheme() return (
const { classes } = useStyles({ <Menu id={Constants.dom.entryContextMenuId(props.entry)} theme={colorScheme} animation={false} className={classes.menu}>
theme, <Item
colorScheme, onClick={() => {
}) window.open(props.entry.url, "_blank", "noreferrer")
const sourceType = useAppSelector(state => state.entries.source.type) dispatch(markEntry({ entry: props.entry, read: true }))
const dispatch = useAppDispatch() }}
const { openLinkInBackgroundTab } = useBrowserExtension() >
<Group>
return ( <TbExternalLink size={iconSize} />
<Menu id={Constants.dom.entryContextMenuId(props.entry)} theme={colorScheme} animation={false} className={classes.menu}> <Trans>Open link in new tab</Trans>
<Item </Group>
onClick={() => { </Item>
window.open(props.entry.url, "_blank", "noreferrer") <Item
dispatch(markEntry({ entry: props.entry, read: true })) onClick={() => {
}} openLinkInBackgroundTab(props.entry.url)
> dispatch(markEntry({ entry: props.entry, read: true }))
<Group> }}
<TbExternalLink size={iconSize} /> >
<Trans>Open link in new tab</Trans> <Group>
</Group> <TbExternalLink size={iconSize} />
</Item> <Trans>Open link in new background tab</Trans>
<Item </Group>
onClick={() => { </Item>
openLinkInBackgroundTab(props.entry.url)
dispatch(markEntry({ entry: props.entry, read: true })) <Separator />
}}
> <Item onClick={async () => await dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}>
<Group> <Group>
<TbExternalLink size={iconSize} /> {props.entry.starred ? <TbStarOff size={iconSize} /> : <TbStar size={iconSize} />}
<Trans>Open link in new background tab</Trans> {props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
</Group> </Group>
</Item> </Item>
{props.entry.markable && (
<Separator /> <Item onClick={async () => await dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))}>
<Group>
<Item onClick={async () => await dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}> {props.entry.read ? <TbEyeOff size={iconSize} /> : <TbEyeCheck size={iconSize} />}
<Group> {props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>}
{props.entry.starred ? <TbStarOff size={iconSize} /> : <TbStar size={iconSize} />} </Group>
{props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>} </Item>
</Group> )}
</Item> <Item onClick={async () => await dispatch(markEntriesUpToEntry(props.entry))}>
<Item onClick={async () => await dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))}> <Group>
<Group> <TbArrowBarToDown size={iconSize} />
{props.entry.read ? <TbEyeOff size={iconSize} /> : <TbEyeCheck size={iconSize} />} <Trans>Mark as read up to here</Trans>
{props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>} </Group>
</Group> </Item>
</Item>
<Item onClick={async () => await dispatch(markEntriesUpToEntry(props.entry))}> {sourceType === "category" && (
<Group> <>
<TbArrowBarToDown size={iconSize} /> <Separator />
<Trans>Mark as read up to here</Trans>
</Group> <Item
</Item> onClick={() => {
dispatch(redirectToFeed(props.entry.feedId))
{sourceType === "category" && ( }}
<> >
<Separator /> <Group>
<TbRss size={iconSize} />
<Item <Trans>Go to {truncate(props.entry.feedName, 30)}</Trans>
onClick={() => { </Group>
dispatch(redirectToFeed(props.entry.feedId)) </Item>
}} </>
> )}
<Group> </Menu>
<TbRss size={iconSize} /> )
<Trans>Go to {truncate(props.entry.feedName, 30)}</Trans> }
</Group>
</Item>
</>
)}
</Menu>
)
}

View File

@@ -1,107 +1,102 @@
import { t, Trans } from "@lingui/macro" import { Trans, t } from "@lingui/macro"
import { Group, Indicator, Popover, TagsInput } from "@mantine/core" import { Group, Indicator, Popover, TagsInput } from "@mantine/core"
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks" import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { type Entry } from "app/types" import type { Entry } from "app/types"
import { ActionButton } from "components/ActionButton" import { ActionButton } from "components/ActionButton"
import { useActionButton } from "hooks/useActionButton" import { useActionButton } from "hooks/useActionButton"
import { useMobile } from "hooks/useMobile" import { useMobile } from "hooks/useMobile"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbShare, TbStar, TbStarOff, TbTag } from "react-icons/tb" import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbShare, TbStar, TbStarOff, TbTag } from "react-icons/tb"
import { ShareButtons } from "./ShareButtons" import { ShareButtons } from "./ShareButtons"
interface FeedEntryFooterProps { interface FeedEntryFooterProps {
entry: Entry entry: Entry
} }
export function FeedEntryFooter(props: FeedEntryFooterProps) { export function FeedEntryFooter(props: FeedEntryFooterProps) {
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings) const tags = useAppSelector(state => state.user.tags)
const tags = useAppSelector(state => state.user.tags) const mobile = useMobile()
const mobile = useMobile() const { spacing } = useActionButton()
const { spacing } = useActionButton() const dispatch = useAppDispatch()
const dispatch = useAppDispatch()
const readStatusButtonClicked = async () =>
const showSharingButtons = sharingSettings && Object.values(sharingSettings).some(v => v) await dispatch(
markEntry({
const readStatusButtonClicked = async () => entry: props.entry,
await dispatch( read: !props.entry.read,
markEntry({ })
entry: props.entry, )
read: !props.entry.read, const onTagsChange = async (values: string[]) =>
}) await dispatch(
) tagEntry({
const onTagsChange = async (values: string[]) => entryId: +props.entry.id,
await dispatch( tags: values,
tagEntry({ })
entryId: +props.entry.id, )
tags: values,
}) return (
) <Group justify="space-between">
<Group gap={spacing}>
return ( {props.entry.markable && (
<Group justify="space-between"> <ActionButton
<Group gap={spacing}> icon={props.entry.read ? <TbEyeOff size={18} /> : <TbEyeCheck size={18} />}
{props.entry.markable && ( label={props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>}
<ActionButton onClick={readStatusButtonClicked}
icon={props.entry.read ? <TbEyeOff size={18} /> : <TbEyeCheck size={18} />} />
label={props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>} )}
onClick={readStatusButtonClicked} <ActionButton
/> icon={props.entry.starred ? <TbStarOff size={18} /> : <TbStar size={18} />}
)} label={props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
<ActionButton onClick={async () =>
icon={props.entry.starred ? <TbStarOff size={18} /> : <TbStar size={18} />} await dispatch(
label={props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>} starEntry({
onClick={async () => entry: props.entry,
await dispatch( starred: !props.entry.starred,
starEntry({ })
entry: props.entry, )
starred: !props.entry.starred, }
}) />
)
} <Popover withArrow withinPortal shadow="md" closeOnClickOutside={!mobile}>
/> <Popover.Target>
<ActionButton icon={<TbShare size={18} />} label={<Trans>Share</Trans>} />
{showSharingButtons && ( </Popover.Target>
<Popover withArrow withinPortal shadow="md" closeOnClickOutside={!mobile}> <Popover.Dropdown>
<Popover.Target> <ShareButtons url={props.entry.url} description={props.entry.title} />
<ActionButton icon={<TbShare size={18} />} label={<Trans>Share</Trans>} /> </Popover.Dropdown>
</Popover.Target> </Popover>
<Popover.Dropdown>
<ShareButtons url={props.entry.url} description={props.entry.title} /> {tags && (
</Popover.Dropdown> <Popover withArrow shadow="md" closeOnClickOutside={!mobile}>
</Popover> <Popover.Target>
)} <Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}>
<ActionButton icon={<TbTag size={18} />} label={<Trans>Tags</Trans>} />
{tags && ( </Indicator>
<Popover withArrow shadow="md" closeOnClickOutside={!mobile}> </Popover.Target>
<Popover.Target> <Popover.Dropdown>
<Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}> <TagsInput
<ActionButton icon={<TbTag size={18} />} label={<Trans>Tags</Trans>} /> placeholder={t`Tags`}
</Indicator> data={tags}
</Popover.Target> value={props.entry.tags}
<Popover.Dropdown> onChange={onTagsChange}
<TagsInput comboboxProps={{
placeholder={t`Tags`} withinPortal: false,
data={tags} }}
value={props.entry.tags} />
onChange={onTagsChange} </Popover.Dropdown>
comboboxProps={{ </Popover>
withinPortal: false, )}
}}
/> <a href={props.entry.url} target="_blank" rel="noreferrer">
</Popover.Dropdown> <ActionButton icon={<TbExternalLink size={18} />} label={<Trans>Open link</Trans>} />
</Popover> </a>
)} </Group>
<a href={props.entry.url} target="_blank" rel="noreferrer"> <ActionButton
<ActionButton icon={<TbExternalLink size={18} />} label={<Trans>Open link</Trans>} /> icon={<TbArrowBarToDown size={18} />}
</a> label={<Trans>Mark as read up to here</Trans>}
</Group> onClick={async () => await dispatch(markEntriesUpToEntry(props.entry))}
/>
<ActionButton </Group>
icon={<TbArrowBarToDown size={18} />} )
label={<Trans>Mark as read up to here</Trans>} }
onClick={async () => await dispatch(markEntriesUpToEntry(props.entry))}
/>
</Group>
)
}

View File

@@ -1,21 +1,21 @@
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
export interface FeedFaviconProps { export interface FeedFaviconProps {
url: string url: string
size?: number size?: number
} }
export function FeedFavicon({ url, size = 18 }: FeedFaviconProps) { export function FeedFavicon({ url, size = 18 }: FeedFaviconProps) {
return ( return (
<ImageWithPlaceholderWhileLoading <ImageWithPlaceholderWhileLoading
src={url} src={url}
alt="feed favicon" alt="feed favicon"
width={size} width={size}
height={size} height={size}
placeholderWidth={size} placeholderWidth={size}
placeholderHeight={size} placeholderHeight={size}
placeholderBackgroundColor="inherit" placeholderBackgroundColor="inherit"
placeholderIconSize={size} placeholderIconSize={size}
/> />
) )
} }

View File

@@ -1,40 +1,40 @@
import { Box } from "@mantine/core" import { Box } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { calculatePlaceholderSize } from "app/utils" import { calculatePlaceholderSize } from "app/utils"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { Content } from "./Content" import { Content } from "./Content"
export interface MediaProps { export interface MediaProps {
thumbnailUrl: string thumbnailUrl: string
thumbnailWidth?: number thumbnailWidth?: number
thumbnailHeight?: number thumbnailHeight?: number
description?: string description?: string
} }
export function Media(props: MediaProps) { export function Media(props: MediaProps) {
const width = props.thumbnailWidth const width = props.thumbnailWidth
const height = props.thumbnailHeight const height = props.thumbnailHeight
const placeholderSize = calculatePlaceholderSize({ const placeholderSize = calculatePlaceholderSize({
width, width,
height, height,
maxWidth: Constants.layout.entryMaxWidth, maxWidth: Constants.layout.entryMaxWidth,
}) })
return ( return (
<BasicHtmlStyles> <BasicHtmlStyles>
<ImageWithPlaceholderWhileLoading <ImageWithPlaceholderWhileLoading
src={props.thumbnailUrl} src={props.thumbnailUrl}
alt="media thumbnail" alt="media thumbnail"
width={props.thumbnailWidth} width={props.thumbnailWidth}
height={props.thumbnailHeight} height={props.thumbnailHeight}
placeholderWidth={placeholderSize.width} placeholderWidth={placeholderSize.width}
placeholderHeight={placeholderSize.height} placeholderHeight={placeholderSize.height}
/> />
{props.description && ( {props.description && (
<Box pt="md"> <Box pt="md">
<Content content={props.description} /> <Content content={props.description} />
</Box> </Box>
)} )}
</BasicHtmlStyles> </BasicHtmlStyles>
) )
} }

View File

@@ -1,69 +1,113 @@
import { ActionIcon, Box, type MantineTheme, SimpleGrid, useMantineTheme } from "@mantine/core" import { Trans } from "@lingui/macro"
import { Constants } from "app/constants" import { ActionIcon, Box, CopyButton, Divider, SimpleGrid } from "@mantine/core"
import { useAppSelector } from "app/store" import { Constants } from "app/constants"
import { type SharingSettings } from "app/types" import { useAppSelector } from "app/store"
import { useColorScheme } from "hooks/useColorScheme" import type { SharingSettings } from "app/types"
import { type IconType } from "react-icons" import { useBrowserExtension } from "hooks/useBrowserExtension"
import { tss } from "tss" import { useMobile } from "hooks/useMobile"
import type { IconType } from "react-icons"
type Color = `#${string}` import { TbCheck, TbCopy, TbDeviceDesktopShare, TbDeviceMobileShare } from "react-icons/tb"
import { tss } from "tss"
const useStyles = tss
.withParams<{ type Color = `#${string}`
theme: MantineTheme
colorScheme: "light" | "dark" const useStyles = tss
color: Color .withParams<{
}>() color: Color
.create(({ theme, colorScheme, color }) => ({ }>()
socialIcon: { .create(({ theme, colorScheme, color }) => ({
color, icon: {
backgroundColor: colorScheme === "dark" ? theme.colors.gray[2] : "white", color,
borderRadius: "50%", backgroundColor: colorScheme === "dark" ? theme.colors.gray[2] : "white",
}, },
})) }))
function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) { function ShareButton({ icon, color, onClick }: { icon: IconType; color: Color; onClick: () => void }) {
const theme = useMantineTheme() const { classes } = useStyles({
const colorScheme = useColorScheme() color,
const { classes } = useStyles({ })
theme,
colorScheme, return (
color, <ActionIcon variant="transparent" radius="xl" size={32}>
}) <Box p={6} className={classes.icon} onClick={onClick}>
{icon({ size: 18 })}
const onClick = (e: React.MouseEvent) => { </Box>
e.preventDefault() </ActionIcon>
window.open(url, "", "menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=800,height=600") )
} }
return ( function SiteShareButton({ url, icon, color }: { icon: IconType; color: Color; url: string }) {
<ActionIcon variant="transparent"> const onClick = () => {
<a href={url} target="_blank" rel="noreferrer" onClick={onClick}> window.open(url, "", "menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=800,height=600")
<Box p={6} className={classes.socialIcon}> }
{icon({ size: 18 })}
</Box> return <ShareButton icon={icon} color={color} onClick={onClick} />
</a> }
</ActionIcon>
) function CopyUrlButton({ url }: { url: string }) {
} return (
<CopyButton value={url}>
export function ShareButtons(props: { url: string; description: string }) { {({ copied, copy }) => <ShareButton icon={copied ? TbCheck : TbCopy} color="#000" onClick={copy} />}
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings) </CopyButton>
const url = encodeURIComponent(props.url) )
const desc = encodeURIComponent(props.description) }
return ( function BrowserNativeShareButton({ url, description }: { url: string; description: string }) {
<SimpleGrid cols={4}> const mobile = useMobile()
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>) const { isBrowserExtensionPopup } = useBrowserExtension()
.filter(site => sharingSettings?.[site]) const onClick = () => {
.map(site => ( navigator.share({
<ShareButton title: description,
key={site} url,
icon={Constants.sharing[site].icon} })
color={Constants.sharing[site].color} }
url={Constants.sharing[site].url(url, desc)}
/> return (
))} <ShareButton
</SimpleGrid> icon={mobile && !isBrowserExtensionPopup ? TbDeviceMobileShare : TbDeviceDesktopShare}
) color="#000"
} onClick={onClick}
/>
)
}
export function ShareButtons(props: { url: string; description: string }) {
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const enabledSharingSites = (Object.keys(Constants.sharing) as Array<keyof SharingSettings>).filter(site => sharingSettings?.[site])
const url = encodeURIComponent(props.url)
const desc = encodeURIComponent(props.description)
const clipboardAvailable = typeof navigator.clipboard !== "undefined"
const nativeSharingAvailable = typeof navigator.share !== "undefined"
const showNativeSection = clipboardAvailable || nativeSharingAvailable
const showSharingSites = enabledSharingSites.length > 0
const showDivider = showNativeSection && showSharingSites
const showNoSharingOptionsAvailable = !showNativeSection && !showSharingSites
return (
<>
{showNativeSection && (
<SimpleGrid cols={4}>
{clipboardAvailable && <CopyUrlButton url={props.url} />}
{nativeSharingAvailable && <BrowserNativeShareButton url={props.url} description={props.description} />}
</SimpleGrid>
)}
{showDivider && <Divider my="xs" />}
{showSharingSites && (
<SimpleGrid cols={4}>
{enabledSharingSites.map(site => (
<SiteShareButton
key={site}
icon={Constants.sharing[site].icon}
color={Constants.sharing[site].color}
url={Constants.sharing[site].url(url, desc)}
/>
))}
</SimpleGrid>
)}
{showNoSharingOptionsAvailable && <Trans>No sharing options available.</Trans>}
</>
)
}

View File

@@ -1,50 +1,50 @@
import { t, Trans } from "@lingui/macro" import { Trans, t } from "@lingui/macro"
import { Box, Button, Group, Stack, TextInput } from "@mantine/core" import { Box, Button, Group, Stack, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client" import { client, errorToStrings } from "app/client"
import { redirectToSelectedSource } from "app/redirect/thunks" import { redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch } from "app/store" import { useAppDispatch } from "app/store"
import { reloadTree } from "app/tree/thunks" import { reloadTree } from "app/tree/thunks"
import { type AddCategoryRequest } from "app/types" import type { AddCategoryRequest } from "app/types"
import { Alert } from "components/Alert" import { Alert } from "components/Alert"
import { useAsyncCallback } from "react-async-hook" import { useAsyncCallback } from "react-async-hook"
import { TbFolderPlus } from "react-icons/tb" import { TbFolderPlus } from "react-icons/tb"
import { CategorySelect } from "./CategorySelect" import { CategorySelect } from "./CategorySelect"
export function AddCategory() { export function AddCategory() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const form = useForm<AddCategoryRequest>() const form = useForm<AddCategoryRequest>()
const addCategory = useAsyncCallback(client.category.add, { const addCategory = useAsyncCallback(client.category.add, {
onSuccess: () => { onSuccess: () => {
dispatch(reloadTree()) dispatch(reloadTree())
dispatch(redirectToSelectedSource()) dispatch(redirectToSelectedSource())
}, },
}) })
return ( return (
<> <>
{addCategory.error && ( {addCategory.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={errorToStrings(addCategory.error)} /> <Alert messages={errorToStrings(addCategory.error)} />
</Box> </Box>
)} )}
<form onSubmit={form.onSubmit(addCategory.execute)}> <form onSubmit={form.onSubmit(addCategory.execute)}>
<Stack> <Stack>
<TextInput label={<Trans>Category</Trans>} placeholder={t`Category`} {...form.getInputProps("name")} required /> <TextInput label={<Trans>Category</Trans>} placeholder={t`Category`} {...form.getInputProps("name")} required />
<CategorySelect label={<Trans>Parent</Trans>} {...form.getInputProps("parentId")} clearable /> <CategorySelect label={<Trans>Parent</Trans>} {...form.getInputProps("parentId")} clearable />
<Group justify="center"> <Group justify="center">
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftSection={<TbFolderPlus size={16} />} loading={addCategory.loading}> <Button type="submit" leftSection={<TbFolderPlus size={16} />} loading={addCategory.loading}>
<Trans>Add</Trans> <Trans>Add</Trans>
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</form> </form>
</> </>
) )
} }

View File

@@ -1,51 +1,52 @@
import { t } from "@lingui/macro" import { t } from "@lingui/macro"
import { Select, type SelectProps } from "@mantine/core" 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 type { Category } from "app/types"
import { flattenCategoryTree } from "app/utils" import { flattenCategoryTree } from "app/utils"
type CategorySelectProps = Partial<SelectProps> & { type CategorySelectProps = Partial<SelectProps> & {
withAll?: boolean withAll?: boolean
withoutCategoryIds?: string[] withoutCategoryIds?: string[]
} }
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) => { const categoriesById = categories?.reduce((map, c) => {
map.set(c.id, c) map.set(c.id, c)
return map return map
}, new Map<string, Category>()) }, new Map<string, Category>())
const categoryLabel = (cat: Category) => { const categoryLabel = (category: Category) => {
let label = cat.name let cat = category
let label = cat.name
while (cat.parentId) {
const parent = categoriesById?.get(cat.parentId) while (cat.parentId) {
if (!parent) { const parent = categoriesById?.get(cat.parentId)
break if (!parent) {
} break
label = `${parent.name}${label}` }
cat = parent label = `${parent.name}${label}`
} cat = parent
}
return label
} return label
const selectData: ComboboxItem[] | undefined = categories }
?.filter(c => c.id !== Constants.categories.all.id) const selectData: ComboboxItem[] | undefined = categories
.filter(c => !props.withoutCategoryIds?.includes(c.id)) ?.filter(c => c.id !== Constants.categories.all.id)
.map(c => ({ .filter(c => !props.withoutCategoryIds?.includes(c.id))
label: categoryLabel(c), .map(c => ({
value: c.id, label: categoryLabel(c),
})) value: c.id,
.sort((c1, c2) => c1.label.localeCompare(c2.label)) }))
if (props.withAll) { .sort((c1, c2) => c1.label.localeCompare(c2.label))
selectData?.unshift({ if (props.withAll) {
label: t`All`, selectData?.unshift({
value: Constants.categories.all.id, label: t`All`,
}) value: Constants.categories.all.id,
} })
}
return <Select {...props} data={selectData ?? []} disabled={!selectData} />
} return <Select {...props} data={selectData ?? []} disabled={!selectData} />
}

View File

@@ -1,65 +1,64 @@
import { t, Trans } from "@lingui/macro" import { Trans, t } from "@lingui/macro"
import { Box, Button, FileInput, Group, Stack } from "@mantine/core" import { Box, Button, FileInput, Group, Stack } from "@mantine/core"
import { useForm } from "@mantine/form" import { isNotEmpty, useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client" import { client, errorToStrings } from "app/client"
import { redirectToSelectedSource } from "app/redirect/thunks" import { redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch } from "app/store" import { useAppDispatch } from "app/store"
import { reloadTree } from "app/tree/thunks" import { reloadTree } from "app/tree/thunks"
import { Alert } from "components/Alert" import { Alert } from "components/Alert"
import { useAsyncCallback } from "react-async-hook" import { useAsyncCallback } from "react-async-hook"
import { TbFileImport } from "react-icons/tb" import { TbFileImport } from "react-icons/tb"
export function ImportOpml() { export function ImportOpml() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const form = useForm<{ file: File }>({ const form = useForm<{ file: File }>({
validate: { validate: {
file: v => (v ? null : t`file is required`), file: isNotEmpty(t`OPML file is required`),
}, },
}) })
const importOpml = useAsyncCallback(client.feed.importOpml, { const importOpml = useAsyncCallback(client.feed.importOpml, {
onSuccess: () => { onSuccess: () => {
dispatch(reloadTree()) dispatch(reloadTree())
dispatch(redirectToSelectedSource()) dispatch(redirectToSelectedSource())
}, },
}) })
return ( return (
<> <>
{importOpml.error && ( {importOpml.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={errorToStrings(importOpml.error)} /> <Alert messages={errorToStrings(importOpml.error)} />
</Box> </Box>
)} )}
<form onSubmit={form.onSubmit(async v => await importOpml.execute(v.file))}> <form onSubmit={form.onSubmit(async v => await importOpml.execute(v.file))}>
<Stack> <Stack>
<FileInput <FileInput
label={<Trans>OPML file</Trans>} label={<Trans>OPML file</Trans>}
leftSection={<TbFileImport />} leftSection={<TbFileImport />}
// https://github.com/mantinedev/mantine/issues/5401 placeholder={t`OPML file`}
{...{ placeholder: t`OPML file` }} description={
description={ <Trans>
<Trans> An opml file is an XML file containing feed URLs and categories. You can get an OPML file by exporting your
An opml file is an XML file containing feed URLs and categories. You can get an OPML file by exporting your data from other feed reading services.
data from other feed reading services. </Trans>
</Trans> }
} {...form.getInputProps("file")}
{...form.getInputProps("file")} required
required accept=".xml,.opml"
accept="application/xml" />
/> <Group justify="center">
<Group justify="center"> <Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}> <Trans>Cancel</Trans>
<Trans>Cancel</Trans> </Button>
</Button> <Button type="submit" leftSection={<TbFileImport size={16} />} loading={importOpml.loading}>
<Button type="submit" leftSection={<TbFileImport size={16} />} loading={importOpml.loading}> <Trans>Import</Trans>
<Trans>Import</Trans> </Button>
</Button> </Group>
</Group> </Stack>
</Stack> </form>
</form> </>
</> )
) }
}

View File

@@ -1,129 +1,129 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core" import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { client, errorToStrings } from "app/client" import { client, errorToStrings } from "app/client"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { redirectToFeed, redirectToSelectedSource } from "app/redirect/thunks" import { redirectToFeed, redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch } from "app/store" import { useAppDispatch } from "app/store"
import { reloadTree } from "app/tree/thunks" import { reloadTree } from "app/tree/thunks"
import { type FeedInfoRequest, type SubscribeRequest } from "app/types" import type { FeedInfoRequest, SubscribeRequest } from "app/types"
import { Alert } from "components/Alert" import { Alert } from "components/Alert"
import { useState } from "react" import { useState } from "react"
import { useAsyncCallback } from "react-async-hook" import { useAsyncCallback } from "react-async-hook"
import { TbRss } from "react-icons/tb" import { TbRss } from "react-icons/tb"
import { CategorySelect } from "./CategorySelect" import { CategorySelect } from "./CategorySelect"
export function Subscribe() { export function Subscribe() {
const [activeStep, setActiveStep] = useState(0) const [activeStep, setActiveStep] = useState(0)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const step0Form = useForm<FeedInfoRequest>({ const step0Form = useForm<FeedInfoRequest>({
initialValues: { initialValues: {
url: "", url: "",
}, },
}) })
const step1Form = useForm<SubscribeRequest>({ const step1Form = useForm<SubscribeRequest>({
initialValues: { initialValues: {
url: "", url: "",
title: "", title: "",
categoryId: Constants.categories.all.id, categoryId: Constants.categories.all.id,
}, },
}) })
const fetchFeed = useAsyncCallback(client.feed.fetchFeed, { const fetchFeed = useAsyncCallback(client.feed.fetchFeed, {
onSuccess: ({ data }) => { onSuccess: ({ data }) => {
step1Form.setFieldValue("url", data.url) step1Form.setFieldValue("url", data.url)
step1Form.setFieldValue("title", data.title) step1Form.setFieldValue("title", data.title)
setActiveStep(step => step + 1) setActiveStep(step => step + 1)
}, },
}) })
const subscribe = useAsyncCallback(client.feed.subscribe, { const subscribe = useAsyncCallback(client.feed.subscribe, {
onSuccess: sub => { onSuccess: sub => {
dispatch(reloadTree()) dispatch(reloadTree())
dispatch(redirectToFeed(sub.data)) dispatch(redirectToFeed(sub.data))
}, },
}) })
const previousStep = () => { const previousStep = () => {
if (activeStep === 0) { if (activeStep === 0) {
dispatch(redirectToSelectedSource()) dispatch(redirectToSelectedSource())
} else { } else {
setActiveStep(activeStep - 1) setActiveStep(activeStep - 1)
} }
} }
const nextStep = (e: React.FormEvent<HTMLFormElement>) => { const nextStep = (e: React.FormEvent<HTMLFormElement>) => {
if (activeStep === 0) { if (activeStep === 0) {
step0Form.onSubmit(fetchFeed.execute)(e) step0Form.onSubmit(fetchFeed.execute)(e)
} else if (activeStep === 1) { } else if (activeStep === 1) {
step1Form.onSubmit(subscribe.execute)(e) step1Form.onSubmit(subscribe.execute)(e)
} }
} }
return ( return (
<> <>
{fetchFeed.error && ( {fetchFeed.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={errorToStrings(fetchFeed.error)} /> <Alert messages={errorToStrings(fetchFeed.error)} />
</Box> </Box>
)} )}
{subscribe.error && ( {subscribe.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={errorToStrings(subscribe.error)} /> <Alert messages={errorToStrings(subscribe.error)} />
</Box> </Box>
)} )}
<form onSubmit={nextStep}> <form onSubmit={nextStep}>
<Stepper active={activeStep} onStepClick={setActiveStep}> <Stepper active={activeStep} onStepClick={setActiveStep}>
<Stepper.Step <Stepper.Step
label={<Trans>Analyze feed</Trans>} label={<Trans>Analyze feed</Trans>}
description={<Trans>Check that the feed is working</Trans>} description={<Trans>Check that the feed is working</Trans>}
allowStepSelect={activeStep === 1} allowStepSelect={activeStep === 1}
> >
<TextInput <TextInput
label={<Trans>Feed URL</Trans>} label={<Trans>Feed URL</Trans>}
placeholder="https://www.mysite.com/rss" placeholder="https://www.mysite.com/rss"
description={ description={
<Trans> <Trans>
The URL for the feed you want to subscribe to. You can also use the website's url directly and CommaFeed The URL for the feed you want to subscribe to. You can also use the website's url directly and CommaFeed
will try to find the feed in the page. will try to find the feed in the page.
</Trans> </Trans>
} }
required required
autoFocus autoFocus
{...step0Form.getInputProps("url")} {...step0Form.getInputProps("url")}
/> />
</Stepper.Step> </Stepper.Step>
<Stepper.Step <Stepper.Step
label={<Trans>Subscribe</Trans>} label={<Trans>Subscribe</Trans>}
description={<Trans>Subscribe to the feed</Trans>} description={<Trans>Subscribe to the feed</Trans>}
allowStepSelect={false} allowStepSelect={false}
> >
<Stack> <Stack>
<TextInput label={<Trans>Feed URL</Trans>} {...step1Form.getInputProps("url")} disabled /> <TextInput label={<Trans>Feed URL</Trans>} {...step1Form.getInputProps("url")} disabled />
<TextInput label={<Trans>Feed name</Trans>} {...step1Form.getInputProps("title")} required autoFocus /> <TextInput label={<Trans>Feed name</Trans>} {...step1Form.getInputProps("title")} required autoFocus />
<CategorySelect label={<Trans>Category</Trans>} {...step1Form.getInputProps("categoryId")} clearable /> <CategorySelect label={<Trans>Category</Trans>} {...step1Form.getInputProps("categoryId")} clearable />
</Stack> </Stack>
</Stepper.Step> </Stepper.Step>
</Stepper> </Stepper>
<Group justify="center" mt="xl"> <Group justify="center" mt="xl">
<Button variant="default" onClick={previousStep}> <Button variant="default" onClick={previousStep}>
<Trans>Back</Trans> <Trans>Back</Trans>
</Button> </Button>
{activeStep === 0 && ( {activeStep === 0 && (
<Button type="submit" loading={fetchFeed.loading}> <Button type="submit" loading={fetchFeed.loading}>
<Trans>Next</Trans> <Trans>Next</Trans>
</Button> </Button>
)} )}
{activeStep === 1 && ( {activeStep === 1 && (
<Button type="submit" leftSection={<TbRss size={16} />} loading={fetchFeed.loading || subscribe.loading}> <Button type="submit" leftSection={<TbRss size={16} />} loading={fetchFeed.loading || subscribe.loading}>
<Trans>Subscribe</Trans> <Trans>Subscribe</Trans>
</Button> </Button>
)} )}
</Group> </Group>
</form> </form>
</> </>
) )
} }

View File

@@ -1,70 +1,72 @@
import { Box, Text } from "@mantine/core" 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 { FeedFavicon } from "components/content/FeedFavicon"
import { useColorScheme } from "hooks/useColorScheme" import { OpenExternalLink } from "components/content/header/OpenExternalLink"
import { tss } from "tss" import { Star } from "components/content/header/Star"
import { FeedEntryTitle } from "./FeedEntryTitle" import { OnDesktop } from "components/responsive/OnDesktop"
import { FeedFavicon } from "./FeedFavicon" import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle"
export interface FeedEntryHeaderProps {
entry: Entry export interface FeedEntryHeaderProps {
} entry: Entry
showStarIcon?: boolean
const useStyles = tss showExternalLinkIcon?: boolean
.withParams<{ }
colorScheme: "light" | "dark"
read: boolean const useStyles = tss
}>() .withParams<{
.create(({ colorScheme, read }) => ({ read: boolean
wrapper: { }>()
display: "flex", .create(({ colorScheme, read }) => ({
alignItems: "center", wrapper: {
columnGap: "10px", display: "flex",
}, alignItems: "center",
title: { columnGap: "10px",
flexGrow: 1, },
fontWeight: colorScheme === "light" && !read ? "bold" : "inherit", title: {
whiteSpace: "nowrap", flexGrow: 1,
overflow: "hidden", fontWeight: colorScheme === "light" && !read ? "bold" : "inherit",
textOverflow: "ellipsis", whiteSpace: "nowrap",
}, overflow: "hidden",
feedName: { textOverflow: "ellipsis",
width: "145px", },
minWidth: "145px", feedName: {
whiteSpace: "nowrap", width: "145px",
overflow: "hidden", minWidth: "145px",
textOverflow: "ellipsis", whiteSpace: "nowrap",
}, overflow: "hidden",
date: { textOverflow: "ellipsis",
whiteSpace: "nowrap", },
}, date: {
})) whiteSpace: "nowrap",
},
export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) { }))
const colorScheme = useColorScheme()
const { classes } = useStyles({ export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
colorScheme, const { classes } = useStyles({
read: props.entry.read, read: props.entry.read,
}) })
return ( return (
<Box className={classes.wrapper}> <Box className={classes.wrapper}>
<Box> {props.showStarIcon && <Star entry={props.entry} />}
<FeedFavicon url={props.entry.iconUrl} /> <Box>
</Box> <FeedFavicon url={props.entry.iconUrl} />
<OnDesktop> </Box>
<Text c="dimmed" className={classes.feedName}> <OnDesktop>
{props.entry.feedName} <Text c="dimmed" className={classes.feedName}>
</Text> {props.entry.feedName}
</OnDesktop> </Text>
<Box className={classes.title}> </OnDesktop>
<FeedEntryTitle entry={props.entry} /> <Box className={classes.title}>
</Box> <FeedEntryTitle entry={props.entry} />
<OnDesktop> </Box>
<Text c="dimmed" className={classes.date}> <OnDesktop>
<RelativeDate date={props.entry.date} /> <Text c="dimmed" className={classes.date}>
</Text> <RelativeDate date={props.entry.date} />
</OnDesktop> </Text>
</Box> </OnDesktop>
) {props.showExternalLinkIcon && <OpenExternalLink entry={props.entry} />}
} </Box>
)
}

View File

@@ -1,61 +1,67 @@
import { Box, Space, Text } from "@mantine/core" import { Box, Flex, 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 { FeedFavicon } from "components/content/FeedFavicon"
import { tss } from "tss" import { OpenExternalLink } from "components/content/header/OpenExternalLink"
import { FeedEntryTitle } from "./FeedEntryTitle" import { Star } from "components/content/header/Star"
import { FeedFavicon } from "./FeedFavicon" import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle"
export interface FeedEntryHeaderProps {
entry: Entry export interface FeedEntryHeaderProps {
expanded: boolean entry: Entry
} expanded: boolean
showStarIcon?: boolean
const useStyles = tss showExternalLinkIcon?: boolean
.withParams<{ }
colorScheme: "light" | "dark"
read: boolean const useStyles = tss
}>() .withParams<{
.create(({ colorScheme, read }) => ({ read: boolean
headerText: { }>()
fontWeight: colorScheme === "light" && !read ? "bold" : "inherit", .create(({ colorScheme, read }) => ({
}, main: {
headerSubtext: { fontWeight: colorScheme === "light" && !read ? "bold" : "inherit",
display: "flex", },
alignItems: "center", details: {
fontSize: "90%", fontSize: "90%",
}, },
})) }))
export function FeedEntryHeader(props: FeedEntryHeaderProps) { export function FeedEntryHeader(props: FeedEntryHeaderProps) {
const colorScheme = useColorScheme() const { classes } = useStyles({
const { classes } = useStyles({ read: props.entry.read,
colorScheme, })
read: props.entry.read, return (
}) <Box>
return ( <Flex align="flex-start" justify="space-between">
<Box> <Flex align="flex-start" className={classes.main}>
<Box className={classes.headerText}> {props.showStarIcon && (
<FeedEntryTitle entry={props.entry} /> <Box ml={-5}>
</Box> <Star entry={props.entry} />
<Box className={classes.headerSubtext}> </Box>
<FeedFavicon url={props.entry.iconUrl} /> )}
<Space w={6} /> <FeedEntryTitle entry={props.entry} />
<Text c="dimmed"> </Flex>
{props.entry.feedName} {props.showExternalLinkIcon && <OpenExternalLink entry={props.entry} />}
<span> · </span> </Flex>
<RelativeDate date={props.entry.date} /> <Flex align="center" className={classes.details}>
</Text> <FeedFavicon url={props.entry.iconUrl} />
</Box> <Space w={6} />
{props.expanded && ( <Text c="dimmed">
<Box className={classes.headerSubtext}> {props.entry.feedName}
<Text c="dimmed"> <span> · </span>
{props.entry.author && <span>by {props.entry.author}</span>} <RelativeDate date={props.entry.date} />
{props.entry.author && props.entry.categories && <span>&nbsp;·&nbsp;</span>} </Text>
{props.entry.categories && <span>{props.entry.categories}</span>} </Flex>
</Text> {props.expanded && (
</Box> <Box className={classes.details}>
)} <Text c="dimmed">
</Box> {props.entry.author && <span>by {props.entry.author}</span>}
) {props.entry.author && props.entry.categories && <span>&nbsp;·&nbsp;</span>}
} {props.entry.categories && <span>{props.entry.categories}</span>}
</Text>
</Box>
)}
</Box>
)
}

View File

@@ -1,22 +1,22 @@
import { Highlight } from "@mantine/core" import { Highlight } from "@mantine/core"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
import { type Entry } from "app/types" import type { Entry } from "app/types"
export interface FeedEntryTitleProps { export interface FeedEntryTitleProps {
entry: Entry entry: Entry
} }
export function FeedEntryTitle(props: FeedEntryTitleProps) { export function FeedEntryTitle(props: FeedEntryTitleProps) {
const search = useAppSelector(state => state.entries.search) const search = useAppSelector(state => state.entries.search)
const keywords = search?.split(" ") const keywords = search?.split(" ")
return ( return (
<Highlight <Highlight
inherit inherit
highlight={keywords ?? ""} highlight={keywords ?? ""}
// make sure ellipsis is shown when title is too long // make sure ellipsis is shown when title is too long
span span
> >
{props.entry.title} {props.entry.title}
</Highlight> </Highlight>
) )
} }

View File

@@ -0,0 +1,30 @@
import { Trans } from "@lingui/macro"
import { ActionIcon, Anchor, Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import { markEntry } from "app/entries/thunks"
import { useAppDispatch } from "app/store"
import type { Entry } from "app/types"
import { TbExternalLink } from "react-icons/tb"
export function OpenExternalLink(props: { entry: Entry }) {
const dispatch = useAppDispatch()
const onClick = (e: React.MouseEvent) => {
e.stopPropagation()
dispatch(
markEntry({
entry: props.entry,
read: true,
})
)
}
return (
<Anchor href={props.entry.url} target="_blank" rel="noreferrer" onClick={onClick}>
<Tooltip label={<Trans>Open link</Trans>} openDelay={Constants.tooltip.delay}>
<ActionIcon variant="transparent" c="dimmed">
<TbExternalLink size={18} />
</ActionIcon>
</Tooltip>
</Anchor>
)
}

View File

@@ -0,0 +1,29 @@
import { Trans } from "@lingui/macro"
import { ActionIcon, Tooltip } from "@mantine/core"
import { Constants } from "app/constants"
import { starEntry } from "app/entries/thunks"
import { useAppDispatch } from "app/store"
import type { Entry } from "app/types"
import { TbStar, TbStarFilled } from "react-icons/tb"
export function Star(props: { entry: Entry }) {
const dispatch = useAppDispatch()
const onClick = (e: React.MouseEvent) => {
e.stopPropagation()
e.preventDefault()
dispatch(
starEntry({
entry: props.entry,
starred: !props.entry.starred,
})
)
}
return (
<Tooltip label={props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>} openDelay={Constants.tooltip.delay}>
<ActionIcon variant="transparent" onClick={onClick}>
{props.entry.starred ? <TbStarFilled size={18} /> : <TbStar size={18} />}
</ActionIcon>
</Tooltip>
)
}

View File

@@ -1,169 +1,169 @@
import { t, Trans } from "@lingui/macro" import { Trans, t } from "@lingui/macro"
import { Box, Center, CloseButton, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core" import { Box, Center, CloseButton, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks" import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { changeReadingMode, changeReadingOrder } from "app/user/thunks" import { changeReadingMode, changeReadingOrder } from "app/user/thunks"
import { ActionButton } from "components/ActionButton" import { ActionButton } from "components/ActionButton"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { useActionButton } from "hooks/useActionButton" import { useActionButton } from "hooks/useActionButton"
import { useBrowserExtension } from "hooks/useBrowserExtension" import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useMobile } from "hooks/useMobile" import { useMobile } from "hooks/useMobile"
import { useEffect } from "react" import { useEffect } from "react"
import { import {
TbArrowDown, TbArrowDown,
TbArrowUp, TbArrowUp,
TbExternalLink, TbExternalLink,
TbEye, TbEye,
TbEyeOff, TbEyeOff,
TbRefresh, TbRefresh,
TbSearch, TbSearch,
TbSettings, TbSettings,
TbSortAscending, TbSortAscending,
TbSortDescending, TbSortDescending,
TbUser, TbUser,
} from "react-icons/tb" } from "react-icons/tb"
import { MarkAllAsReadButton } from "./MarkAllAsReadButton" import { MarkAllAsReadButton } from "./MarkAllAsReadButton"
import { ProfileMenu } from "./ProfileMenu" import { ProfileMenu } from "./ProfileMenu"
function HeaderDivider() { function HeaderDivider() {
return <Divider orientation="vertical" /> return <Divider orientation="vertical" />
} }
function HeaderToolbar(props: { children: React.ReactNode }) { function HeaderToolbar(props: { children: React.ReactNode }) {
const { spacing } = useActionButton() const { spacing } = useActionButton()
const mobile = useMobile("480px") const mobile = useMobile("480px")
return mobile ? ( return mobile ? (
// on mobile use all available width // on mobile use all available width
<Box <Box
style={{ style={{
width: "100%", width: "100%",
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
}} }}
> >
{props.children} {props.children}
</Box> </Box>
) : ( ) : (
<Group gap={spacing}>{props.children}</Group> <Group gap={spacing}>{props.children}</Group>
) )
} }
const iconSize = 18 const iconSize = 18
export function Header() { export function Header() {
const settings = useAppSelector(state => state.user.settings) const settings = useAppSelector(state => state.user.settings)
const profile = useAppSelector(state => state.user.profile) const profile = useAppSelector(state => state.user.profile)
const searchFromStore = useAppSelector(state => state.entries.search) const searchFromStore = useAppSelector(state => state.entries.search)
const { isBrowserExtensionPopup, openSettingsPage, openAppInNewTab } = useBrowserExtension() const { isBrowserExtensionPopup, openSettingsPage, openAppInNewTab } = useBrowserExtension()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const searchForm = useForm<{ search: string }>({ const searchForm = useForm<{ search: string }>({
validate: { validate: {
search: value => (value.length > 0 && value.length < 3 ? t`Search requires at least 3 characters` : null), search: value => (value.length > 0 && value.length < 3 ? t`Search requires at least 3 characters` : null),
}, },
}) })
const { setValues } = searchForm const { setValues } = searchForm
useEffect(() => { useEffect(() => {
setValues({ setValues({
search: searchFromStore, search: searchFromStore,
}) })
}, [setValues, searchFromStore]) }, [setValues, searchFromStore])
if (!settings) return <Loader /> if (!settings) return <Loader />
return ( return (
<Center> <Center>
<HeaderToolbar> <HeaderToolbar>
<ActionButton <ActionButton
icon={<TbArrowUp size={iconSize} />} icon={<TbArrowUp size={iconSize} />}
label={<Trans>Previous</Trans>} label={<Trans>Previous</Trans>}
onClick={async () => onClick={async () =>
await dispatch( await dispatch(
selectPreviousEntry({ selectPreviousEntry({
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} }
/> />
<ActionButton <ActionButton
icon={<TbArrowDown size={iconSize} />} icon={<TbArrowDown size={iconSize} />}
label={<Trans>Next</Trans>} label={<Trans>Next</Trans>}
onClick={async () => onClick={async () =>
await dispatch( await dispatch(
selectNextEntry({ selectNextEntry({
expand: true, expand: true,
markAsRead: true, markAsRead: true,
scrollToEntry: true, scrollToEntry: true,
}) })
) )
} }
/> />
<HeaderDivider /> <HeaderDivider />
<ActionButton <ActionButton
icon={<TbRefresh size={iconSize} />} icon={<TbRefresh size={iconSize} />}
label={<Trans>Refresh</Trans>} label={<Trans>Refresh</Trans>}
onClick={async () => await dispatch(reloadEntries())} onClick={async () => await dispatch(reloadEntries())}
/> />
<MarkAllAsReadButton iconSize={iconSize} /> <MarkAllAsReadButton iconSize={iconSize} />
<HeaderDivider /> <HeaderDivider />
<ActionButton <ActionButton
icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />} icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />}
label={settings.readingMode === "all" ? <Trans>All</Trans> : <Trans>Unread</Trans>} label={settings.readingMode === "all" ? <Trans>All</Trans> : <Trans>Unread</Trans>}
onClick={async () => await dispatch(changeReadingMode(settings.readingMode === "all" ? "unread" : "all"))} onClick={async () => await dispatch(changeReadingMode(settings.readingMode === "all" ? "unread" : "all"))}
/> />
<ActionButton <ActionButton
icon={settings.readingOrder === "asc" ? <TbSortAscending size={iconSize} /> : <TbSortDescending size={iconSize} />} icon={settings.readingOrder === "asc" ? <TbSortAscending size={iconSize} /> : <TbSortDescending size={iconSize} />}
label={settings.readingOrder === "asc" ? <Trans>Asc</Trans> : <Trans>Desc</Trans>} label={settings.readingOrder === "asc" ? <Trans>Asc</Trans> : <Trans>Desc</Trans>}
onClick={async () => await dispatch(changeReadingOrder(settings.readingOrder === "asc" ? "desc" : "asc"))} onClick={async () => await dispatch(changeReadingOrder(settings.readingOrder === "asc" ? "desc" : "asc"))}
/> />
<Popover> <Popover>
<Popover.Target> <Popover.Target>
<Indicator disabled={!searchFromStore}> <Indicator disabled={!searchFromStore}>
<ActionButton icon={<TbSearch size={iconSize} />} label={<Trans>Search</Trans>} /> <ActionButton icon={<TbSearch size={iconSize} />} label={<Trans>Search</Trans>} />
</Indicator> </Indicator>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
<form onSubmit={searchForm.onSubmit(async values => await dispatch(search(values.search)))}> <form onSubmit={searchForm.onSubmit(async values => await dispatch(search(values.search)))}>
<TextInput <TextInput
placeholder={t`Search`} placeholder={t`Search`}
{...searchForm.getInputProps("search")} {...searchForm.getInputProps("search")}
leftSection={<TbSearch size={iconSize} />} leftSection={<TbSearch size={iconSize} />}
rightSection={<CloseButton onClick={async () => await (searchFromStore && dispatch(search("")))} />} rightSection={<CloseButton onClick={async () => await (searchFromStore && dispatch(search("")))} />}
autoFocus autoFocus
/> />
</form> </form>
</Popover.Dropdown> </Popover.Dropdown>
</Popover> </Popover>
<HeaderDivider /> <HeaderDivider />
<ProfileMenu control={<ActionButton icon={<TbUser size={iconSize} />} label={profile?.name} />} /> <ProfileMenu control={<ActionButton icon={<TbUser size={iconSize} />} label={profile?.name} />} />
{isBrowserExtensionPopup && ( {isBrowserExtensionPopup && (
<> <>
<HeaderDivider /> <HeaderDivider />
<ActionButton <ActionButton
icon={<TbSettings size={iconSize} />} icon={<TbSettings size={iconSize} />}
label={<Trans>Extension options</Trans>} label={<Trans>Extension options</Trans>}
onClick={() => openSettingsPage()} onClick={() => openSettingsPage()}
/> />
<ActionButton <ActionButton
icon={<TbExternalLink size={iconSize} />} icon={<TbExternalLink size={iconSize} />}
label={<Trans>Open CommaFeed</Trans>} label={<Trans>Open CommaFeed</Trans>}
onClick={() => openAppInNewTab()} onClick={() => openAppInNewTab()}
/> />
</> </>
)} )}
</HeaderToolbar> </HeaderToolbar>
</Center> </Center>
) )
} }

View File

@@ -1,97 +1,97 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core" import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core"
import { markAllEntries } from "app/entries/thunks" import { markAllEntries } from "app/entries/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { ActionButton } from "components/ActionButton" import { ActionButton } from "components/ActionButton"
import { useState } from "react" import { useState } from "react"
import { TbChecks } from "react-icons/tb" import { TbChecks } from "react-icons/tb"
export function MarkAllAsReadButton(props: { iconSize: number }) { export function MarkAllAsReadButton(props: { iconSize: number }) {
const [opened, setOpened] = useState(false) const [opened, setOpened] = useState(false)
const [threshold, setThreshold] = useState(0) const [threshold, setThreshold] = useState(0)
const source = useAppSelector(state => state.entries.source) const source = useAppSelector(state => state.entries.source)
const sourceLabel = useAppSelector(state => state.entries.sourceLabel) const sourceLabel = useAppSelector(state => state.entries.sourceLabel)
const entriesTimestamp = useAppSelector(state => state.entries.timestamp) ?? Date.now() const entriesTimestamp = useAppSelector(state => state.entries.timestamp) ?? Date.now()
const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation) const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const buttonClicked = () => { const buttonClicked = () => {
if (markAllAsReadConfirmation) { if (markAllAsReadConfirmation) {
setThreshold(0) setThreshold(0)
setOpened(true) setOpened(true)
} else { } else {
dispatch( dispatch(
markAllEntries({ markAllEntries({
sourceType: source.type, sourceType: source.type,
req: { req: {
id: source.id, id: source.id,
read: true, read: true,
olderThan: Date.now(), olderThan: Date.now(),
insertedBefore: entriesTimestamp, insertedBefore: entriesTimestamp,
}, },
}) })
) )
} }
} }
return ( return (
<> <>
<Modal opened={opened} onClose={() => setOpened(false)} title={<Trans>Mark all entries as read</Trans>}> <Modal opened={opened} onClose={() => setOpened(false)} title={<Trans>Mark all entries as read</Trans>}>
<Stack> <Stack>
<Text size="sm"> <Text size="sm">
{threshold === 0 && ( {threshold === 0 && (
<Trans> <Trans>
Are you sure you want to mark all entries of <Code>{sourceLabel}</Code> as read? Are you sure you want to mark all entries of <Code>{sourceLabel}</Code> as read?
</Trans> </Trans>
)} )}
{threshold > 0 && ( {threshold > 0 && (
<Trans> <Trans>
Are you sure you want to mark entries older than {threshold} days of <Code>{sourceLabel}</Code> as read? Are you sure you want to mark entries older than {threshold} days of <Code>{sourceLabel}</Code> as read?
</Trans> </Trans>
)} )}
</Text> </Text>
<Slider <Slider
py="xl" py="xl"
min={0} min={0}
max={28} max={28}
marks={[ marks={[
{ value: 0, label: "0" }, { value: 0, label: "0" },
{ value: 7, label: "7" }, { value: 7, label: "7" },
{ value: 14, label: "14" }, { value: 14, label: "14" },
{ value: 21, label: "21" }, { value: 21, label: "21" },
{ value: 28, label: "28" }, { value: 28, label: "28" },
]} ]}
value={threshold} value={threshold}
onChange={setThreshold} onChange={setThreshold}
/> />
<Group justify="flex-end"> <Group justify="flex-end">
<Button variant="default" onClick={() => setOpened(false)}> <Button variant="default" onClick={() => setOpened(false)}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button <Button
color="red" color="red"
onClick={() => { onClick={() => {
setOpened(false) setOpened(false)
dispatch( dispatch(
markAllEntries({ markAllEntries({
sourceType: source.type, sourceType: source.type,
req: { req: {
id: source.id, id: source.id,
read: true, read: true,
olderThan: Date.now() - threshold * 24 * 60 * 60 * 1000, olderThan: Date.now() - threshold * 24 * 60 * 60 * 1000,
insertedBefore: entriesTimestamp, insertedBefore: entriesTimestamp,
}, },
}) })
) )
}} }}
> >
<Trans>Confirm</Trans> <Trans>Confirm</Trans>
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</Modal> </Modal>
<ActionButton icon={<TbChecks size={props.iconSize} />} label={<Trans>Mark all as read</Trans>} onClick={buttonClicked} /> <ActionButton icon={<TbChecks size={props.iconSize} />} label={<Trans>Mark all as read</Trans>} onClick={buttonClicked} />
</> </>
) )
} }

View File

@@ -1,217 +1,217 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { import {
Box, Box,
Divider, Divider,
Group, Group,
type MantineColorScheme, type MantineColorScheme,
Menu, Menu,
SegmentedControl, SegmentedControl,
type SegmentedControlItem, type SegmentedControlItem,
useMantineColorScheme, useMantineColorScheme,
} from "@mantine/core" } from "@mantine/core"
import { showNotification } from "@mantine/notifications" import { showNotification } from "@mantine/notifications"
import { client } from "app/client" import { client } from "app/client"
import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetrics, redirectToSettings } from "app/redirect/thunks" import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetrics, redirectToSettings } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { type ViewMode } from "app/types" import type { ViewMode } from "app/types"
import { useViewMode } from "hooks/useViewMode" import { useViewMode } from "hooks/useViewMode"
import { type ReactNode, useState } from "react" import { type ReactNode, useState } from "react"
import { import {
TbChartLine, TbChartLine,
TbHeartFilled, TbHeartFilled,
TbHelp, TbHelp,
TbLayoutList, TbLayoutList,
TbList, TbList,
TbListDetails, TbListDetails,
TbMoon, TbMoon,
TbNotes, TbNotes,
TbPower, TbPower,
TbSettings, TbSettings,
TbSun, TbSun,
TbSunMoon, TbSunMoon,
TbUsers, TbUsers,
TbWorldDownload, TbWorldDownload,
} from "react-icons/tb" } from "react-icons/tb"
interface ProfileMenuProps { interface ProfileMenuProps {
control: React.ReactElement control: React.ReactElement
} }
const ProfileMenuControlItem = ({ icon, label }: { icon: ReactNode; label: ReactNode }) => { const ProfileMenuControlItem = ({ icon, label }: { icon: ReactNode; label: ReactNode }) => {
return ( return (
<Group> <Group>
{icon} {icon}
<Box ml={6}>{label}</Box> <Box ml={6}>{label}</Box>
</Group> </Group>
) )
} }
const iconSize = 16 const iconSize = 16
interface ColorSchemeControlItem extends SegmentedControlItem { interface ColorSchemeControlItem extends SegmentedControlItem {
value: MantineColorScheme value: MantineColorScheme
} }
const colorSchemeData: ColorSchemeControlItem[] = [ const colorSchemeData: ColorSchemeControlItem[] = [
{ {
value: "light", value: "light",
label: <ProfileMenuControlItem icon={<TbSun size={iconSize} />} label={<Trans>Light</Trans>} />, label: <ProfileMenuControlItem icon={<TbSun size={iconSize} />} label={<Trans>Light</Trans>} />,
}, },
{ {
value: "dark", value: "dark",
label: <ProfileMenuControlItem icon={<TbMoon size={iconSize} />} label={<Trans>Dark</Trans>} />, label: <ProfileMenuControlItem icon={<TbMoon size={iconSize} />} label={<Trans>Dark</Trans>} />,
}, },
{ {
value: "auto", value: "auto",
label: <ProfileMenuControlItem icon={<TbSunMoon size={iconSize} />} label={<Trans>System</Trans>} />, label: <ProfileMenuControlItem icon={<TbSunMoon size={iconSize} />} label={<Trans>System</Trans>} />,
}, },
] ]
interface ViewModeControlItem extends SegmentedControlItem { interface ViewModeControlItem extends SegmentedControlItem {
value: ViewMode value: ViewMode
} }
const viewModeData: ViewModeControlItem[] = [ const viewModeData: ViewModeControlItem[] = [
{ {
value: "title", value: "title",
label: <ProfileMenuControlItem icon={<TbList size={iconSize} />} label={<Trans>Compact</Trans>} />, label: <ProfileMenuControlItem icon={<TbList size={iconSize} />} label={<Trans>Compact</Trans>} />,
}, },
{ {
value: "cozy", value: "cozy",
label: <ProfileMenuControlItem icon={<TbLayoutList size={iconSize} />} label={<Trans>Cozy</Trans>} />, label: <ProfileMenuControlItem icon={<TbLayoutList size={iconSize} />} label={<Trans>Cozy</Trans>} />,
}, },
{ {
value: "detailed", value: "detailed",
label: <ProfileMenuControlItem icon={<TbListDetails size={iconSize} />} label={<Trans>Detailed</Trans>} />, label: <ProfileMenuControlItem icon={<TbListDetails size={iconSize} />} label={<Trans>Detailed</Trans>} />,
}, },
{ {
value: "expanded", value: "expanded",
label: <ProfileMenuControlItem icon={<TbNotes size={iconSize} />} label={<Trans>Expanded</Trans>} />, label: <ProfileMenuControlItem icon={<TbNotes size={iconSize} />} label={<Trans>Expanded</Trans>} />,
}, },
] ]
export function ProfileMenu(props: ProfileMenuProps) { export function ProfileMenu(props: ProfileMenuProps) {
const [opened, setOpened] = useState(false) const [opened, setOpened] = useState(false)
const { viewMode, setViewMode } = useViewMode() const { viewMode, setViewMode } = useViewMode()
const profile = useAppSelector(state => state.user.profile) const profile = useAppSelector(state => state.user.profile)
const admin = useAppSelector(state => state.user.profile?.admin) const admin = useAppSelector(state => state.user.profile?.admin)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { colorScheme, setColorScheme } = useMantineColorScheme() const { colorScheme, setColorScheme } = useMantineColorScheme()
const logout = () => { const logout = () => {
window.location.href = "logout" window.location.href = "logout"
} }
return ( return (
<Menu position="bottom-end" closeOnItemClick={false} opened={opened} onChange={setOpened}> <Menu position="bottom-end" closeOnItemClick={false} opened={opened} onChange={setOpened}>
<Menu.Target>{props.control}</Menu.Target> <Menu.Target>{props.control}</Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
{profile && <Menu.Label>{profile.name}</Menu.Label>} {profile && <Menu.Label>{profile.name}</Menu.Label>}
<Menu.Item <Menu.Item
leftSection={<TbSettings size={iconSize} />} leftSection={<TbSettings size={iconSize} />}
onClick={() => { onClick={() => {
dispatch(redirectToSettings()) dispatch(redirectToSettings())
setOpened(false) setOpened(false)
}} }}
> >
<Trans>Settings</Trans> <Trans>Settings</Trans>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
leftSection={<TbWorldDownload size={iconSize} />} leftSection={<TbWorldDownload size={iconSize} />}
onClick={async () => onClick={async () =>
await client.feed.refreshAll().then(() => { await client.feed.refreshAll().then(() => {
showNotification({ showNotification({
message: <Trans>Your feeds have been queued for refresh.</Trans>, message: <Trans>Your feeds have been queued for refresh.</Trans>,
color: "green", color: "green",
autoClose: 1000, autoClose: 1000,
}) })
setOpened(false) setOpened(false)
}) })
} }
> >
<Trans>Fetch all my feeds now</Trans> <Trans>Fetch all my feeds now</Trans>
</Menu.Item> </Menu.Item>
<Divider /> <Divider />
<Menu.Label> <Menu.Label>
<Trans>Theme</Trans> <Trans>Theme</Trans>
</Menu.Label> </Menu.Label>
<SegmentedControl <SegmentedControl
fullWidth fullWidth
orientation="vertical" orientation="vertical"
data={colorSchemeData} data={colorSchemeData}
value={colorScheme} value={colorScheme}
onChange={e => setColorScheme(e as MantineColorScheme)} onChange={e => setColorScheme(e as MantineColorScheme)}
mb="xs" mb="xs"
/> />
<Divider /> <Divider />
<Menu.Label> <Menu.Label>
<Trans>Display</Trans> <Trans>Display</Trans>
</Menu.Label> </Menu.Label>
<SegmentedControl <SegmentedControl
fullWidth fullWidth
orientation="vertical" orientation="vertical"
data={viewModeData} data={viewModeData}
value={viewMode} value={viewMode}
onChange={e => setViewMode(e as ViewMode)} onChange={e => setViewMode(e as ViewMode)}
mb="xs" mb="xs"
/> />
{admin && ( {admin && (
<> <>
<Divider /> <Divider />
<Menu.Label> <Menu.Label>
<Trans>Admin</Trans> <Trans>Admin</Trans>
</Menu.Label> </Menu.Label>
<Menu.Item <Menu.Item
leftSection={<TbUsers size={iconSize} />} leftSection={<TbUsers size={iconSize} />}
onClick={() => { onClick={() => {
dispatch(redirectToAdminUsers()) dispatch(redirectToAdminUsers())
setOpened(false) setOpened(false)
}} }}
> >
<Trans>Manage users</Trans> <Trans>Manage users</Trans>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
leftSection={<TbChartLine size={iconSize} />} leftSection={<TbChartLine size={iconSize} />}
onClick={() => { onClick={() => {
dispatch(redirectToMetrics()) dispatch(redirectToMetrics())
setOpened(false) setOpened(false)
}} }}
> >
<Trans>Metrics</Trans> <Trans>Metrics</Trans>
</Menu.Item> </Menu.Item>
</> </>
)} )}
<Divider /> <Divider />
<Menu.Item <Menu.Item
leftSection={<TbHeartFilled size={iconSize} color="red" />} leftSection={<TbHeartFilled size={iconSize} color="red" />}
onClick={() => { onClick={() => {
dispatch(redirectToDonate()) dispatch(redirectToDonate())
setOpened(false) setOpened(false)
}} }}
> >
<Trans>Donate</Trans> <Trans>Donate</Trans>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
leftSection={<TbHelp size={iconSize} />} leftSection={<TbHelp size={iconSize} />}
onClick={() => { onClick={() => {
dispatch(redirectToAbout()) dispatch(redirectToAbout())
setOpened(false) setOpened(false)
}} }}
> >
<Trans>About</Trans> <Trans>About</Trans>
</Menu.Item> </Menu.Item>
<Menu.Item leftSection={<TbPower size={iconSize} />} onClick={logout}> <Menu.Item leftSection={<TbPower size={iconSize} />} onClick={logout}>
<Trans>Logout</Trans> <Trans>Logout</Trans>
</Menu.Item> </Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>
</Menu> </Menu>
) )
} }

View File

@@ -1,9 +1,9 @@
import { type MetricGauge } from "app/types" import type { MetricGauge } from "app/types"
interface MeterProps { interface MeterProps {
gauge: MetricGauge gauge: MetricGauge
} }
export function Gauge(props: MeterProps) { export function Gauge(props: MeterProps) {
return <span>{props.gauge.value}</span> return <span>{props.gauge.value}</span>
} }

View File

@@ -1,19 +1,19 @@
import { Box } from "@mantine/core" import { Box } from "@mantine/core"
import { type MetricMeter } from "app/types" import type { MetricMeter } from "app/types"
interface MeterProps { interface MeterProps {
meter: MetricMeter meter: MetricMeter
} }
export function Meter(props: MeterProps) { export function Meter(props: MeterProps) {
return ( return (
<Box> <Box>
<Box>Mean: {props.meter.mean_rate.toFixed(2)}</Box> <Box>Mean: {props.meter.mean_rate.toFixed(2)}</Box>
<Box>Last minute: {props.meter.m1_rate.toFixed(2)}</Box> <Box>Last minute: {props.meter.m1_rate.toFixed(2)}</Box>
<Box>Last 5 minutes: {props.meter.m5_rate.toFixed(2)}</Box> <Box>Last 5 minutes: {props.meter.m5_rate.toFixed(2)}</Box>
<Box>Last 15 minutes: {props.meter.m15_rate.toFixed(2)}</Box> <Box>Last 15 minutes: {props.meter.m15_rate.toFixed(2)}</Box>
<Box>Units: {props.meter.units}</Box> <Box>Units: {props.meter.units}</Box>
<Box>Total: {props.meter.count}</Box> <Box>Total: {props.meter.count}</Box>
</Box> </Box>
) )
} }

View File

@@ -1,22 +1,22 @@
import { Accordion, Box, Group } from "@mantine/core" import { Accordion, Box, Group } from "@mantine/core"
interface MetricAccordionItemProps { interface MetricAccordionItemProps {
metricKey: string metricKey: string
name: string name: string
headerValue: number headerValue: number
children: React.ReactNode children: React.ReactNode
} }
export function MetricAccordionItem({ metricKey, name, headerValue, children }: MetricAccordionItemProps) { export function MetricAccordionItem({ metricKey, name, headerValue, children }: MetricAccordionItemProps) {
return ( return (
<Accordion.Item value={metricKey} key={metricKey}> <Accordion.Item value={metricKey} key={metricKey}>
<Accordion.Control> <Accordion.Control>
<Group justify="space-between"> <Group justify="space-between">
<Box>{name}</Box> <Box>{name}</Box>
<Box>{headerValue}</Box> <Box>{headerValue}</Box>
</Group> </Group>
</Accordion.Control> </Accordion.Control>
<Accordion.Panel>{children}</Accordion.Panel> <Accordion.Panel>{children}</Accordion.Panel>
</Accordion.Item> </Accordion.Item>
) )
} }

View File

@@ -1,19 +1,19 @@
import { Box } from "@mantine/core" import { Box } from "@mantine/core"
import { type MetricTimer } from "app/types" import type { MetricTimer } from "app/types"
interface MetricTimerProps { interface MetricTimerProps {
timer: MetricTimer timer: MetricTimer
} }
export function Timer(props: MetricTimerProps) { export function Timer(props: MetricTimerProps) {
return ( return (
<Box> <Box>
<Box>Mean: {props.timer.mean_rate.toFixed(2)}</Box> <Box>Mean: {props.timer.mean_rate.toFixed(2)}</Box>
<Box>Last minute: {props.timer.m1_rate.toFixed(2)}</Box> <Box>Last minute: {props.timer.m1_rate.toFixed(2)}</Box>
<Box>Last 5 minutes: {props.timer.m5_rate.toFixed(2)}</Box> <Box>Last 5 minutes: {props.timer.m5_rate.toFixed(2)}</Box>
<Box>Last 15 minutes: {props.timer.m15_rate.toFixed(2)}</Box> <Box>Last 15 minutes: {props.timer.m15_rate.toFixed(2)}</Box>
<Box>Units: {props.timer.rate_units}</Box> <Box>Units: {props.timer.rate_units}</Box>
<Box>Total: {props.timer.count}</Box> <Box>Total: {props.timer.count}</Box>
</Box> </Box>
) )
} }

View File

@@ -1,8 +1,8 @@
import { Box } from "@mantine/core" import { Box } from "@mantine/core"
import { useMobile } from "hooks/useMobile" import { useMobile } from "hooks/useMobile"
import React from "react" import type React from "react"
export function OnDesktop(props: { children: React.ReactNode }) { export function OnDesktop(props: { children: React.ReactNode }) {
const mobile = useMobile() const mobile = useMobile()
return <Box>{!mobile && props.children}</Box> return <Box>{!mobile && props.children}</Box>
} }

View File

@@ -1,8 +1,8 @@
import { Box } from "@mantine/core" import { Box } from "@mantine/core"
import { useMobile } from "hooks/useMobile" import { useMobile } from "hooks/useMobile"
import React from "react" import type React from "react"
export function OnMobile(props: { children: React.ReactNode }) { export function OnMobile(props: { children: React.ReactNode }) {
const mobile = useMobile() const mobile = useMobile()
return <Box>{mobile && props.children}</Box> return <Box>{mobile && props.children}</Box>
} }

View File

@@ -1,106 +1,161 @@
import { Trans } from "@lingui/macro" import { Trans, t } 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 type { ComboboxData } from "@mantine/core/lib/components/Combobox/Combobox.types"
import { useAppDispatch, useAppSelector } from "app/store" import { Constants } from "app/constants"
import { type SharingSettings } from "app/types" import { useAppDispatch, useAppSelector } from "app/store"
import { import type { IconDisplayMode, ScrollMode, SharingSettings } from "app/types"
changeAlwaysScrollToEntry, import {
changeCustomContextMenu, changeCustomContextMenu,
changeLanguage, changeExternalLinkIconDisplayMode,
changeMarkAllAsReadConfirmation, changeLanguage,
changeMobileFooter, changeMarkAllAsReadConfirmation,
changeScrollMarks, changeMobileFooter,
changeScrollSpeed, changeScrollMarks,
changeSharingSetting, changeScrollMode,
changeShowRead, changeScrollSpeed,
} from "app/user/thunks" changeSharingSetting,
import { locales } from "i18n" changeShowRead,
changeStarIconDisplayMode,
export function DisplaySettings() { } from "app/user/thunks"
const language = useAppSelector(state => state.user.settings?.language) import { locales } from "i18n"
const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed) import type { ReactNode } from "react"
const showRead = useAppSelector(state => state.user.settings?.showRead)
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks) export function DisplaySettings() {
const alwaysScrollToEntry = useAppSelector(state => state.user.settings?.alwaysScrollToEntry) const language = useAppSelector(state => state.user.settings?.language)
const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation) const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed)
const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu) const showRead = useAppSelector(state => state.user.settings?.showRead)
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter) const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings) const scrollMode = useAppSelector(state => state.user.settings?.scrollMode)
const dispatch = useAppDispatch() const starIconDisplayMode = useAppSelector(state => state.user.settings?.starIconDisplayMode)
const externalLinkIconDisplayMode = useAppSelector(state => state.user.settings?.externalLinkIconDisplayMode)
return ( const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation)
<Stack> const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu)
<Select const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
description={<Trans>Language</Trans>} const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
value={language} const dispatch = useAppDispatch()
data={locales.map(l => ({
value: l.key, const scrollModeOptions: Record<ScrollMode, ReactNode> = {
label: l.label, always: <Trans>Always</Trans>,
}))} never: <Trans>Never</Trans>,
onChange={async s => await (s && dispatch(changeLanguage(s)))} if_needed: <Trans>If the entry doesn't entirely fit on the screen</Trans>,
/> }
<Switch const displayModeData: ComboboxData = [
label={<Trans>Scroll smoothly when navigating between entries</Trans>} {
checked={scrollSpeed ? scrollSpeed > 0 : false} value: "always",
onChange={async e => await dispatch(changeScrollSpeed(e.currentTarget.checked))} label: t`Always`,
/> },
{
<Switch value: "on_desktop",
label={<Trans>Always scroll selected entry to the top of the page, even if it fits entirely on screen</Trans>} label: t`On desktop`,
checked={alwaysScrollToEntry} },
onChange={async e => await dispatch(changeAlwaysScrollToEntry(e.currentTarget.checked))} {
/> value: "on_mobile",
label: t`On mobile`,
<Switch },
label={<Trans>Show feeds and categories with no unread entries</Trans>} {
checked={showRead} value: "never",
onChange={async e => await dispatch(changeShowRead(e.currentTarget.checked))} label: t`Never`,
/> },
]
<Switch
label={<Trans>In expanded view, scrolling through entries mark them as read</Trans>} return (
checked={scrollMarks} <Stack>
onChange={async e => await dispatch(changeScrollMarks(e.currentTarget.checked))} <Select
/> description={<Trans>Language</Trans>}
value={language}
<Switch data={locales.map(l => ({
label={<Trans>Show confirmation when marking all entries as read</Trans>} value: l.key,
checked={markAllAsReadConfirmation} label: l.label,
onChange={async e => await dispatch(changeMarkAllAsReadConfirmation(e.currentTarget.checked))} }))}
/> onChange={async s => await (s && dispatch(changeLanguage(s)))}
/>
<Switch
label={<Trans>Show CommaFeed's own context menu on right click</Trans>} <Switch
checked={customContextMenu} label={<Trans>Show feeds and categories with no unread entries</Trans>}
onChange={async e => await dispatch(changeCustomContextMenu(e.currentTarget.checked))} checked={showRead}
/> onChange={async e => await dispatch(changeShowRead(e.currentTarget.checked))}
/>
<Switch
label={<Trans>On mobile, show action buttons at the bottom of the screen</Trans>} <Switch
checked={mobileFooter} label={<Trans>Show confirmation when marking all entries as read</Trans>}
onChange={async e => await dispatch(changeMobileFooter(e.currentTarget.checked))} checked={markAllAsReadConfirmation}
/> onChange={async e => await dispatch(changeMarkAllAsReadConfirmation(e.currentTarget.checked))}
/>
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
<Switch
<SimpleGrid cols={2}> label={<Trans>On mobile, show action buttons at the bottom of the screen</Trans>}
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>).map(site => ( checked={mobileFooter}
<Switch onChange={async e => await dispatch(changeMobileFooter(e.currentTarget.checked))}
key={site} />
label={Constants.sharing[site].label}
checked={sharingSettings?.[site]} <Divider label={<Trans>Entry headers</Trans>} labelPosition="center" />
onChange={async e =>
await dispatch( <Select
changeSharingSetting({ description={<Trans>Show star icon</Trans>}
site, value={starIconDisplayMode}
value: e.currentTarget.checked, data={displayModeData}
}) onChange={async s => await dispatch(changeStarIconDisplayMode(s as IconDisplayMode))}
) />
}
/> <Select
))} description={<Trans>Show external link icon</Trans>}
</SimpleGrid> value={externalLinkIconDisplayMode}
</Stack> data={displayModeData}
) onChange={async s => await dispatch(changeExternalLinkIconDisplayMode(s as IconDisplayMode))}
} />
<Switch
label={<Trans>Show CommaFeed's own context menu on right click</Trans>}
checked={customContextMenu}
onChange={async e => await dispatch(changeCustomContextMenu(e.currentTarget.checked))}
/>
<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" />
<SimpleGrid cols={2}>
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>).map(site => (
<Switch
key={site}
label={Constants.sharing[site].label}
checked={sharingSettings?.[site]}
onChange={async e =>
await dispatch(
changeSharingSetting({
site,
value: e.currentTarget.checked,
})
)
}
/>
))}
</SimpleGrid>
</Stack>
)
}

View File

@@ -1,162 +1,162 @@
import { t, Trans } from "@lingui/macro" import { Trans, t } from "@lingui/macro"
import { Anchor, Box, Button, Checkbox, Divider, Group, Input, PasswordInput, Stack, Text, TextInput } from "@mantine/core" import { Anchor, Box, Button, Checkbox, Divider, Group, Input, PasswordInput, Stack, Text, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { openConfirmModal } from "@mantine/modals" import { openConfirmModal } from "@mantine/modals"
import { client, errorToStrings } from "app/client" import { client, errorToStrings } from "app/client"
import { redirectToLogin, redirectToSelectedSource } from "app/redirect/thunks" import { redirectToLogin, redirectToSelectedSource } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { type ProfileModificationRequest } from "app/types" import type { ProfileModificationRequest } from "app/types"
import { reloadProfile } from "app/user/thunks" import { reloadProfile } from "app/user/thunks"
import { Alert } from "components/Alert" import { Alert } from "components/Alert"
import { useEffect } from "react" import { useEffect } from "react"
import { useAsyncCallback } from "react-async-hook" import { useAsyncCallback } from "react-async-hook"
import { TbDeviceFloppy, TbTrash } from "react-icons/tb" import { TbDeviceFloppy, TbTrash } from "react-icons/tb"
interface FormData extends ProfileModificationRequest { interface FormData extends ProfileModificationRequest {
newPasswordConfirmation?: string newPasswordConfirmation?: string
} }
export function ProfileSettings() { export function ProfileSettings() {
const profile = useAppSelector(state => state.user.profile) const profile = useAppSelector(state => state.user.profile)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const form = useForm<FormData>({ const form = useForm<FormData>({
validate: { validate: {
newPasswordConfirmation: (value, values) => (value !== values.newPassword ? t`Passwords do not match` : null), newPasswordConfirmation: (value, values) => (value !== values.newPassword ? t`Passwords do not match` : null),
}, },
}) })
const { setValues } = form const { setValues } = form
const saveProfile = useAsyncCallback(client.user.saveProfile, { const saveProfile = useAsyncCallback(client.user.saveProfile, {
onSuccess: () => { onSuccess: () => {
dispatch(reloadProfile()) dispatch(reloadProfile())
dispatch(redirectToSelectedSource()) dispatch(redirectToSelectedSource())
}, },
}) })
const deleteProfile = useAsyncCallback(client.user.deleteProfile, { const deleteProfile = useAsyncCallback(client.user.deleteProfile, {
onSuccess: () => { onSuccess: () => {
dispatch(redirectToLogin()) dispatch(redirectToLogin())
}, },
}) })
const openDeleteProfileModal = () => const openDeleteProfileModal = () =>
openConfirmModal({ openConfirmModal({
title: <Trans>Delete account</Trans>, title: <Trans>Delete account</Trans>,
children: ( children: (
<Text size="sm"> <Text size="sm">
<Trans>Are you sure you want to delete your account? There's no turning back!</Trans> <Trans>Are you sure you want to delete your account? There's no turning back!</Trans>
</Text> </Text>
), ),
labels: { confirm: <Trans>Confirm</Trans>, cancel: <Trans>Cancel</Trans> }, labels: { confirm: <Trans>Confirm</Trans>, cancel: <Trans>Cancel</Trans> },
confirmProps: { color: "red" }, confirmProps: { color: "red" },
onConfirm: async () => await deleteProfile.execute(), onConfirm: async () => await deleteProfile.execute(),
}) })
useEffect(() => { useEffect(() => {
if (!profile) return if (!profile) return
setValues({ setValues({
currentPassword: "", currentPassword: "",
email: profile.email ?? "", email: profile.email ?? "",
newApiKey: false, newApiKey: false,
}) })
}, [setValues, profile]) }, [setValues, profile])
return ( return (
<> <>
{saveProfile.error && ( {saveProfile.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={errorToStrings(saveProfile.error)} /> <Alert messages={errorToStrings(saveProfile.error)} />
</Box> </Box>
)} )}
{deleteProfile.error && ( {deleteProfile.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={errorToStrings(deleteProfile.error)} /> <Alert messages={errorToStrings(deleteProfile.error)} />
</Box> </Box>
)} )}
<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 <TextInput
label={<Trans>API key</Trans>} label={<Trans>API key</Trans>}
description={ description={
<Trans> <Trans>
This is your API key. It can be used for some read-only API operations and grants access to the Fever API. 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 Use the form at the bottom of the page to generate a new API key
</Trans> </Trans>
} }
readOnly readOnly
value={profile?.apiKey} value={profile?.apiKey}
/> />
<Input.Wrapper <Input.Wrapper
label={<Trans>OPML export</Trans>} label={<Trans>OPML export</Trans>}
description={ description={
<Trans> <Trans>
Export your subscriptions and categories as an OPML file that can be imported in other feed reading services Export your subscriptions and categories as an OPML file that can be imported in other feed reading services
</Trans> </Trans>
} }
> >
<Box> <Box>
<Anchor href="rest/feed/export" download="commafeed_opml.xml"> <Anchor href="rest/feed/export" download="commafeed.opml">
<Trans>Download</Trans> <Trans>Download</Trans>
</Anchor> </Anchor>
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<Input.Wrapper <Input.Wrapper
label={<Trans>Fever API</Trans>} label={<Trans>Fever API</Trans>}
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.
Login with your username and your <u>API key</u>. Login with your username and your <u>API key</u>.
</Trans> </Trans>
} }
> >
<Box> <Box>
<Anchor href={`rest/fever/user/${profile?.id}`} target="_blank"> <Anchor href={`rest/fever/user/${profile?.id}`} target="_blank">
<Trans>Fever API URL</Trans> <Trans>Fever API URL</Trans>
</Anchor> </Anchor>
</Box> </Box>
</Input.Wrapper> </Input.Wrapper>
<Divider /> <Divider />
<PasswordInput <PasswordInput
label={<Trans>Current password</Trans>} label={<Trans>Current password</Trans>}
description={<Trans>Enter your current password to change profile settings</Trans>} description={<Trans>Enter your current password to change profile settings</Trans>}
required required
{...form.getInputProps("currentPassword")} {...form.getInputProps("currentPassword")}
/> />
<TextInput type="email" label={<Trans>E-mail</Trans>} {...form.getInputProps("email")} required /> <TextInput type="email" label={<Trans>E-mail</Trans>} {...form.getInputProps("email")} required />
<PasswordInput <PasswordInput
label={<Trans>New password</Trans>} label={<Trans>New password</Trans>}
description={<Trans>Changing password will generate a new API key</Trans>} description={<Trans>Changing password will generate a new API key</Trans>}
{...form.getInputProps("newPassword")} {...form.getInputProps("newPassword")}
/> />
<PasswordInput label={<Trans>Confirm password</Trans>} {...form.getInputProps("newPasswordConfirmation")} /> <PasswordInput label={<Trans>Confirm password</Trans>} {...form.getInputProps("newPasswordConfirmation")} />
<Checkbox label={<Trans>Generate new API key</Trans>} {...form.getInputProps("newApiKey", { type: "checkbox" })} /> <Checkbox label={<Trans>Generate new API key</Trans>} {...form.getInputProps("newApiKey", { type: "checkbox" })} />
<Group> <Group>
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveProfile.loading}> <Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveProfile.loading}>
<Trans>Save</Trans> <Trans>Save</Trans>
</Button> </Button>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
color="red" color="red"
leftSection={<TbTrash size={16} />} leftSection={<TbTrash size={16} />}
onClick={() => openDeleteProfileModal()} onClick={() => openDeleteProfileModal()}
loading={deleteProfile.loading} loading={deleteProfile.loading}
> >
<Trans>Delete account</Trans> <Trans>Delete account</Trans>
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</form> </form>
</> </>
) )
} }

View File

@@ -1,175 +1,175 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Box, Stack } from "@mantine/core" import { Box, Stack } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { import {
redirectToCategory, redirectToCategory,
redirectToCategoryDetails, redirectToCategoryDetails,
redirectToFeed, redirectToFeed,
redirectToFeedDetails, redirectToFeedDetails,
redirectToTag, redirectToTag,
redirectToTagDetails, redirectToTagDetails,
} from "app/redirect/thunks" } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { collapseTreeCategory } from "app/tree/thunks" import { collapseTreeCategory } from "app/tree/thunks"
import { type Category, type Subscription } from "app/types" import type { Category, Subscription } from "app/types"
import { categoryUnreadCount, flattenCategoryTree } from "app/utils" import { categoryUnreadCount, flattenCategoryTree } from "app/utils"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { OnDesktop } from "components/responsive/OnDesktop" import { OnDesktop } from "components/responsive/OnDesktop"
import React from "react" import React from "react"
import { TbChevronDown, TbChevronRight, TbInbox, TbStar, TbTag } from "react-icons/tb" import { TbChevronDown, TbChevronRight, TbInbox, TbStar, TbTag } from "react-icons/tb"
import { TreeNode } from "./TreeNode" import { TreeNode } from "./TreeNode"
import { TreeSearch } from "./TreeSearch" import { TreeSearch } from "./TreeSearch"
const allIcon = <TbInbox size={16} /> const allIcon = <TbInbox size={16} />
const starredIcon = <TbStar size={16} /> const starredIcon = <TbStar size={16} />
const tagIcon = <TbTag size={16} /> const tagIcon = <TbTag size={16} />
const expandedIcon = <TbChevronDown size={16} /> const expandedIcon = <TbChevronDown size={16} />
const collapsedIcon = <TbChevronRight size={16} /> const collapsedIcon = <TbChevronRight size={16} />
const errorThreshold = 9 const errorThreshold = 9
export function Tree() { export function Tree() {
const root = useAppSelector(state => state.tree.rootCategory) const root = useAppSelector(state => state.tree.rootCategory)
const source = useAppSelector(state => state.entries.source) const source = useAppSelector(state => state.entries.source)
const tags = useAppSelector(state => state.user.tags) const tags = useAppSelector(state => state.user.tags)
const showRead = useAppSelector(state => state.user.settings?.showRead) const showRead = useAppSelector(state => state.user.settings?.showRead)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const feedClicked = (e: React.MouseEvent, id: string) => { const feedClicked = (e: React.MouseEvent, id: string) => {
if (e.detail === 2) { if (e.detail === 2) {
dispatch(redirectToFeedDetails(id)) dispatch(redirectToFeedDetails(id))
} else { } else {
dispatch(redirectToFeed(id)) dispatch(redirectToFeed(id))
} }
} }
const categoryClicked = (e: React.MouseEvent, id: string) => { const categoryClicked = (e: React.MouseEvent, id: string) => {
if (e.detail === 2) { if (e.detail === 2) {
dispatch(redirectToCategoryDetails(id)) dispatch(redirectToCategoryDetails(id))
} else { } else {
dispatch(redirectToCategory(id)) dispatch(redirectToCategory(id))
} }
} }
const categoryIconClicked = (e: React.MouseEvent, category: Category) => { const categoryIconClicked = (e: React.MouseEvent, category: Category) => {
e.stopPropagation() e.stopPropagation()
dispatch( dispatch(
collapseTreeCategory({ collapseTreeCategory({
id: +category.id, id: +category.id,
collapse: category.expanded, collapse: category.expanded,
}) })
) )
} }
const tagClicked = (e: React.MouseEvent, id: string) => { const tagClicked = (e: React.MouseEvent, id: string) => {
if (e.detail === 2) { if (e.detail === 2) {
dispatch(redirectToTagDetails(id)) dispatch(redirectToTagDetails(id))
} else { } else {
dispatch(redirectToTag(id)) dispatch(redirectToTag(id))
} }
} }
const allCategoryNode = () => ( const allCategoryNode = () => (
<TreeNode <TreeNode
id={Constants.categories.all.id} id={Constants.categories.all.id}
name={<Trans>All</Trans>} name={<Trans>All</Trans>}
icon={allIcon} icon={allIcon}
unread={categoryUnreadCount(root)} unread={categoryUnreadCount(root)}
selected={source.type === "category" && source.id === Constants.categories.all.id} selected={source.type === "category" && source.id === Constants.categories.all.id}
expanded={false} expanded={false}
level={0} level={0}
hasError={false} hasError={false}
onClick={categoryClicked} onClick={categoryClicked}
/> />
) )
const starredCategoryNode = () => ( const starredCategoryNode = () => (
<TreeNode <TreeNode
id={Constants.categories.starred.id} id={Constants.categories.starred.id}
name={<Trans>Starred</Trans>} name={<Trans>Starred</Trans>}
icon={starredIcon} icon={starredIcon}
unread={0} unread={0}
selected={source.type === "category" && source.id === Constants.categories.starred.id} selected={source.type === "category" && source.id === Constants.categories.starred.id}
expanded={false} expanded={false}
level={0} level={0}
hasError={false} hasError={false}
onClick={categoryClicked} onClick={categoryClicked}
/> />
) )
const categoryNode = (category: Category, level = 0) => { const categoryNode = (category: Category, level = 0) => {
const unreadCount = categoryUnreadCount(category) const unreadCount = categoryUnreadCount(category)
if (unreadCount === 0 && !showRead) return null if (unreadCount === 0 && !showRead) return null
const hasError = !category.expanded && flattenCategoryTree(category).some(c => c.feeds.some(f => f.errorCount > errorThreshold)) const hasError = !category.expanded && flattenCategoryTree(category).some(c => c.feeds.some(f => f.errorCount > errorThreshold))
return ( return (
<TreeNode <TreeNode
id={category.id} id={category.id}
name={category.name} name={category.name}
icon={category.expanded ? expandedIcon : collapsedIcon} icon={category.expanded ? expandedIcon : collapsedIcon}
unread={unreadCount} unread={unreadCount}
selected={source.type === "category" && source.id === category.id} selected={source.type === "category" && source.id === category.id}
expanded={category.expanded} expanded={category.expanded}
level={level} level={level}
hasError={hasError} hasError={hasError}
onClick={categoryClicked} onClick={categoryClicked}
onIconClick={e => categoryIconClicked(e, category)} onIconClick={e => categoryIconClicked(e, category)}
key={category.id} key={category.id}
/> />
) )
} }
const feedNode = (feed: Subscription, level = 0) => { const feedNode = (feed: Subscription, level = 0) => {
if (feed.unread === 0 && !showRead) return null if (feed.unread === 0 && !showRead) return null
return ( return (
<TreeNode <TreeNode
id={String(feed.id)} id={String(feed.id)}
name={feed.name} name={feed.name}
icon={feed.iconUrl} icon={feed.iconUrl}
unread={feed.unread} unread={feed.unread}
selected={source.type === "feed" && source.id === String(feed.id)} selected={source.type === "feed" && source.id === String(feed.id)}
level={level} level={level}
hasError={feed.errorCount > errorThreshold} hasError={feed.errorCount > errorThreshold}
onClick={feedClicked} onClick={feedClicked}
key={feed.id} key={feed.id}
/> />
) )
} }
const tagNode = (tag: string) => ( const tagNode = (tag: string) => (
<TreeNode <TreeNode
id={tag} id={tag}
name={tag} name={tag}
icon={tagIcon} icon={tagIcon}
unread={0} unread={0}
selected={source.type === "tag" && source.id === tag} selected={source.type === "tag" && source.id === tag}
level={0} level={0}
hasError={false} hasError={false}
onClick={tagClicked} onClick={tagClicked}
key={tag} key={tag}
/> />
) )
const recursiveCategoryNode = (category: Category, level = 0) => ( const recursiveCategoryNode = (category: Category, level = 0) => (
<React.Fragment key={`recursiveCategoryNode-${category.id}`}> <React.Fragment key={`recursiveCategoryNode-${category.id}`}>
{categoryNode(category, level)} {categoryNode(category, level)}
{category.expanded && category.children.map(c => recursiveCategoryNode(c, level + 1))} {category.expanded && category.children.map(c => recursiveCategoryNode(c, level + 1))}
{category.expanded && category.feeds.map(f => feedNode(f, level + 1))} {category.expanded && category.feeds.map(f => feedNode(f, level + 1))}
</React.Fragment> </React.Fragment>
) )
if (!root) return <Loader /> if (!root) return <Loader />
const feeds = flattenCategoryTree(root).flatMap(c => c.feeds) const feeds = flattenCategoryTree(root).flatMap(c => c.feeds)
return ( return (
<Stack> <Stack>
<OnDesktop> <OnDesktop>
<TreeSearch feeds={feeds} /> <TreeSearch feeds={feeds} />
</OnDesktop> </OnDesktop>
<Box> <Box>
{allCategoryNode()} {allCategoryNode()}
{starredCategoryNode()} {starredCategoryNode()}
{root.children.map(c => recursiveCategoryNode(c))} {root.children.map(c => recursiveCategoryNode(c))}
{root.feeds.map(f => feedNode(f))} {root.feeds.map(f => feedNode(f))}
{tags?.map(tag => tagNode(tag))} {tags?.map(tag => tagNode(tag))}
</Box> </Box>
</Stack> </Stack>
) )
} }

View File

@@ -1,85 +1,78 @@
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 type React from "react"
import React, { type ReactNode } from "react" import { tss } from "tss"
import { tss } from "tss" import { UnreadCount } from "./UnreadCount"
import { UnreadCount } from "./UnreadCount"
interface TreeNodeProps {
interface TreeNodeProps { id: string
id: string name: React.ReactNode
name: ReactNode icon: React.ReactNode
icon: ReactNode unread: number
unread: number selected: boolean
selected: boolean expanded?: boolean
expanded?: boolean level: number
level: number hasError: boolean
hasError: boolean onClick: (e: React.MouseEvent, id: string) => void
onClick: (e: React.MouseEvent, id: string) => void onIconClick?: (e: React.MouseEvent, id: string) => void
onIconClick?: (e: React.MouseEvent, id: string) => void }
}
const useStyles = tss
const useStyles = tss .withParams<{
.withParams<{ selected: boolean
theme: MantineTheme hasError: boolean
colorScheme: "dark" | "light" hasUnread: boolean
selected: boolean }>()
hasError: boolean .create(({ theme, colorScheme, selected, hasError, hasUnread }) => {
hasUnread: boolean let backgroundColor = "inherit"
}>() if (selected) backgroundColor = colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]
.create(({ theme, colorScheme, selected, hasError, hasUnread }) => {
let backgroundColor = "inherit" let color: string
if (selected) backgroundColor = colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1] if (hasError) {
color = theme.colors.red[6]
let color } else if (colorScheme === "dark") {
if (hasError) { color = hasUnread ? theme.colors.dark[0] : theme.colors.dark[3]
color = theme.colors.red[6] } else {
} else if (colorScheme === "dark") { color = hasUnread ? theme.black : theme.colors.gray[6]
color = hasUnread ? theme.colors.dark[0] : theme.colors.dark[3] }
} else {
color = hasUnread ? theme.black : theme.colors.gray[6] return {
} node: {
display: "flex",
return { alignItems: "center",
node: { cursor: "pointer",
display: "flex", color,
alignItems: "center", backgroundColor,
cursor: "pointer", "&:hover": {
color, backgroundColor: colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[0],
backgroundColor, },
"&:hover": { },
backgroundColor: colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[0], nodeText: {
}, flexGrow: 1,
}, whiteSpace: "nowrap",
nodeText: { overflow: "hidden",
flexGrow: 1, textOverflow: "ellipsis",
whiteSpace: "nowrap", },
overflow: "hidden", }
textOverflow: "ellipsis", })
},
} export function TreeNode(props: TreeNodeProps) {
}) const { classes } = useStyles({
selected: props.selected,
export function TreeNode(props: TreeNodeProps) { hasError: props.hasError,
const theme = useMantineTheme() hasUnread: props.unread > 0,
const colorScheme = useColorScheme() })
const { classes } = useStyles({ return (
theme, <Box py={1} pl={props.level * 20} className={classes.node} onClick={(e: React.MouseEvent) => props.onClick(e, props.id)}>
colorScheme, <Box mr={6} onClick={(e: React.MouseEvent) => props.onIconClick?.(e, props.id)}>
selected: props.selected, <Center>{typeof props.icon === "string" ? <FeedFavicon url={props.icon} /> : props.icon}</Center>
hasError: props.hasError, </Box>
hasUnread: props.unread > 0, <Box className={classes.nodeText}>{props.name}</Box>
}) {!props.expanded && (
return ( <Box>
<Box py={1} pl={props.level * 20} className={classes.node} onClick={(e: React.MouseEvent) => props.onClick(e, props.id)}> <UnreadCount unreadCount={props.unread} />
<Box mr={6} onClick={(e: React.MouseEvent) => props.onIconClick?.(e, props.id)}> </Box>
<Center>{typeof props.icon === "string" ? <FeedFavicon url={props.icon} /> : props.icon}</Center> )}
</Box> </Box>
<Box className={classes.nodeText}>{props.name}</Box> )
{!props.expanded && ( }
<Box>
<UnreadCount unreadCount={props.unread} />
</Box>
)}
</Box>
)
}

View File

@@ -1,68 +1,69 @@
import { t, Trans } from "@lingui/macro" import { Trans, t } from "@lingui/macro"
import { Box, Center, Kbd, TextInput } from "@mantine/core" import { Box, Center, Kbd, TextInput } from "@mantine/core"
import { Spotlight, spotlight, type SpotlightActionData } from "@mantine/spotlight" import { useOs } from "@mantine/hooks"
import { redirectToFeed } from "app/redirect/thunks" import { Spotlight, type SpotlightActionData, spotlight } from "@mantine/spotlight"
import { useAppDispatch } from "app/store" import { redirectToFeed } from "app/redirect/thunks"
import { type Subscription } from "app/types" import { useAppDispatch } from "app/store"
import { FeedFavicon } from "components/content/FeedFavicon" import type { Subscription } from "app/types"
import { useMousetrap } from "hooks/useMousetrap" import { FeedFavicon } from "components/content/FeedFavicon"
import { TbSearch } from "react-icons/tb" import { useMousetrap } from "hooks/useMousetrap"
import { TbSearch } from "react-icons/tb"
export interface TreeSearchProps {
feeds: Subscription[] export interface TreeSearchProps {
} feeds: Subscription[]
}
export function TreeSearch(props: TreeSearchProps) {
const dispatch = useAppDispatch() export function TreeSearch(props: TreeSearchProps) {
const dispatch = useAppDispatch()
const actions: SpotlightActionData[] = props.feeds const isMacOS = useOs() === "macos"
.map(f => ({ const actions: SpotlightActionData[] = props.feeds
id: `${f.id}`, .map(f => ({
label: f.name, id: `${f.id}`,
leftSection: <FeedFavicon url={f.iconUrl} />, label: f.name,
onClick: async () => await dispatch(redirectToFeed(f.id)), leftSection: <FeedFavicon url={f.iconUrl} />,
})) onClick: async () => await dispatch(redirectToFeed(f.id)),
.sort((f1, f2) => f1.label.localeCompare(f2.label)) }))
.sort((f1, f2) => f1.label.localeCompare(f2.label))
const searchIcon = <TbSearch size={18} />
const rightSection = ( const searchIcon = <TbSearch size={18} />
<Center style={{ cursor: "pointer" }} onClick={() => spotlight.open()}> const rightSection = (
<Kbd>Ctrl</Kbd> <Center style={{ cursor: "pointer" }} onClick={() => spotlight.open()}>
<Box mx={5}>+</Box> <Kbd>{isMacOS ? "Cmd" : "Ctrl"}</Kbd>
<Kbd>K</Kbd> <Box mx={5}>+</Box>
</Center> <Kbd>K</Kbd>
) </Center>
)
// additional keyboard shortcut used by commafeed v1
useMousetrap("g u", () => spotlight.open()) // additional keyboard shortcut used by commafeed v1
useMousetrap("g u", () => spotlight.open())
return (
<> return (
<TextInput <>
placeholder={t`Search`} <TextInput
leftSection={searchIcon} placeholder={t`Search`}
rightSectionWidth={100} leftSection={searchIcon}
rightSection={rightSection} rightSectionWidth={100}
styles={{ rightSection={rightSection}
input: { styles={{
cursor: "pointer", input: {
}, cursor: "pointer",
}} },
onClick={() => spotlight.open()} }}
// prevent focus onClick={() => spotlight.open()}
onFocus={e => e.target.blur()} // prevent focus
readOnly onFocus={e => e.target.blur()}
/> readOnly
<Spotlight />
actions={actions} <Spotlight
limit={10} actions={actions}
shortcut="ctrl+k" limit={10}
searchProps={{ shortcut="mod+k"
leftSection: searchIcon, searchProps={{
placeholder: t`Search`, leftSection: searchIcon,
}} placeholder: t`Search`,
nothingFound={<Trans>Nothing found</Trans>} }}
></Spotlight> nothingFound={<Trans>Nothing found</Trans>}
</> />
) </>
} )
}

View File

@@ -1,25 +1,26 @@
import { Badge, Tooltip } from "@mantine/core" import { Badge, Tooltip } from "@mantine/core"
import { tss } from "tss" import { Constants } from "app/constants"
import { tss } from "tss"
const useStyles = tss.create(() => ({
badge: { const useStyles = tss.create(() => ({
width: "3.2rem", badge: {
// for some reason, mantine Badge has "cursor: 'default'" width: "3.2rem",
cursor: "pointer", // for some reason, mantine Badge has "cursor: 'default'"
}, cursor: "pointer",
})) },
}))
export function UnreadCount(props: { unreadCount: number }) {
const { classes } = useStyles() export function UnreadCount(props: { unreadCount: number }) {
const { classes } = useStyles()
if (props.unreadCount <= 0) return null
if (props.unreadCount <= 0) return null
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
return ( const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count}> return (
<Badge className={classes.badge} variant="light"> <Tooltip label={props.unreadCount} disabled={props.unreadCount === count} openDelay={Constants.tooltip.delay}>
{count} <Badge className={classes.badge} variant="light">
</Badge> {count}
</Tooltip> </Badge>
) </Tooltip>
} )
}

View File

@@ -1,9 +1,9 @@
import { useMantineTheme } from "@mantine/core" import { useMantineTheme } from "@mantine/core"
import { useMobile } from "hooks/useMobile" import { useMobile } from "hooks/useMobile"
export const useActionButton = () => { export const useActionButton = () => {
const theme = useMantineTheme() const theme = useMantineTheme()
const mobile = useMobile(theme.breakpoints.xl) const mobile = useMobile(theme.breakpoints.xl)
const spacing = mobile ? 14 : 0 const spacing = mobile ? 14 : 0
return { mobile, spacing } return { mobile, spacing }
} }

View File

@@ -1,39 +1,39 @@
import { t } from "@lingui/macro" import { t } from "@lingui/macro"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
interface Step { interface Step {
label: string label: string
done: boolean done: boolean
} }
export const useAppLoading = () => { export const useAppLoading = () => {
const profile = useAppSelector(state => state.user.profile) const profile = useAppSelector(state => state.user.profile)
const settings = useAppSelector(state => state.user.settings) const settings = useAppSelector(state => state.user.settings)
const rootCategory = useAppSelector(state => state.tree.rootCategory) const rootCategory = useAppSelector(state => state.tree.rootCategory)
const tags = useAppSelector(state => state.user.tags) const tags = useAppSelector(state => state.user.tags)
const steps: Step[] = [ const steps: Step[] = [
{ {
label: t`Loading settings...`, label: t`Loading settings...`,
done: !!settings, done: !!settings,
}, },
{ {
label: t`Loading profile...`, label: t`Loading profile...`,
done: !!profile, done: !!profile,
}, },
{ {
label: t`Loading subscriptions...`, label: t`Loading subscriptions...`,
done: !!rootCategory, done: !!rootCategory,
}, },
{ {
label: t`Loading tags...`, label: t`Loading tags...`,
done: !!tags, done: !!tags,
}, },
] ]
const loading = steps.some(s => !s.done) const loading = steps.some(s => !s.done)
const loadingPercentage = Math.round((100.0 * steps.filter(s => s.done).length) / steps.length) const loadingPercentage = Math.round((100.0 * steps.filter(s => s.done).length) / steps.length)
const loadingStepLabel = steps.find(s => !s.done)?.label const loadingStepLabel = steps.find(s => !s.done)?.label
return { steps, loading, loadingPercentage, loadingStepLabel } return { steps, loading, loadingPercentage, loadingStepLabel }
} }

View File

@@ -1,64 +1,64 @@
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
export const useBrowserExtension = () => { export const useBrowserExtension = () => {
// the extension will set the "browser-extension-installed" attribute on the root element // the extension will set the "browser-extension-installed" attribute on the root element
const [browserExtensionVersion, setBrowserExtensionVersion] = useState( const [browserExtensionVersion, setBrowserExtensionVersion] = useState(
document.documentElement.getAttribute("browser-extension-installed") document.documentElement.getAttribute("browser-extension-installed")
) )
// monitor the attribute on the root element as it may change after the page was loaded // monitor the attribute on the root element as it may change after the page was loaded
useEffect(() => { useEffect(() => {
const observer = new MutationObserver(mutations => { const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => { for (const mutation of mutations) {
if (mutation.type === "attributes") { if (mutation.type === "attributes") {
const element = mutation.target as Element const element = mutation.target as Element
const version = element.getAttribute("browser-extension-installed") const version = element.getAttribute("browser-extension-installed")
if (version) setBrowserExtensionVersion(version) if (version) setBrowserExtensionVersion(version)
} }
}) }
}) })
observer.observe(document.documentElement, { observer.observe(document.documentElement, {
attributes: true, attributes: true,
}) })
return () => observer.disconnect() return () => observer.disconnect()
}, []) }, [])
// when not in an iframe, window.parent is a reference to window // when not in an iframe, window.parent is a reference to window
const isBrowserExtensionPopup = window.parent !== window const isBrowserExtensionPopup = window.parent !== window
const isBrowserExtensionInstalled = isBrowserExtensionPopup || !!browserExtensionVersion const isBrowserExtensionInstalled = isBrowserExtensionPopup || !!browserExtensionVersion
const isBrowserExtensionInstallable = !isBrowserExtensionPopup const isBrowserExtensionInstallable = !isBrowserExtensionPopup
const w = isBrowserExtensionPopup ? window.parent : window const w = isBrowserExtensionPopup ? window.parent : window
const openSettingsPage = () => w.postMessage("open-settings-page", "*") const openSettingsPage = () => w.postMessage("open-settings-page", "*")
const openAppInNewTab = () => w.postMessage("open-app-in-new-tab", "*") const openAppInNewTab = () => w.postMessage("open-app-in-new-tab", "*")
const openLinkInBackgroundTab = (url: string) => { const openLinkInBackgroundTab = (url: string) => {
if (isBrowserExtensionInstalled) { if (isBrowserExtensionInstalled) {
w.postMessage(`open-link-in-background-tab:${url}`, "*") w.postMessage(`open-link-in-background-tab:${url}`, "*")
} else { } else {
// fallback to ctrl+click simulation // fallback to ctrl+click simulation
const a = document.createElement("a") const a = document.createElement("a")
a.href = url a.href = url
a.rel = "noreferrer" a.rel = "noreferrer"
a.dispatchEvent( a.dispatchEvent(
new MouseEvent("click", { new MouseEvent("click", {
ctrlKey: true, ctrlKey: true,
metaKey: true, metaKey: true,
}) })
) )
} }
} }
const setBadgeUnreadCount = (count: number) => w.postMessage(`set-badge-unread-count:${count}`, "*") const setBadgeUnreadCount = (count: number | string) => w.postMessage(`set-badge-unread-count:${count}`, "*")
return { return {
browserExtensionVersion, browserExtensionVersion,
isBrowserExtensionInstallable, isBrowserExtensionInstallable,
isBrowserExtensionInstalled, isBrowserExtensionInstalled,
isBrowserExtensionPopup, isBrowserExtensionPopup,
openSettingsPage, openSettingsPage,
openAppInNewTab, openAppInNewTab,
openLinkInBackgroundTab, openLinkInBackgroundTab,
setBadgeUnreadCount, setBadgeUnreadCount,
} }
} }

View File

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

View File

@@ -1,9 +1,9 @@
import { useMediaQuery } from "@mantine/hooks" import { useMediaQuery } from "@mantine/hooks"
import { Constants } from "app/constants" import { Constants } from "app/constants"
export const useMobile = (breakpoint: string | number = Constants.layout.mobileBreakpoint) => { export const useMobile = (breakpoint: string | number = Constants.layout.mobileBreakpoint) => {
const bp = typeof breakpoint === "number" ? `${breakpoint}px` : breakpoint const bp = typeof breakpoint === "number" ? `${breakpoint}px` : breakpoint
return !useMediaQuery(`(min-width: ${bp})`, undefined, { return !useMediaQuery(`(min-width: ${bp})`, undefined, {
getInitialValueInEffect: false, getInitialValueInEffect: false,
}) })
} }

View File

@@ -1,22 +1,22 @@
import mousetrap, { type ExtendedKeyboardEvent } from "mousetrap" import mousetrap, { type ExtendedKeyboardEvent } from "mousetrap"
import { useEffect, useRef } from "react" import { useEffect, useRef } from "react"
type Callback = (e: ExtendedKeyboardEvent, combo: string) => void type Callback = (e: ExtendedKeyboardEvent, combo: string) => void
export const useMousetrap = (key: string | string[], callback: Callback) => { export const useMousetrap = (key: string | string[], callback: Callback) => {
// use a ref to avoid unbinding/rebinding every time the callback changes // use a ref to avoid unbinding/rebinding every time the callback changes
const callbackRef = useRef(callback) const callbackRef = useRef(callback)
callbackRef.current = callback callbackRef.current = callback
useEffect(() => { useEffect(() => {
mousetrap.bind(key, (event, combo) => { mousetrap.bind(key, (event, combo) => {
callbackRef.current(event, combo) callbackRef.current(event, combo)
// prevent default behavior // prevent default behavior
return false return false
}) })
return () => { return () => {
mousetrap.unbind(key) mousetrap.unbind(key)
} }
}, [key]) }, [key])
} }

View File

@@ -1,7 +1,7 @@
import { type ViewMode } from "app/types" import type { ViewMode } from "app/types"
import useLocalStorage from "use-local-storage" import useLocalStorage from "use-local-storage"
export function useViewMode() { export function useViewMode() {
const [viewMode, setViewMode] = useLocalStorage<ViewMode>("view-mode", "detailed") const [viewMode, setViewMode] = useLocalStorage<ViewMode>("view-mode", "detailed")
return { viewMode, setViewMode } return { viewMode, setViewMode }
} }

View File

@@ -1,50 +1,49 @@
import { setWebSocketConnected } from "app/server/slice" import { setWebSocketConnected } from "app/server/slice"
import { type AppDispatch, useAppDispatch, useAppSelector } from "app/store" import { type AppDispatch, useAppDispatch, useAppSelector } from "app/store"
import { incrementUnreadCount } from "app/tree/slice" 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 handleMessage = (dispatch: AppDispatch, message: string) => {
const parts = message.split(":") const parts = message.split(":")
const type = parts[0] const type = parts[0]
if (type === "new-feed-entries") { if (type === "new-feed-entries") {
dispatch( dispatch(
incrementUnreadCount({ incrementUnreadCount({
feedId: +parts[1], feedId: +parts[1],
amount: +parts[2], 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)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useEffect(() => { useEffect(() => {
let ws: WebsocketHeartbeatJs | undefined let ws: WebsocketHeartbeatJs | undefined
if (websocketEnabled && websocketPingInterval) { if (websocketEnabled && websocketPingInterval) {
const currentUrl = new URL(window.location.href) const currentUrl = new URL(window.location.href)
const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss" const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss"
const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}${currentUrl.pathname}ws` const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}${currentUrl.pathname}ws`
ws = new WebsocketHeartbeatJs({ ws = new WebsocketHeartbeatJs({
url: wsUrl, url: wsUrl,
pingMsg: "ping", pingMsg: "ping",
pingTimeout: websocketPingInterval, pingTimeout: websocketPingInterval,
}) })
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)
handleMessage(dispatch, data) }
} }
} }
}
return () => ws?.close()
return () => ws?.close() }, [dispatch, websocketEnabled, websocketPingInterval])
}, [dispatch, websocketEnabled, websocketPingInterval]) }
}

View File

@@ -1,64 +1,64 @@
import { i18n, type Messages } from "@lingui/core" import { type Messages, i18n } from "@lingui/core"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
import dayjs from "dayjs" import dayjs from "dayjs"
import { useEffect } from "react" import { useEffect } from "react"
interface Locale { interface Locale {
key: string key: string
label: string label: string
daysjsImportFn: () => Promise<ILocale> dayjsImportFn: () => Promise<ILocale>
} }
// add an object to the array to add a new locale // add an object to the array to add a new locale
// don't forget to also add it to the 'locales' array in .linguirc // don't forget to also add it to the 'locales' array in .linguirc
export const locales: Locale[] = [ export const locales: Locale[] = [
{ key: "ar", label: "العربية", daysjsImportFn: async () => await import("dayjs/locale/ar") }, { key: "ar", label: "العربية", dayjsImportFn: async () => await import("dayjs/locale/ar") },
{ key: "ca", label: "Català", daysjsImportFn: async () => await import("dayjs/locale/ca") }, { key: "ca", label: "Català", dayjsImportFn: async () => await import("dayjs/locale/ca") },
{ key: "cs", label: "Čeština", daysjsImportFn: async () => await import("dayjs/locale/cs") }, { key: "cs", label: "Čeština", dayjsImportFn: async () => await import("dayjs/locale/cs") },
{ key: "cy", label: "Cymraeg", daysjsImportFn: async () => await import("dayjs/locale/cy") }, { key: "cy", label: "Cymraeg", dayjsImportFn: async () => await import("dayjs/locale/cy") },
{ key: "da", label: "Danish", daysjsImportFn: async () => await import("dayjs/locale/da") }, { key: "da", label: "Danish", dayjsImportFn: async () => await import("dayjs/locale/da") },
{ key: "de", label: "Deutsch", daysjsImportFn: async () => await import("dayjs/locale/de") }, { key: "de", label: "Deutsch", dayjsImportFn: async () => await import("dayjs/locale/de") },
{ key: "en", label: "English", daysjsImportFn: async () => await import("dayjs/locale/en") }, { key: "en", label: "English", dayjsImportFn: async () => await import("dayjs/locale/en") },
{ key: "es", label: "Español", daysjsImportFn: async () => await import("dayjs/locale/es") }, { key: "es", label: "Español", dayjsImportFn: async () => await import("dayjs/locale/es") },
{ key: "fa", label: "فارسی", daysjsImportFn: async () => await import("dayjs/locale/fa") }, { key: "fa", label: "فارسی", dayjsImportFn: async () => await import("dayjs/locale/fa") },
{ key: "fi", label: "Suomi", daysjsImportFn: async () => await import("dayjs/locale/fi") }, { key: "fi", label: "Suomi", dayjsImportFn: async () => await import("dayjs/locale/fi") },
{ key: "fr", label: "Français", daysjsImportFn: async () => await import("dayjs/locale/fr") }, { key: "fr", label: "Français", dayjsImportFn: async () => await import("dayjs/locale/fr") },
{ key: "gl", label: "Galician", daysjsImportFn: async () => await import("dayjs/locale/gl") }, { key: "gl", label: "Galician", dayjsImportFn: async () => await import("dayjs/locale/gl") },
{ key: "hu", label: "Magyar", daysjsImportFn: async () => await import("dayjs/locale/hu") }, { key: "hu", label: "Magyar", dayjsImportFn: async () => await import("dayjs/locale/hu") },
{ key: "id", label: "Indonesian", daysjsImportFn: async () => await import("dayjs/locale/id") }, { key: "id", label: "Indonesian", dayjsImportFn: async () => await import("dayjs/locale/id") },
{ key: "it", label: "Italiano", daysjsImportFn: async () => await import("dayjs/locale/it") }, { key: "it", label: "Italiano", dayjsImportFn: async () => await import("dayjs/locale/it") },
{ key: "ja", label: "日本語", daysjsImportFn: async () => await import("dayjs/locale/ja") }, { key: "ja", label: "日本語", dayjsImportFn: async () => await import("dayjs/locale/ja") },
{ key: "ko", label: "한국어", daysjsImportFn: async () => await import("dayjs/locale/ko") }, { key: "ko", label: "한국어", dayjsImportFn: async () => await import("dayjs/locale/ko") },
{ key: "ms", label: "Bahasa Malaysian", daysjsImportFn: async () => await import("dayjs/locale/ms") }, { key: "ms", label: "Bahasa Malaysian", dayjsImportFn: async () => await import("dayjs/locale/ms") },
{ key: "nb", label: "Norsk (bokmål)", daysjsImportFn: async () => await import("dayjs/locale/nb") }, { key: "nb", label: "Norsk (bokmål)", dayjsImportFn: async () => await import("dayjs/locale/nb") },
{ key: "nl", label: "Nederlands", daysjsImportFn: async () => await import("dayjs/locale/nl") }, { key: "nl", label: "Nederlands", dayjsImportFn: async () => await import("dayjs/locale/nl") },
{ key: "nn", label: "Norsk (nynorsk)", daysjsImportFn: async () => await import("dayjs/locale/nn") }, { key: "nn", label: "Norsk (nynorsk)", dayjsImportFn: async () => await import("dayjs/locale/nn") },
{ key: "pl", label: "Polski", daysjsImportFn: async () => await import("dayjs/locale/pl") }, { key: "pl", label: "Polski", dayjsImportFn: async () => await import("dayjs/locale/pl") },
{ key: "pt", label: "Português", daysjsImportFn: async () => await import("dayjs/locale/pt") }, { key: "pt", label: "Português", dayjsImportFn: async () => await import("dayjs/locale/pt") },
{ key: "ru", label: "Русский", daysjsImportFn: async () => await import("dayjs/locale/ru") }, { key: "ru", label: "Русский", dayjsImportFn: async () => await import("dayjs/locale/ru") },
{ key: "sk", label: "Slovenčina", daysjsImportFn: async () => await import("dayjs/locale/sk") }, { key: "sk", label: "Slovenčina", dayjsImportFn: async () => await import("dayjs/locale/sk") },
{ key: "sv", label: "Svenska", daysjsImportFn: async () => await import("dayjs/locale/sv") }, { key: "sv", label: "Svenska", dayjsImportFn: async () => await import("dayjs/locale/sv") },
{ key: "tr", label: "Türkçe", daysjsImportFn: async () => await import("dayjs/locale/tr") }, { key: "tr", label: "Türkçe", dayjsImportFn: async () => await import("dayjs/locale/tr") },
{ key: "zh", label: "简体中文", daysjsImportFn: async () => await import("dayjs/locale/zh") }, { key: "zh", label: "简体中文", dayjsImportFn: async () => await import("dayjs/locale/zh") },
] ]
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)
}) })
// dayjs // dayjs
locales locales
.find(l => l.key === locale) .find(l => l.key === locale)
?.daysjsImportFn() ?.dayjsImportFn()
.then(() => dayjs.locale(locale)) .then(() => dayjs.locale(locale))
} }
export const useI18n = () => { export const useI18n = () => {
const locale = useAppSelector(state => state.user.settings?.language) const locale = useAppSelector(state => state.user.settings?.language)
useEffect(() => { useEffect(() => {
activateLocale(locale ?? "en") activateLocale(locale ?? "en")
}, [locale]) }, [locale])
} }

View File

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr "دخول"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "أدخل كلمة المرور الحالية لتغيير إعدادات ملف التعريف" msgstr "أدخل كلمة المرور الحالية لتغيير إعدادات ملف التعريف"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "خطأ" msgstr "خطأ"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "الملف مطلوب"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "تصفية التعبير" msgstr "تصفية التعبير"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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 "كلمة مرور جديدة"
@@ -566,6 +576,10 @@ msgstr "اختصار العنصر غير المقروء التالي"
msgid "No more entries" msgid "No more entries"
msgstr "لا مزيد من الإدخالات" msgstr "لا مزيد من الإدخالات"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "لم يتم العثور على شيء" msgstr "لم يتم العثور على شيء"
@@ -574,6 +588,14 @@ msgstr "لم يتم العثور على شيء"
msgid "Oldest first" msgid "Oldest first"
msgstr "الأقدم أولا" msgstr "الأقدم أولا"
#: src/components/settings/DisplaySettings.tsx
msgid "On desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "فتح الإدخال الحالي في علامة تبويب جديدة في الخلفية" msgstr "فتح الإدخال الحالي في علامة تبويب جديدة في الخلفية"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "افتح الرابط" msgstr "افتح الرابط"
@@ -635,6 +658,10 @@ msgstr "تصدير OPML"
msgid "OPML file" msgid "OPML file"
msgstr "ملف OPML" msgstr "ملف OPML"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "طلب" msgstr "طلب"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "إظهار موجز ويب والفئات التي لا تحتوي على إدخالات غير مقروءة" msgstr "إظهار موجز ويب والفئات التي لا تحتوي على إدخالات غير مقروءة"
@@ -779,6 +818,10 @@ msgstr "إظهار تعليمات اختصار لوحة المفاتيح"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "فضاء"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "النجم" msgstr "النجم"
@@ -881,6 +925,7 @@ msgstr "غير مقروءة"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "إلغاء النجم" msgstr "إلغاء النجم"

View File

@@ -15,11 +15,11 @@ 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>"
@@ -27,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>"
@@ -68,8 +68,9 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgstr "" msgid "Always"
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."
@@ -85,7 +86,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"
@@ -117,7 +118,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."
@@ -133,11 +134,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
@@ -173,15 +174,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. Login with your username and your <0>API key</0>." 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"
@@ -189,7 +190,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"
@@ -201,7 +202,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"
@@ -213,7 +214,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"
@@ -221,19 +222,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"
@@ -258,11 +259,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
@@ -272,7 +273,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"
@@ -312,9 +313,13 @@ msgstr "Entra"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "introduïu la vostra contrasenya actual per canviar la configuració del perfil" msgstr "introduïu la vostra contrasenya actual per canviar la configuració del perfil"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: 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}."
@@ -331,7 +336,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"
@@ -345,7 +350,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"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "el fitxer és necessari"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Expressió de filtratge" msgstr "Expressió de filtratge"
@@ -385,7 +386,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"
@@ -401,12 +402,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."
@@ -447,7 +452,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
@@ -515,7 +520,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"
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Següent marcador d'elements no llegit"
msgid "No more entries" msgid "No more entries"
msgstr "No hi ha més entrades" msgstr "No hi ha més entrades"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "No s'ha trobat res" msgstr "No s'ha trobat res"
@@ -575,16 +589,24 @@ msgid "Oldest first"
msgstr "el més vell primer" msgstr "el més vell primer"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On desktop"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: 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"
@@ -595,20 +617,21 @@ msgid "Open current entry in a new tab in the background"
msgstr "Obre l'entrada actual en una pestanya nova al fons" msgstr "Obre l'entrada actual en una pestanya nova al fons"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.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 +647,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"
@@ -635,6 +658,10 @@ msgstr "Exportació OPML"
msgid "OPML file" msgid "OPML file"
msgstr "Fitxer OPML" msgstr "Fitxer OPML"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Ordre" msgstr "Ordre"
@@ -670,7 +697,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 +723,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 +733,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,18 +788,22 @@ 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 "Mostra el menú d'entrada (mòbil)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
@@ -777,13 +816,17 @@ 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 "Mostra el menú natiu (escriptori)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr "" msgstr ""
#: 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..."
@@ -796,6 +839,7 @@ msgstr "Espai"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "Estrella" msgstr "Estrella"
@@ -824,7 +868,7 @@ msgstr "Éxit"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the left" 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 +880,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
@@ -853,7 +897,7 @@ msgstr "Tema"
#: src/components/settings/ProfileSettings.tsx #: 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" msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr "" msgstr "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"
@@ -861,11 +905,11 @@ 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"
@@ -873,7 +917,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"
@@ -881,6 +925,7 @@ msgstr "Sense llegir"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Desestrellar" msgstr "Desestrellar"
@@ -912,4 +957,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

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr "Vstupte"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Zadejte své aktuální heslo pro změnu nastavení profilu" msgstr "Zadejte své aktuální heslo pro změnu nastavení profilu"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Chyba" msgstr "Chyba"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrování výrazu" msgstr "Filtrování výrazu"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Další nepřečtená položka bookmarklet"
msgid "No more entries" msgid "No more entries"
msgstr "Žádné další záznamy" msgstr "Žádné další záznamy"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Nic nebylo nalezeno" msgstr "Nic nebylo nalezeno"
@@ -574,6 +588,14 @@ 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 desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Otevřít aktuální položku na nové kartě na pozadí" msgstr "Otevřít aktuální položku na nové kartě na pozadí"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Otevřít odkaz" msgstr "Otevřít odkaz"
@@ -635,6 +658,10 @@ msgstr "Export OPML"
msgid "OPML file" msgid "OPML file"
msgstr "soubor OPML" msgstr "soubor OPML"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Objednávka" msgstr "Objednávka"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "Zobrazit kanály a kategorie bez nepřečtených položek" msgstr "Zobrazit kanály a kategorie bez nepřečtených položek"
@@ -779,6 +818,10 @@ msgstr "Zobrazit nápovědu ke klávesovým zkratkám"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "Vesmír"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "Hvězda" msgstr "Hvězda"
@@ -881,6 +925,7 @@ msgstr "Nepřečteno"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Odstranit hvězdu" msgstr "Odstranit hvězdu"

View File

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr "Ewch i mewn"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Rhowch eich cyfrinair presennol i newid gosodiadau proffil" msgstr "Rhowch eich cyfrinair presennol i newid gosodiadau proffil"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Gwall" msgstr "Gwall"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "mae angen y ffeil"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Hidlo mynegiant" msgstr "Hidlo mynegiant"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Llyfrnod yr eitem nesaf heb ei darllen"
msgid "No more entries" msgid "No more entries"
msgstr "Dim mwy o gofnodion" msgstr "Dim mwy o gofnodion"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Dim wedi'i ddarganfod" msgstr "Dim wedi'i ddarganfod"
@@ -574,6 +588,14 @@ 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 desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Agorwch y cofnod cyfredol mewn tab newydd yn y cefndir" msgstr "Agorwch y cofnod cyfredol mewn tab newydd yn y cefndir"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Dolen agored" msgstr "Dolen agored"
@@ -635,6 +658,10 @@ msgstr "allforio OPML"
msgid "OPML file" msgid "OPML file"
msgstr "ffeil OPML" msgstr "ffeil OPML"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "gorchymyn" msgstr "gorchymyn"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "Dangos ffrydiau a chategorïau heb unrhyw gofnodion heb eu darllen" msgstr "Dangos ffrydiau a chategorïau heb unrhyw gofnodion heb eu darllen"
@@ -779,6 +818,10 @@ msgstr "Dangos cymorth llwybr byr bysellfwrdd"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "Gofod"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "seren" msgstr "seren"
@@ -881,6 +925,7 @@ msgstr "Heb ei ddarllen"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "dad-seren" msgstr "dad-seren"

View File

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr ""
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Indtast din nuværende adgangskode for at ændre profilindstillinger" msgstr "Indtast din nuværende adgangskode for at ændre profilindstillinger"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Fejl" msgstr "Fejl"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "fil er påkrævet"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrerende udtryk" msgstr "Filtrerende udtryk"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Næste ulæste emne bogmærke"
msgid "No more entries" msgid "No more entries"
msgstr "Ingen flere poster" msgstr "Ingen flere poster"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Intet fundet" msgstr "Intet fundet"
@@ -574,6 +588,14 @@ msgstr "Intet fundet"
msgid "Oldest first" msgid "Oldest first"
msgstr "Ældst først" msgstr "Ældst først"
#: src/components/settings/DisplaySettings.tsx
msgid "On desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Åbn den aktuelle post i en ny fane i baggrunden" msgstr "Åbn den aktuelle post i en ny fane i baggrunden"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Åbent link" msgstr "Åbent link"
@@ -635,6 +658,10 @@ msgstr "OPML eksport"
msgid "OPML file" msgid "OPML file"
msgstr "OPML fil" msgstr "OPML fil"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Bestilling" msgstr "Bestilling"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "Vis feeds og kategorier uden ulæste poster" msgstr "Vis feeds og kategorier uden ulæste poster"
@@ -779,6 +818,10 @@ msgstr "Vis hjælp til tastaturgenveje"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "Rum"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "Stjerne" msgstr "Stjerne"
@@ -881,6 +925,7 @@ msgstr "Ulæst"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "" msgstr ""

View File

@@ -15,11 +15,11 @@ 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 "<0>CommaFeed ist ein Open Source Projekt. Der Quellcode wird auf auf </0><1>GitHub</1> gehostet."
#: 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 "Vollständiger Syntax ist </0><1>hier</1> verfügbar." msgstr "<0>Die vollständige Syntax ist </0><1>hier</1> verfügbar."
#: 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>"
@@ -27,7 +27,7 @@ msgstr "<0>Haben Sie ein Konto?</0><1>Melden Sie sich an!</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>Hey,</0><1>Ich bin Jérémie aus Belgien und arbeite seit über 10 Jahren in meiner Freizeit an CommaFeed. Vielen Dank für das Interesse, CommaFeed weiterhin zu unterstützen.</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>"
@@ -68,12 +68,13 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgstr "" msgid "Always"
msgstr "Immer"
#: 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."
msgstr "Eine E-Mail wurde gesendet, wenn diese Adresse registriert wurde. Bitte den Posteingang prüfen." msgstr "Falls diese Adresse registriert ist, wurde eine E-Mail gesendet. Bitte den Posteingang prüfen."
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "An opml file is an XML file containing feed URLs and categories. You can get an OPML file by exporting your data from other feed reading services." msgid "An opml file is an XML file containing feed URLs and categories. You can get an OPML file by exporting your data from other feed reading services."
@@ -169,19 +170,19 @@ msgstr "Das Ändern des Passworts generiert einen neuen API-Schlüssel"
#: src/components/content/add/Subscribe.tsx #: src/components/content/add/Subscribe.tsx
msgid "Check that the feed is working" msgid "Check that the feed is working"
msgstr "Überprüfen Sie, ob der Feed funktioniert" msgstr "Überprüfe, ob der Feed funktioniert"
#: src/pages/app/Layout.tsx #: src/pages/app/Layout.tsx
msgid "Close menu" msgid "Close menu"
msgstr "" msgstr "Menü schließen"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed browser extension version {browserExtensionVersion}." msgid "CommaFeed browser extension version {browserExtensionVersion}."
msgstr "" msgstr "CommaFeed Browser Erweiterung 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. Login with your username and your <0>API key</0>." 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 ist mit der Fever-API kompatibel. Verwenden Sie die folgende URL in Ihrem Fever-kompatiblen mobilen Client. Melden Sie sich mit Ihrem Benutzernamen und Ihrem <0>API-Schlüssel</0> an."
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed next unread item" msgid "CommaFeed next unread item"
@@ -189,7 +190,7 @@ msgstr "CommaFeed nächstes ungelesenes Element"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "CommaFeed version {version} ({revision})." msgid "CommaFeed version {version} ({revision})."
msgstr "" msgstr "CommaFeed version {version} ({revision})."
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Compact" msgid "Compact"
@@ -221,19 +222,19 @@ msgstr "Aktuelles Passwort"
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Custom code" msgid "Custom code"
msgstr "" msgstr "Eigener Code"
#: 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 "Eigene CSS Regeln die angewandt werden"
#: 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 "Einer JS Code der beim Laden der Seite ausgeführt wird"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Dark" msgid "Dark"
msgstr "" msgstr "Dunkel"
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Date created" msgid "Date created"
@@ -262,7 +263,7 @@ msgstr "Beschr"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Detailed" msgid "Detailed"
msgstr "" msgstr "Detailliert"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
@@ -312,13 +313,17 @@ msgstr "Eintreten"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Geben Sie Ihr aktuelles Passwort ein, um die Profileinstellungen zu ändern" msgstr "Geben Sie Ihr aktuelles Passwort ein, um die Profileinstellungen zu ändern"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Fehler" msgstr "Fehler"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Example: {example}." msgid "Example: {example}."
msgstr "Beispiel: {Beispiel}." msgstr "Beispiel: {example}."
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Expanded" msgid "Expanded"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "Datei ist erforderlich"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filterausdruck" msgstr "Filterausdruck"
@@ -405,7 +406,11 @@ msgstr ""
#: 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 "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 "Wenn der Eintrag nicht ganz auf den Bildschirm passt"
#: 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."
@@ -447,7 +452,7 @@ msgstr "Letzte Aktualisierungsmeldung"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Light" msgid "Light"
msgstr "" msgstr "Hell"
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
@@ -541,6 +546,11 @@ 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
#: src/components/settings/DisplaySettings.tsx
msgid "Never"
msgstr "Niemals"
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
msgid "New password" msgid "New password"
msgstr "Neues Passwort" msgstr "Neues Passwort"
@@ -566,6 +576,10 @@ msgstr "Lesezeichen für das nächste ungelesene Element"
msgid "No more entries" msgid "No more entries"
msgstr "Keine weiteren Einträge" msgstr "Keine weiteren Einträge"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Nichts gefunden" msgstr "Nichts gefunden"
@@ -575,16 +589,24 @@ msgid "Oldest first"
msgstr "Älteste zuerst" msgstr "Älteste zuerst"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On desktop"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "Auf mobilen Geräten die Aktion-Buttons am unteren Ende des Bildschirms anzeigen"
#: src/pages/ErrorPage.tsx #: src/pages/ErrorPage.tsx
msgid "Oops!" msgid "Oops!"
msgstr "Ups!" msgstr "Ups!"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Open CommaFeed" msgid "Open CommaFeed"
msgstr "" msgstr "CommaFeed öffnen"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open current entry in a new tab" msgid "Open current entry in a new tab"
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Aktuellen Eintrag in neuem Tab im Hintergrund öffnen" msgstr "Aktuellen Eintrag in neuem Tab im Hintergrund öffnen"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Link öffnen" msgstr "Link öffnen"
@@ -608,7 +631,7 @@ msgstr "Link in neuem Tab öffnen"
#: src/pages/app/Layout.tsx #: src/pages/app/Layout.tsx
msgid "Open menu" msgid "Open menu"
msgstr "" msgstr "Menü öffnen"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
@@ -635,6 +658,10 @@ msgstr "OPML-Export"
msgid "OPML file" msgid "OPML file"
msgstr "OPML-Datei" msgstr "OPML-Datei"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Bestellung" msgstr "Bestellung"
@@ -706,10 +733,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 "Ausgewählten Eintrag an den Anfang der Seite verschieben"
#: 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 "Scrollen"
#: 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
@@ -761,10 +796,14 @@ msgstr "Bestätigung beim Markieren von allen Einträgen als gelesen"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)" msgid "Show entry menu (desktop)"
msgstr "" msgstr "Eintragsmenü anzeigen (Desktop)"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "Eintragsmenü anzeigen (Handy)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
@@ -777,6 +816,10 @@ msgstr "Tastenkürzel-Hilfe anzeigen"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "Natives Menü anzeigen (Desktop)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr "" msgstr ""
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
@@ -796,6 +839,7 @@ msgstr "Raum"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "Stern" msgstr "Stern"
@@ -824,7 +868,7 @@ msgstr "Erfolg"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the left" msgid "Swipe header to the left"
msgstr "" msgstr "Kopfzeile nach links schieben"
#: src/pages/WelcomePage.tsx #: src/pages/WelcomePage.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
@@ -853,7 +897,7 @@ msgstr "Thema"
#: src/components/settings/ProfileSettings.tsx #: 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" msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
msgstr "" msgstr "Dies ist Ihr API-Schlüssel. Er kann für einige schreibgeschützte API-Vorgänge verwendet werden und ermöglicht den Zugriff auf die Fever-API. Verwenden Sie das Formular unten auf der Seite, um einen neuen API-Schlüssel zu generieren"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Toggle read status of current entry" msgid "Toggle read status of current entry"
@@ -881,6 +925,7 @@ msgstr "Ungelesen"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Stern entfernen" msgstr "Stern entfernen"
@@ -908,7 +953,7 @@ msgstr "Webseite"
#: src/pages/app/FeedEntriesPage.tsx #: src/pages/app/FeedEntriesPage.tsx
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Sie haben noch keine Abonnements. " msgstr "Sie haben noch keine Abonnements."
#: 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."

View File

@@ -68,8 +68,9 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgstr "Always scroll selected entry to the top of the page, even if it fits entirely on screen" msgid "Always"
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."
@@ -312,6 +313,10 @@ msgstr "Enter"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Enter your current password to change profile settings" msgstr "Enter your current password to change profile settings"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr "Entry headers"
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Error" msgstr "Error"
@@ -355,10 +360,6 @@ msgstr "Fever API"
msgid "Fever API URL" msgid "Fever API URL"
msgstr "Fever API URL" msgstr "Fever API URL"
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "file is required"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtering expression" msgstr "Filtering expression"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Next unread item bookmarklet"
msgid "No more entries" msgid "No more entries"
msgstr "No more entries" msgstr "No more entries"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr "No sharing options available."
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Nothing found" msgstr "Nothing found"
@@ -574,6 +588,14 @@ msgstr "Nothing found"
msgid "Oldest first" msgid "Oldest first"
msgstr "Oldest first" msgstr "Oldest first"
#: src/components/settings/DisplaySettings.tsx
msgid "On desktop"
msgstr "On desktop"
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr "On mobile"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "On mobile, show action buttons at the bottom of the screen" msgstr "On mobile, show action buttons at the bottom of the screen"
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Open current entry in a new tab in the background" msgstr "Open current entry in a new tab in the background"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Open link" msgstr "Open link"
@@ -635,6 +658,10 @@ msgstr "OPML export"
msgid "OPML file" msgid "OPML file"
msgstr "OPML file" msgstr "OPML file"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr "OPML file is required"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Order" msgstr "Order"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr "Show entry menu (desktop)"
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "Show entry menu (mobile)" msgstr "Show entry menu (mobile)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr "Show external link icon"
#: 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"
msgstr "Show feeds and categories with no unread entries" msgstr "Show feeds and categories with no unread entries"
@@ -779,6 +818,10 @@ msgstr "Show keyboard shortcut help"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "Show native menu (desktop)" msgstr "Show native menu (desktop)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr "Show star icon"
#: 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
@@ -796,6 +839,7 @@ msgstr "Space"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "Star" msgstr "Star"
@@ -881,6 +925,7 @@ msgstr "Unread"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Unstar" msgstr "Unstar"

View File

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr "Entrar"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Ingrese su contraseña actual para cambiar la configuración del perfil" msgstr "Ingrese su contraseña actual para cambiar la configuración del perfil"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "" msgstr ""
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "archivo requerido"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Expresión de filtrado" msgstr "Expresión de filtrado"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Bookmarklet del siguiente elemento no leído"
msgid "No more entries" msgid "No more entries"
msgstr "No más entradas" msgstr "No más entradas"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Nada encontrado" msgstr "Nada encontrado"
@@ -574,6 +588,14 @@ 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 desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Abrir la entrada actual en una nueva pestaña en segundo plano" msgstr "Abrir la entrada actual en una nueva pestaña en segundo plano"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Abrir enlace" msgstr "Abrir enlace"
@@ -635,6 +658,10 @@ msgstr "Exportación OPML"
msgid "OPML file" msgid "OPML file"
msgstr "archivo OPML" msgstr "archivo OPML"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Orden" msgstr "Orden"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "Mostrar feeds y categorías sin entradas no leídas" msgstr "Mostrar feeds y categorías sin entradas no leídas"
@@ -779,6 +818,10 @@ msgstr "Mostrar ayuda de atajo de teclado"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "Espacio"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "estrella" msgstr "estrella"
@@ -881,6 +925,7 @@ msgstr "No leído"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Desmarcar" msgstr "Desmarcar"

View File

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr "وارد شوید"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "رمز عبور فعلی خود را برای تغییر تنظیمات نمایه وارد کنید" msgstr "رمز عبور فعلی خود را برای تغییر تنظیمات نمایه وارد کنید"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "خطا" msgstr "خطا"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "فایل مورد نیاز است"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "بیان فیلتر" msgstr "بیان فیلتر"
@@ -407,6 +408,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 گزارش دهید."
@@ -541,6 +546,11 @@ 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
#: 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 "رمز عبور جدید"
@@ -566,6 +576,10 @@ msgstr "بوکمارک مورد خوانده نشده بعدی"
msgid "No more entries" msgid "No more entries"
msgstr "ورودی دیگری وجود ندارد" msgstr "ورودی دیگری وجود ندارد"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "چیزی پیدا نشد" msgstr "چیزی پیدا نشد"
@@ -574,6 +588,14 @@ msgstr "چیزی پیدا نشد"
msgid "Oldest first" msgid "Oldest first"
msgstr "قدیمی ترین اول" msgstr "قدیمی ترین اول"
#: src/components/settings/DisplaySettings.tsx
msgid "On desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "ورودی فعلی را در یک برگه جدید در پس زمینه باز کنید" msgstr "ورودی فعلی را در یک برگه جدید در پس زمینه باز کنید"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "پیوند را باز کنید" msgstr "پیوند را باز کنید"
@@ -635,6 +658,10 @@ msgstr "صادرات OPML"
msgid "OPML file" msgid "OPML file"
msgstr "فایل OPML" msgstr "فایل OPML"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "سفارش" msgstr "سفارش"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "فیدها و دسته ها را بدون ورودی خوانده نشده نشان دهید" msgstr "فیدها و دسته ها را بدون ورودی خوانده نشده نشان دهید"
@@ -779,6 +818,10 @@ msgstr "نمایش راهنمایی میانبر صفحه کلید"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "فضا"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "ستاره" msgstr "ستاره"
@@ -881,6 +925,7 @@ msgstr "خوانده نشده"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "" msgstr ""

View File

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr ""
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Anna nykyinen salasanasi muuttaaksesi profiiliasetuksia" msgstr "Anna nykyinen salasanasi muuttaaksesi profiiliasetuksia"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Virhe" msgstr "Virhe"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "tiedosto vaaditaan"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Suodattava lauseke" msgstr "Suodattava lauseke"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Seuraavan lukemattoman kohteen kirjanmerkki"
msgid "No more entries" msgid "No more entries"
msgstr "Ei enää merkintöjä" msgstr "Ei enää merkintöjä"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Mitään ei löytynyt" msgstr "Mitään ei löytynyt"
@@ -574,6 +588,14 @@ 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 desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Avaa nykyinen merkintä uudella välilehdellä taustalla" msgstr "Avaa nykyinen merkintä uudella välilehdellä taustalla"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Avaa linkki" msgstr "Avaa linkki"
@@ -635,6 +658,10 @@ msgstr "OPML-vienti"
msgid "OPML file" msgid "OPML file"
msgstr "OPML-tiedosto" msgstr "OPML-tiedosto"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Tilaus" msgstr "Tilaus"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "Näytä syötteet ja luokat ilman lukemattomia merkintöjä" msgstr "Näytä syötteet ja luokat ilman lukemattomia merkintöjä"
@@ -779,6 +818,10 @@ msgstr "Näytä pikanäppäimen ohje"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "Avaruus"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "Tähti" msgstr "Tähti"
@@ -881,6 +925,7 @@ msgstr "Lukematon"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Poista tähti" msgstr "Poista tähti"

View File

@@ -68,8 +68,9 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgstr "Toujours remonter l'entrée sélectionnée en haut de la page, même si elle s'affiche complètement à l'écran" msgid "Always"
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."
@@ -312,6 +313,10 @@ msgstr "Entrer"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Entrez votre mot de passe actuel pour changer les paramètres du profil" msgstr "Entrez votre mot de passe actuel pour changer les paramètres du profil"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Erreur" msgstr "Erreur"
@@ -355,10 +360,6 @@ msgstr "API Fever"
msgid "Fever API URL" msgid "Fever API URL"
msgstr "URL API Fever" msgstr "URL API Fever"
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "fichier requis"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Expression de filtrage" msgstr "Expression de filtrage"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Bookmarklet vers le prochain article non lu"
msgid "No more entries" msgid "No more entries"
msgstr "Fin de la liste" msgstr "Fin de la liste"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Aucun résultat" msgstr "Aucun résultat"
@@ -575,9 +589,17 @@ msgid "Oldest first"
msgstr "Du plus ancien au plus récent" msgstr "Du plus ancien au plus récent"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On desktop"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: 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 !"
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Ouvrir l'entrée actuelle dans un nouvel onglet en arrière-plan" msgstr "Ouvrir l'entrée actuelle dans un nouvel onglet en arrière-plan"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Ouvrir le lien" msgstr "Ouvrir le lien"
@@ -635,6 +658,10 @@ msgstr "Export du fichier OPML"
msgid "OPML file" msgid "OPML file"
msgstr "Fichier OPML" msgstr "Fichier OPML"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Ordre" msgstr "Ordre"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr "Afficher les options de l'entrée (ordinateur)"
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "Afficher les options de l'entrée (mobile)" msgstr "Afficher les options de l'entrée (mobile)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "Afficher les flux et les catégories pour lesquels tout est déjà lu" msgstr "Afficher les flux et les catégories pour lesquels tout est déjà lu"
@@ -779,6 +818,10 @@ msgstr "Montrer les raccourcis clavier"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "Afficher les options du navigateur (ordinateur)" msgstr "Afficher les options du navigateur (ordinateur)"
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "Espace"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "Ajouter aux favoris" msgstr "Ajouter aux favoris"
@@ -881,6 +925,7 @@ msgstr "Non lu"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Retirer des favoris" msgstr "Retirer des favoris"

View File

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr "Entra"
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Introduce o teu contrasinal actual para cambiar a configuración do perfil" msgstr "Introduce o teu contrasinal actual para cambiar a configuración do perfil"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Erro" msgstr "Erro"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "é necesario o ficheiro"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Expresión de filtrado" msgstr "Expresión de filtrado"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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"
@@ -566,6 +576,10 @@ msgstr "Seguinte marcador de elementos non lidos"
msgid "No more entries" msgid "No more entries"
msgstr "Non hai máis entradas" msgstr "Non hai máis entradas"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Non se atopou nada" msgstr "Non se atopou nada"
@@ -574,6 +588,14 @@ 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 desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Abre a entrada actual nunha nova pestana en segundo plano" msgstr "Abre a entrada actual nunha nova pestana en segundo plano"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "ligazón aberta" msgstr "ligazón aberta"
@@ -635,6 +658,10 @@ msgstr "Exportación OPML"
msgid "OPML file" msgid "OPML file"
msgstr "ficheiro OPML" msgstr "ficheiro OPML"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Orde" msgstr "Orde"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "Mostrar fontes e categorías sen entradas sen ler" msgstr "Mostrar fontes e categorías sen entradas sen ler"
@@ -779,6 +818,10 @@ msgstr "Mostrar axuda do atallo do teclado"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr "Espazo"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "estrela" msgstr "estrela"
@@ -881,6 +925,7 @@ msgstr "Sen ler"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Desestrela" msgstr "Desestrela"

View File

@@ -68,7 +68,8 @@ 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" #: src/components/settings/DisplaySettings.tsx
msgid "Always"
msgstr "" msgstr ""
#: src/pages/auth/PasswordRecoveryPage.tsx #: src/pages/auth/PasswordRecoveryPage.tsx
@@ -312,6 +313,10 @@ msgstr ""
msgid "Enter your current password to change profile settings" msgid "Enter your current password to change profile settings"
msgstr "Adja meg jelenlegi jelszavát a profilbeállítások módosításához" msgstr "Adja meg jelenlegi jelszavát a profilbeállítások módosításához"
#: src/components/settings/DisplaySettings.tsx
msgid "Entry headers"
msgstr ""
#: src/components/Alert.tsx #: src/components/Alert.tsx
msgid "Error" msgid "Error"
msgstr "Hiba" msgstr "Hiba"
@@ -355,10 +360,6 @@ msgstr ""
msgid "Fever API URL" msgid "Fever API URL"
msgstr "" msgstr ""
#: src/components/content/add/ImportOpml.tsx
msgid "file is required"
msgstr "fájl szükséges"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Szűrő kifejezés" msgstr "Szűrő kifejezés"
@@ -407,6 +408,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."
@@ -541,6 +546,11 @@ 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
#: 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ó"
@@ -566,6 +576,10 @@ msgstr "Következő olvasatlan elem könyvjelző"
msgid "No more entries" msgid "No more entries"
msgstr "Nincs több bejegyzés" msgstr "Nincs több bejegyzés"
#: src/components/content/ShareButtons.tsx
msgid "No sharing options available."
msgstr ""
#: src/components/sidebar/TreeSearch.tsx #: src/components/sidebar/TreeSearch.tsx
msgid "Nothing found" msgid "Nothing found"
msgstr "Semmi sem található" msgstr "Semmi sem található"
@@ -574,6 +588,14 @@ 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 desktop"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "On mobile"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "On mobile, show action buttons at the bottom of the screen" msgid "On mobile, show action buttons at the bottom of the screen"
msgstr "" msgstr ""
@@ -595,6 +617,7 @@ msgid "Open current entry in a new tab in the background"
msgstr "Az aktuális bejegyzés megnyitása egy új lapon a háttérben" msgstr "Az aktuális bejegyzés megnyitása egy új lapon a háttérben"
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/OpenExternalLink.tsx
msgid "Open link" msgid "Open link"
msgstr "Link megnyitása" msgstr "Link megnyitása"
@@ -635,6 +658,10 @@ msgstr ""
msgid "OPML file" msgid "OPML file"
msgstr "OPML fájl" msgstr "OPML fájl"
#: src/components/content/add/ImportOpml.tsx
msgid "OPML file is required"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Order" msgid "Order"
msgstr "Rendelés" msgstr "Rendelés"
@@ -706,10 +733,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
@@ -767,6 +802,10 @@ msgstr ""
msgid "Show entry menu (mobile)" msgid "Show entry menu (mobile)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show external link icon"
msgstr ""
#: 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"
msgstr "Hírcsatornák és kategóriák megjelenítése olvasatlan bejegyzések nélkül" msgstr "Hírcsatornák és kategóriák megjelenítése olvasatlan bejegyzések nélkül"
@@ -779,6 +818,10 @@ msgstr "A billentyűparancsok súgójának megjelenítése"
msgid "Show native menu (desktop)" msgid "Show native menu (desktop)"
msgstr "" msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Show star icon"
msgstr ""
#: 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
@@ -796,6 +839,7 @@ msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Star" msgid "Star"
msgstr "Csillag" msgstr "Csillag"
@@ -881,6 +925,7 @@ msgstr "Olvasatlan"
#: src/components/content/FeedEntryContextMenu.tsx #: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
#: src/components/content/header/Star.tsx
msgid "Unstar" msgid "Unstar"
msgstr "" msgstr ""

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