mirror of
https://github.com/wallabag/wallabag.git
synced 2026-03-05 11:17:36 +01:00
Compare commits
371 Commits
6fa3e9581c
...
change-rea
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f563c3073 | |||
| 064b9c4415 | |||
| 3ef9e56478 | |||
| 9460df5e37 | |||
| 76fa0d940b | |||
| 0130cca0c1 | |||
| 17d44e4ff6 | |||
| f410c36736 | |||
| 923cf60604 | |||
| 546e20ae5e | |||
| 5b1bda636e | |||
| 9068e51c54 | |||
| b73638b7ec | |||
| fbe01e78a4 | |||
| cb6a4cf91a | |||
| 99f2d527fc | |||
| 4f98befd5f | |||
| f6a8412a82 | |||
| 02a30c6438 | |||
| 4fd7c7cce7 | |||
| 9e1b91b057 | |||
| 54dcb72288 | |||
| 97bedb1b0c | |||
| d5fa287d21 | |||
| a648efdcce | |||
| 85e53eb903 | |||
| d405145686 | |||
| 4d0b509e7d | |||
| 7a4aca6d8c | |||
| e90bbaf540 | |||
| 049d87e180 | |||
| 0d1748c8a8 | |||
| 540483c583 | |||
| 95de911efd | |||
| 9cae4c6410 | |||
| fcdcd5dfe3 | |||
| 25eeb24960 | |||
| edfb821c44 | |||
| bd4b474981 | |||
| ffe6ad1819 | |||
| 2a06a4412d | |||
| 9721fd9c63 | |||
| 0ade0f0112 | |||
| c2c4e46294 | |||
| 2a042a7735 | |||
| b340a581c9 | |||
| 98a8adc6f8 | |||
| 244c238aff | |||
| 1efed23313 | |||
| 2bef2a09c1 | |||
| 8fd5a5273d | |||
| 30a22e690b | |||
| 4f34cfa6fc | |||
| 661fbadbcb | |||
| 8e90c0f320 | |||
| 6ffd7382c7 | |||
| 46d6e4d923 | |||
| 09af127446 | |||
| 4c23196304 | |||
| 42746f418e | |||
| 35c4feedd8 | |||
| 2dc1a2b94c | |||
| d4472a1684 | |||
| cdb1a6fad5 | |||
| c451cc96e5 | |||
| c4240c866b | |||
| 27d66d9e1d | |||
| c1397f43ac | |||
| 52a16bb75f | |||
| f82c87b520 | |||
| 9ec448ab07 | |||
| f33a11d387 | |||
| 4a56e00ea6 | |||
| a083e1f1fd | |||
| 2cc4c483ae | |||
| bd5d504b39 | |||
| e8ce4b4e1a | |||
| 910b057032 | |||
| 46ab120a46 | |||
| 47d9d92c69 | |||
| 37e519f2c0 | |||
| e874b5d2bc | |||
| 13909a8054 | |||
| bbed48e25f | |||
| 0691d2445d | |||
| cfcdbcd1e9 | |||
| e51e5c624b | |||
| ebeb2b105e | |||
| 81a4b52841 | |||
| 47a374270a | |||
| c01408981a | |||
| 772a802596 | |||
| 29162bde9d | |||
| 2e95ae76c2 | |||
| 50b3be776a | |||
| cad5a24fb6 | |||
| 2cd1df4722 | |||
| 843d177d80 | |||
| b1614e9267 | |||
| 95ff88dae8 | |||
| 34740694fe | |||
| 1f3c8eeb61 | |||
| d3c448c858 | |||
| f57280a247 | |||
| 366adf0770 | |||
| 29e43a809d | |||
| 8c91ff613c | |||
| 33ab6a8b9c | |||
| 2d7f2e84be | |||
| 0b0724cc95 | |||
| 3037c374c1 | |||
| 2a70f6c1b5 | |||
| 046cdb978b | |||
| dcaf6b0713 | |||
| c2d35a35e3 | |||
| 48bdeb930d | |||
| c60d237fb2 | |||
| 94ccfff72a | |||
| ab51a65319 | |||
| f4b089e679 | |||
| db21570517 | |||
| 38a18f644b | |||
| 5d3d639d3c | |||
| e6fc4e038a | |||
| 50853f2ad4 | |||
| 14af4f356d | |||
| de630c6050 | |||
| 9559492524 | |||
| ba1020abd5 | |||
| 394ef8f935 | |||
| cb1d340fca | |||
| 478e10ba4a | |||
| 72a3477a83 | |||
| 36eb513e1b | |||
| 3ef7064ada | |||
| 1f8a30f56d | |||
| a03da9414e | |||
| 885e042097 | |||
| 03873e5ac9 | |||
| 1393656e56 | |||
| 16c6f191db | |||
| c554bf7d6d | |||
| 9fba643094 | |||
| dab0bc520b | |||
| 1f89bed117 | |||
| fed449b1e2 | |||
| fe4474869c | |||
| 6937241226 | |||
| 00ad4e0a78 | |||
| ef00122edc | |||
| ae2a867cdd | |||
| 7ffcd8f7f6 | |||
| 96dea32650 | |||
| 70999075a6 | |||
| 262f674245 | |||
| a3ac567c31 | |||
| e7668eabd1 | |||
| df12d66564 | |||
| 8b53d3c9c5 | |||
| 260beeec68 | |||
| 01ffc6c3d5 | |||
| 5586930376 | |||
| b45116b73e | |||
| c2e38cedac | |||
| 35dcc43366 | |||
| de8f859536 | |||
| e6ce9c524c | |||
| 5cdac6c0bb | |||
| 466cd17d5b | |||
| 14cdd123ce | |||
| bdb420b13f | |||
| 99c8a06594 | |||
| 387224f830 | |||
| e4fb100163 | |||
| e28e1bddb4 | |||
| b266d6ca2f | |||
| 8b0e6319e4 | |||
| 7eaaf5d38c | |||
| a8cb9f4f77 | |||
| c9cfae11f7 | |||
| 2054be7bd4 | |||
| f7c8466231 | |||
| e438b5e63f | |||
| 1fd861078d | |||
| a06da68e72 | |||
| 9da9e6b004 | |||
| ffeca7f94d | |||
| d515e11fe4 | |||
| 019d252446 | |||
| 503b82ea13 | |||
| 3125eb43ad | |||
| e5042074a2 | |||
| 76c101938d | |||
| a7a4c5fefb | |||
| 1b683dbb05 | |||
| a69ea46945 | |||
| 2a2172037e | |||
| 0589066ed1 | |||
| 1f76184d02 | |||
| 66c6a25941 | |||
| 46f505f69f | |||
| b4483023e6 | |||
| 31e1be4191 | |||
| 63dc69d70f | |||
| 4e177e1778 | |||
| a766826a69 | |||
| a1440dffda | |||
| 2a60d8473d | |||
| 6a3780ce81 | |||
| 42a63be61a | |||
| 9e2720cddc | |||
| ca018c77e3 | |||
| a107773c11 | |||
| 1d5674a230 | |||
| 4168727f36 | |||
| ce8ed589d5 | |||
| f8c8bb7d93 | |||
| 241cddc899 | |||
| 745fef44f4 | |||
| 99a49bfc96 | |||
| 84eb99c59b | |||
| a679117736 | |||
| eadb6838c0 | |||
| 5fca2db1e5 | |||
| b56d3fef87 | |||
| 4b652b416c | |||
| 528650b525 | |||
| 5597f527d5 | |||
| c79d4449bd | |||
| 07d888a71e | |||
| 10e80a7b9a | |||
| 967ac9f9ac | |||
| be9b1ef60a | |||
| d29e757a09 | |||
| 8a15feb730 | |||
| d4fbb80dd5 | |||
| 27a93cc281 | |||
| e63d473032 | |||
| 41767e8fbc | |||
| c0bb737200 | |||
| 0d93add058 | |||
| 3f2f57e0c0 | |||
| 412352ff03 | |||
| 4ab26a1902 | |||
| a7f7022229 | |||
| 95730754e8 | |||
| 402d80cd30 | |||
| c9301bd0b3 | |||
| 47d3bd4b69 | |||
| f7f5c714ac | |||
| 069c09d8d9 | |||
| 1127b147c0 | |||
| c50265c1eb | |||
| f3e88ec461 | |||
| 5809afd256 | |||
| 2f9d95c2dd | |||
| c860f4db6a | |||
| ae8d7e2e37 | |||
| 67bd937619 | |||
| 82e49ead7d | |||
| a72f7930c4 | |||
| c75e4cf63a | |||
| a9f9c9d513 | |||
| 08d2233882 | |||
| 2344a23be2 | |||
| 6e54f0d0ab | |||
| d6e27feac0 | |||
| 1382af1a5d | |||
| 04f29e4a6b | |||
| 8ac2e699e0 | |||
| 0d9cbcdfcf | |||
| bff0853021 | |||
| 23565c0784 | |||
| b7648f575e | |||
| 622ab8cf96 | |||
| 7a407160c1 | |||
| 985e81e017 | |||
| 5bf34d3c72 | |||
| 4c52f71895 | |||
| 677b2986bc | |||
| 5ea5115a72 | |||
| 27f0d94db7 | |||
| cf49be6940 | |||
| ddf2e80842 | |||
| d1e128900a | |||
| 0d8429dfc7 | |||
| eb8408b22f | |||
| 00d0e6f951 | |||
| edffef8375 | |||
| 3817010e29 | |||
| ed1acf59e1 | |||
| 56ce98fb9a | |||
| d5aa680054 | |||
| 6c183081df | |||
| d965c25304 | |||
| 8fade1416b | |||
| 4d7fdc00db | |||
| a60f8599d8 | |||
| 3a44ed7943 | |||
| e162408139 | |||
| 6fa61c0f9c | |||
| 264f91126e | |||
| ac5b5fb379 | |||
| d703fa6a3a | |||
| 2382140a12 | |||
| 7ba55697b9 | |||
| 2272d3da66 | |||
| 7e9e179860 | |||
| 31ba90b060 | |||
| c20f37975b | |||
| ecb8b8ff49 | |||
| 67c359a6dd | |||
| 1393f78005 | |||
| 63cf403eaf | |||
| 1e61a51e82 | |||
| 0330d01a49 | |||
| 595f35a1a5 | |||
| 943bfd9162 | |||
| 4a1598165f | |||
| fb11f5870e | |||
| 4b4e021a04 | |||
| 440d2d7c76 | |||
| 86e15954c5 | |||
| 9a55f17b42 | |||
| 7ae25f3cc6 | |||
| 787a812f8e | |||
| c540bff62b | |||
| f3da3a42e8 | |||
| f501c6206e | |||
| 0aedbd7fd7 | |||
| f042e7e178 | |||
| b39b361440 | |||
| 9499b062d0 | |||
| f9676270f2 | |||
| e3dc63f739 | |||
| d2dd7f78d3 | |||
| deae27bdae | |||
| 61e2cb37df | |||
| b9900c311d | |||
| 1afe48e732 | |||
| 198f0a64d0 | |||
| 41fe283a94 | |||
| 1f0d4723c6 | |||
| bc9bdc72f3 | |||
| 7bebe5fd4b | |||
| 3bd434091f | |||
| 1acbc91484 | |||
| b05fb21f2b | |||
| b61611ffd4 | |||
| 1447c183a4 | |||
| ed2ad4776b | |||
| 8542edc4f1 | |||
| d5126c8a0e | |||
| 939e8cf8df | |||
| 991f11cdf2 | |||
| 4fa015bddf | |||
| 39c71dfdbf | |||
| 3b68d3ff62 | |||
| 27d07be2b7 | |||
| 206a04bc05 | |||
| 78ab273dab | |||
| 530bc71924 | |||
| d082def664 | |||
| f71d8332e0 | |||
| 3dffcadc03 | |||
| fab0c02ba0 | |||
| c4857564f3 | |||
| c7c74de4b8 | |||
| 08b68d4d87 | |||
| 93e877f086 | |||
| 82430b50c6 |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@ -24,7 +24,7 @@ If you want to test using an other database than SQLite, uncomment the `postgres
|
||||
|
||||
### Using your own PHP server
|
||||
|
||||
- Ensure you are running PHP >= 7.4.
|
||||
- Ensure you are running PHP >= 8.2.
|
||||
- Clone the repository
|
||||
- Launch `composer install`
|
||||
- If you got some errors, fix them (they might be related to some missing PHP extension from your machine)
|
||||
|
||||
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@ -21,7 +21,7 @@ updates:
|
||||
- package-ecosystem: composer
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
interval: weekly
|
||||
time: "04:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 10
|
||||
@ -40,13 +40,8 @@ updates:
|
||||
- yguedidi
|
||||
- Kdecherf
|
||||
ignore:
|
||||
- dependency-name: lcobucci/jwt
|
||||
versions:
|
||||
- ">= 4.2.0"
|
||||
# until we add support for Symfony 5+
|
||||
- dependency-name: symfony/*
|
||||
versions:
|
||||
- ">= 5.0.0"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
||||
4
.github/workflows/coding-standards.yml
vendored
4
.github/workflows/coding-standards.yml
vendored
@ -13,7 +13,7 @@ permissions:
|
||||
jobs:
|
||||
coding-standards:
|
||||
name: "CS Fixer, PHPStan & TwigCS"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "7.4"
|
||||
php-version: "8.2"
|
||||
tools: cs2pr, pecl
|
||||
extensions: pdo, pdo_mysql, pdo_sqlite, pdo_pgsql, curl, imagick, pgsql, gd, tidy
|
||||
ini-values: "date.timezone=Europe/Paris"
|
||||
|
||||
9
.github/workflows/continuous-integration.yml
vendored
9
.github/workflows/continuous-integration.yml
vendored
@ -16,7 +16,7 @@ env:
|
||||
jobs:
|
||||
phpunit:
|
||||
name: "PHP ${{ matrix.php }} using ${{ matrix.database }}"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
rabbitmq:
|
||||
image: rabbitmq:3-alpine
|
||||
@ -31,9 +31,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php:
|
||||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
@ -96,7 +93,7 @@ jobs:
|
||||
|
||||
phpunit_no_prefix:
|
||||
name: "PHP ${{ matrix.php }} using ${{ matrix.database }} without prefix"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
rabbitmq:
|
||||
image: rabbitmq:3-alpine
|
||||
@ -176,7 +173,7 @@ jobs:
|
||||
|
||||
phpunit-without-rmq-redis:
|
||||
name: "PHP ${{ matrix.php }} using ${{ matrix.database }} without Rabbit & Redis"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v2.3.0
|
||||
uses: dependabot/fetch-metadata@v2.4.0
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
- name: Approve and merge minor updates
|
||||
|
||||
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@ -13,12 +13,12 @@ permissions:
|
||||
jobs:
|
||||
translations:
|
||||
name: "Translations"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php:
|
||||
- "7.4"
|
||||
- "8.2"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
|
||||
2
.github/workflows/upload-release-package.yml
vendored
2
.github/workflows/upload-release-package.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php:
|
||||
- "7.4"
|
||||
- "8.2"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
|
||||
@ -37,17 +37,6 @@ return $config
|
||||
// 'psr_autoloading' => true,
|
||||
'strict_comparison' => true,
|
||||
'strict_param' => true,
|
||||
// We override next rule because of current @Symfony ruleSet
|
||||
// 'parameters' element is breaking PHP 7.4
|
||||
// https://cs.symfony.com/doc/rules/control_structure/trailing_comma_in_multiline.html
|
||||
// TODO: remove this configuration after dropping support of PHP 7.4
|
||||
'trailing_comma_in_multiline' => [
|
||||
'elements' => [
|
||||
'arrays',
|
||||
'array_destructuring',
|
||||
'match',
|
||||
],
|
||||
],
|
||||
'concat_space' => [
|
||||
'spacing' => 'one',
|
||||
],
|
||||
|
||||
42
CHANGELOG.md
42
CHANGELOG.md
@ -2,8 +2,50 @@
|
||||
|
||||
## Upcoming changes
|
||||
|
||||
* **[BC BREAK]** Convert 403 errors to 404 errors by @yguedidi in https://github.com/wallabag/wallabag/pull/8075
|
||||
* `wallassets/` folder renamed to `build/`
|
||||
|
||||
## [2.6.13](https://github.com/wallabag/wallabag/tree/2.6.13)
|
||||
[Full Changelog](https://github.com/wallabag/wallabag/compare/2.6.12...2.6.13)
|
||||
|
||||
### Improvements
|
||||
|
||||
* Add support of Pocket CSV import by @kdecherf and @nicosomb in [https://github.com/wallabag/wallabag/pull/8240](https://github.com/wallabag/wallabag/pull/8240)
|
||||
* Backport Pocket and Shaarli HTML imports from master by @nicosomb in [https://github.com/wallabag/wallabag/pull/8193](https://github.com/wallabag/wallabag/pull/8193)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Avoid non-validated OTP to be enabled #8139 by @j0k3r in [https://github.com/wallabag/wallabag/pull/8139](https://github.com/wallabag/wallabag/pull/8139)
|
||||
|
||||
### Technical stuff
|
||||
|
||||
* Update j0k3r/php-readability:1.2.13 to fix regression (about latin1 instead of UTF-8 used for entries) by @nicosomb [https://github.com/wallabag/wallabag/pull/8194](https://github.com/wallabag/wallabag/pull/8194)
|
||||
|
||||
## [2.6.12](https://github.com/wallabag/wallabag/tree/2.6.12)
|
||||
[Full Changelog](https://github.com/wallabag/wallabag/compare/2.6.11...2.6.12)
|
||||
|
||||
### Technical stuff
|
||||
|
||||
* Fix changelog by @yguedidi in [https://github.com/wallabag/wallabag/pull/8135](https://github.com/wallabag/wallabag/pull/8135)
|
||||
* Update dependencies by @yguedidi in [https://github.com/wallabag/wallabag/pull/8136](https://github.com/wallabag/wallabag/pull/8136)
|
||||
|
||||
## [2.6.11](https://github.com/wallabag/wallabag/tree/2.6.11)
|
||||
[Full Changelog](https://github.com/wallabag/wallabag/compare/2.6.10...2.6.11)
|
||||
|
||||
### Security fix
|
||||
* Protect actions with a CSRF token by @yguedidi in https://github.com/wallabag/wallabag/commit/99c8a06594d6ee7480ce4d041ccff3025b353656
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix redirection after action in search results by @nicosomb in [https://github.com/wallabag/wallabag/pull/7827](https://github.com/wallabag/wallabag/pull/7827)
|
||||
* Fix title tag filter by @nicosomb in [https://github.com/wallabag/wallabag/pull/7846](https://github.com/wallabag/wallabag/pull/7846)
|
||||
* Change NB_ELEMENTS in pocket importer to 30 by @j0k3r in [https://github.com/wallabag/wallabag/pull/7993](https://github.com/wallabag/wallabag/pull/7993)
|
||||
* Fix entries counter for annotated entries in the menu by @j0k3r in [https://github.com/wallabag/wallabag/pull/7999](https://github.com/wallabag/wallabag/pull/7999)
|
||||
|
||||
### Technical stuff
|
||||
|
||||
* Prepare 2.6.11 release by @yguedidi in [https://github.com/wallabag/wallabag/pull/8133](https://github.com/wallabag/wallabag/pull/8133)
|
||||
|
||||
## [2.6.10](https://github.com/wallabag/wallabag/tree/2.6.10)
|
||||
[Full Changelog](https://github.com/wallabag/wallabag/compare/2.6.9...2.6.10)
|
||||
|
||||
|
||||
@ -52,6 +52,9 @@ fix-cs: ## Run PHP-CS-Fixer
|
||||
phpstan: ## Run PHPStan
|
||||
@$(PHP_NO_XDEBUG) bin/phpstan analyse
|
||||
|
||||
phpstan-baseline: ## Generate PHPStan baseline
|
||||
@$(PHP_NO_XDEBUG) bin/phpstan analyse --generate-baseline
|
||||
|
||||
lint-js: ## Run ESLint
|
||||
@$(YARN) lint:js
|
||||
|
||||
|
||||
@ -25,13 +25,13 @@ During this documentation, we assume the release is `$LAST_WALLABAG_RELEASE` (li
|
||||
### Target PHP version
|
||||
`composer.lock` is _always_ built for a particular version, by default the one it is generated (with `composer update`).
|
||||
|
||||
If the PHP version used to generate the .lock isn't a widely available one (like PHP 8), a more common one should
|
||||
If the PHP version used to generate the .lock isn't a widely available one (like latest PHP versions), a more common one should
|
||||
be locally specified in `composer.lock`:
|
||||
|
||||
```json
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.4.29",
|
||||
"php": "8.2.27",
|
||||
"ext-something": "4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,39 @@
|
||||
<?php
|
||||
|
||||
use BabDev\PagerfantaBundle\BabDevPagerfantaBundle;
|
||||
use Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle;
|
||||
use Craue\ConfigBundle\CraueConfigBundle;
|
||||
use DAMA\DoctrineTestBundle\DAMADoctrineTestBundle;
|
||||
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
|
||||
use Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle;
|
||||
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
|
||||
use FOS\JsRoutingBundle\FOSJsRoutingBundle;
|
||||
use FOS\OAuthServerBundle\FOSOAuthServerBundle;
|
||||
use FOS\RestBundle\FOSRestBundle;
|
||||
use FOS\UserBundle\FOSUserBundle;
|
||||
use JMS\SerializerBundle\JMSSerializerBundle;
|
||||
use KPhoen\RulerZBundle\KPhoenRulerZBundle;
|
||||
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
|
||||
use Nelmio\CorsBundle\NelmioCorsBundle;
|
||||
use OldSound\RabbitMqBundle\OldSoundRabbitMqBundle;
|
||||
use Scheb\TwoFactorBundle\SchebTwoFactorBundle;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle;
|
||||
use Sentry\SentryBundle\SentryBundle;
|
||||
use Spiriit\Bundle\FormFilterBundle\SpiriitFormFilterBundle;
|
||||
use Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle;
|
||||
use Symfony\Bundle\DebugBundle\DebugBundle;
|
||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||
use Symfony\Bundle\MakerBundle\MakerBundle;
|
||||
use Symfony\Bundle\MonologBundle\MonologBundle;
|
||||
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
||||
use Symfony\Bundle\TwigBundle\TwigBundle;
|
||||
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
|
||||
use Symfony\Bundle\WebServerBundle\WebServerBundle;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
use Symfony\WebpackEncoreBundle\WebpackEncoreBundle;
|
||||
use Twig\Extra\TwigExtraBundle\TwigExtraBundle;
|
||||
use Wallabag\Import\ImportCompilerPass;
|
||||
|
||||
class AppKernel extends Kernel
|
||||
@ -10,45 +41,45 @@ class AppKernel extends Kernel
|
||||
public function registerBundles()
|
||||
{
|
||||
$bundles = [
|
||||
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
|
||||
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
|
||||
new Symfony\Bundle\TwigBundle\TwigBundle(),
|
||||
new Symfony\Bundle\MonologBundle\MonologBundle(),
|
||||
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
|
||||
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
|
||||
new FOS\RestBundle\FOSRestBundle(),
|
||||
new FOS\UserBundle\FOSUserBundle(),
|
||||
new JMS\SerializerBundle\JMSSerializerBundle(),
|
||||
new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
|
||||
new Nelmio\CorsBundle\NelmioCorsBundle(),
|
||||
new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),
|
||||
new Spiriit\Bundle\FormFilterBundle\SpiriitFormFilterBundle(),
|
||||
new FOS\OAuthServerBundle\FOSOAuthServerBundle(),
|
||||
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
|
||||
new Scheb\TwoFactorBundle\SchebTwoFactorBundle(),
|
||||
new KPhoen\RulerZBundle\KPhoenRulerZBundle(),
|
||||
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
|
||||
new Craue\ConfigBundle\CraueConfigBundle(),
|
||||
new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),
|
||||
new FOS\JsRoutingBundle\FOSJsRoutingBundle(),
|
||||
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
|
||||
new Sentry\SentryBundle\SentryBundle(),
|
||||
new Twig\Extra\TwigExtraBundle\TwigExtraBundle(),
|
||||
new Symfony\WebpackEncoreBundle\WebpackEncoreBundle(),
|
||||
new FrameworkBundle(),
|
||||
new SecurityBundle(),
|
||||
new TwigBundle(),
|
||||
new MonologBundle(),
|
||||
new DoctrineBundle(),
|
||||
new SensioFrameworkExtraBundle(),
|
||||
new FOSRestBundle(),
|
||||
new FOSUserBundle(),
|
||||
new JMSSerializerBundle(),
|
||||
new NelmioApiDocBundle(),
|
||||
new NelmioCorsBundle(),
|
||||
new BazingaHateoasBundle(),
|
||||
new SpiriitFormFilterBundle(),
|
||||
new FOSOAuthServerBundle(),
|
||||
new StofDoctrineExtensionsBundle(),
|
||||
new SchebTwoFactorBundle(),
|
||||
new KPhoenRulerZBundle(),
|
||||
new DoctrineMigrationsBundle(),
|
||||
new CraueConfigBundle(),
|
||||
new BabDevPagerfantaBundle(),
|
||||
new FOSJsRoutingBundle(),
|
||||
new OldSoundRabbitMqBundle(),
|
||||
new SentryBundle(),
|
||||
new TwigExtraBundle(),
|
||||
new WebpackEncoreBundle(),
|
||||
];
|
||||
|
||||
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
|
||||
$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
|
||||
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
|
||||
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
|
||||
$bundles[] = new DebugBundle();
|
||||
$bundles[] = new WebProfilerBundle();
|
||||
$bundles[] = new DoctrineFixturesBundle();
|
||||
|
||||
if ('test' === $this->getEnvironment()) {
|
||||
$bundles[] = new DAMA\DoctrineTestBundle\DAMADoctrineTestBundle();
|
||||
$bundles[] = new DAMADoctrineTestBundle();
|
||||
}
|
||||
|
||||
if ('dev' === $this->getEnvironment()) {
|
||||
$bundles[] = new Symfony\Bundle\MakerBundle\MakerBundle();
|
||||
$bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
|
||||
$bundles[] = new MakerBundle();
|
||||
$bundles[] = new WebServerBundle();
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,13 +100,13 @@ class AppKernel extends Kernel
|
||||
{
|
||||
$loader->load($this->getProjectDir() . '/app/config/config_' . $this->getEnvironment() . '.yml');
|
||||
|
||||
$loader->load(function (ContainerBuilder $container) {
|
||||
$loader->load(function (ContainerBuilder $container): void {
|
||||
// $container->setParameter('container.autowiring.strict_mode', true);
|
||||
// $container->setParameter('container.dumper.inline_class_loader', true);
|
||||
$container->addObjectResource($this);
|
||||
});
|
||||
|
||||
$loader->load(function (ContainerBuilder $container) {
|
||||
$loader->load(function (ContainerBuilder $container): void {
|
||||
$this->processDatabaseParameters($container);
|
||||
$this->defineRedisUrlEnvVar($container);
|
||||
$this->defineRabbitMqUrlEnvVar($container);
|
||||
@ -89,19 +120,12 @@ class AppKernel extends Kernel
|
||||
|
||||
private function processDatabaseParameters(ContainerBuilder $container)
|
||||
{
|
||||
switch ($container->getParameter('database_driver')) {
|
||||
case 'pdo_mysql':
|
||||
$scheme = 'mysql';
|
||||
break;
|
||||
case 'pdo_pgsql':
|
||||
$scheme = 'pgsql';
|
||||
break;
|
||||
case 'pdo_sqlite':
|
||||
$scheme = 'sqlite';
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException('Unsupported database driver: ' . $container->getParameter('database_driver'));
|
||||
}
|
||||
$scheme = match ($container->getParameter('database_driver')) {
|
||||
'pdo_mysql' => 'mysql',
|
||||
'pdo_pgsql' => 'pgsql',
|
||||
'pdo_sqlite' => 'sqlite',
|
||||
default => throw new RuntimeException('Unsupported database driver: ' . $container->getParameter('database_driver')),
|
||||
};
|
||||
|
||||
$container->setParameter('database_scheme', $scheme);
|
||||
|
||||
|
||||
@ -29,6 +29,8 @@ framework:
|
||||
handler_id: session.handler.native_file
|
||||
save_path: "%kernel.project_dir%/var/sessions/%kernel.environment%"
|
||||
cookie_secure: auto
|
||||
cookie_samesite: lax
|
||||
storage_factory_id: session.storage.factory.native
|
||||
fragments: ~
|
||||
http_method_override: true
|
||||
assets:
|
||||
@ -77,7 +79,7 @@ doctrine:
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
Wallabag:
|
||||
type: annotation
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'Wallabag\Entity'
|
||||
@ -197,7 +199,7 @@ fos_user:
|
||||
address: "%from_email%"
|
||||
sender_name: wallabag
|
||||
service:
|
||||
mailer: Wallabag\Mailer\UserMailer
|
||||
mailer: fos_user.mailer.twig_symfony
|
||||
|
||||
fos_oauth_server:
|
||||
db_driver: orm
|
||||
@ -232,10 +234,6 @@ scheb_two_factor:
|
||||
template: "Authentication/form.html.twig"
|
||||
mailer: Wallabag\Mailer\AuthCodeMailer
|
||||
|
||||
rulerz:
|
||||
targets:
|
||||
doctrine: true
|
||||
|
||||
old_sound_rabbit_mq:
|
||||
connections:
|
||||
default:
|
||||
@ -307,6 +305,11 @@ old_sound_rabbit_mq:
|
||||
exchange_options:
|
||||
name: 'wallabag.import.pocket_html'
|
||||
type: topic
|
||||
import_pocket_csv:
|
||||
connection: default
|
||||
exchange_options:
|
||||
name: 'wallabag.import.pocket_csv'
|
||||
type: topic
|
||||
consumers:
|
||||
import_pocket:
|
||||
connection: default
|
||||
@ -425,6 +428,15 @@ old_sound_rabbit_mq:
|
||||
name: 'wallabag.import.pocket_html'
|
||||
callback: wallabag.consumer.amqp.pocket_html
|
||||
qos_options: {prefetch_count: "%rabbitmq_prefetch_count%"}
|
||||
import_pocket_csv:
|
||||
connection: default
|
||||
exchange_options:
|
||||
name: 'wallabag.import.pocket_csv'
|
||||
type: topic
|
||||
queue_options:
|
||||
name: 'wallabag.import.pocket_csv'
|
||||
callback: wallabag.consumer.amqp.pocket_csv
|
||||
qos_options: {prefetch_count: "%rabbitmq_prefetch_count%"}
|
||||
|
||||
fos_js_routing:
|
||||
routes_to_expose:
|
||||
|
||||
@ -10,7 +10,7 @@ parameters:
|
||||
framework:
|
||||
test: ~
|
||||
session:
|
||||
storage_id: session.storage.mock_file
|
||||
storage_factory_id: session.storage.factory.mock_file
|
||||
profiler:
|
||||
collect: false
|
||||
translator:
|
||||
|
||||
@ -33,7 +33,7 @@ craue_config_settings_modify:
|
||||
_controller: 'Craue\ConfigBundle\Controller\SettingsController::modifyAction'
|
||||
|
||||
fos_js_routing:
|
||||
resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml"
|
||||
resource: "@FOSJsRoutingBundle/Resources/config/routing/routing-sf4.xml"
|
||||
|
||||
2fa_login:
|
||||
path: /2fa
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
security:
|
||||
encoders:
|
||||
password_hashers:
|
||||
FOS\UserBundle\Model\UserInterface: sha512
|
||||
|
||||
role_hierarchy:
|
||||
@ -70,10 +70,9 @@ security:
|
||||
- { path: /(unread|starred|archive|annotated|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/locale, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: /tags/(.*).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/feed, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/feed, roles: PUBLIC_ACCESS }
|
||||
- { path: /(unread|starred|archive|annotated).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } # For backwards compatibility
|
||||
- { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/settings, roles: ROLE_SUPER_ADMIN }
|
||||
- { path: ^/annotations, roles: ROLE_USER }
|
||||
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
|
||||
- { path: ^/, roles: ROLE_USER }
|
||||
|
||||
@ -108,6 +108,11 @@ services:
|
||||
$rabbitMqProducer: '@old_sound_rabbit_mq.import_pocket_html_producer'
|
||||
$redisProducer: '@wallabag.producer.redis.pocket_html'
|
||||
|
||||
Wallabag\Controller\Import\PocketCsvController:
|
||||
arguments:
|
||||
$rabbitMqProducer: '@old_sound_rabbit_mq.import_pocket_csv_producer'
|
||||
$redisProducer: '@wallabag.producer.redis.pocket_csv'
|
||||
|
||||
Wallabag\Doctrine\MigrationFactoryDecorator:
|
||||
decorates: doctrine.migrations.migrations_factory
|
||||
|
||||
@ -230,18 +235,10 @@ services:
|
||||
tags:
|
||||
- { name: rulerz.operator, target: native, operator: matches }
|
||||
|
||||
Wallabag\Operator\Doctrine\Matches:
|
||||
tags:
|
||||
- { name: rulerz.operator, target: doctrine, operator: matches, inline: true }
|
||||
|
||||
Wallabag\Operator\PHP\NotMatches:
|
||||
tags:
|
||||
- { name: rulerz.operator, target: native, operator: notmatches }
|
||||
|
||||
Wallabag\Operator\Doctrine\NotMatches:
|
||||
tags:
|
||||
- { name: rulerz.operator, target: doctrine, operator: notmatches, inline: true }
|
||||
|
||||
Wallabag\Operator\PHP\PatternMatches:
|
||||
tags:
|
||||
- { name: rulerz.operator, target: native, operator: "~" }
|
||||
@ -268,16 +265,6 @@ services:
|
||||
$defaultSettings: '%wallabag.default_internal_settings%'
|
||||
$defaultIgnoreOriginInstanceRules: '%wallabag.default_ignore_origin_instance_rules%'
|
||||
|
||||
Wallabag\Mailer\UserMailer:
|
||||
arguments:
|
||||
$parameters:
|
||||
template:
|
||||
confirmation: '%fos_user.registration.confirmation.template%'
|
||||
resetting: '%fos_user.resetting.email.template%'
|
||||
from_email:
|
||||
confirmation: '%fos_user.registration.confirmation.from_email%'
|
||||
resetting: '%fos_user.resetting.email.from_email%'
|
||||
|
||||
Wallabag\Event\Listener\CreateConfigListener:
|
||||
arguments:
|
||||
$itemsOnPage: "%wallabag.items_on_page%"
|
||||
@ -346,6 +333,10 @@ services:
|
||||
tags:
|
||||
- { name: wallabag.import, alias: pocket_html }
|
||||
|
||||
Wallabag\Import\PocketCsvImport:
|
||||
tags:
|
||||
- { name: wallabag.import, alias: pocket_csv }
|
||||
|
||||
# to factorize the proximity and bypass translation for prev & next
|
||||
pagerfanta.view.default_wallabag:
|
||||
class: Pagerfanta\View\OptionableView
|
||||
|
||||
@ -20,6 +20,7 @@ services:
|
||||
$elcuratorConsumer: '@old_sound_rabbit_mq.import_elcurator_consumer'
|
||||
$shaarliConsumer: '@old_sound_rabbit_mq.import_shaarli_consumer'
|
||||
$pocketHtmlConsumer: '@old_sound_rabbit_mq.import_pocket_html_consumer'
|
||||
$pocketCsvConsumer: '@old_sound_rabbit_mq.import_pocket_csv_consumer'
|
||||
$omnivoreConsumer: '@old_sound_rabbit_mq.import_omnivore_consumer'
|
||||
|
||||
wallabag.consumer.amqp.pocket:
|
||||
@ -86,3 +87,8 @@ services:
|
||||
class: Wallabag\Consumer\AMQPEntryConsumer
|
||||
arguments:
|
||||
$import: '@Wallabag\Import\PocketHtmlImport'
|
||||
|
||||
wallabag.consumer.amqp.pocket_csv:
|
||||
class: Wallabag\Consumer\AMQPEntryConsumer
|
||||
arguments:
|
||||
$import: '@Wallabag\Import\PocketCsvImport'
|
||||
|
||||
@ -212,3 +212,19 @@ services:
|
||||
class: Wallabag\Consumer\RedisEntryConsumer
|
||||
arguments:
|
||||
$import: '@Wallabag\Import\PocketHtmlImport'
|
||||
|
||||
# pocket csv
|
||||
wallabag.queue.redis.pocket_csv:
|
||||
class: Simpleue\Queue\RedisQueue
|
||||
arguments:
|
||||
$queueName: "wallabag.import.pocket_csv"
|
||||
|
||||
wallabag.producer.redis.pocket_csv:
|
||||
class: Wallabag\Redis\Producer
|
||||
arguments:
|
||||
- "@wallabag.queue.redis.pocket_csv"
|
||||
|
||||
wallabag.consumer.redis.pocket_csv:
|
||||
class: Wallabag\Consumer\RedisEntryConsumer
|
||||
arguments:
|
||||
$import: '@Wallabag\Import\PocketCsvImport'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
parameters:
|
||||
wallabag.version: 2.6.10
|
||||
wallabag.version: 2.7.0-dev
|
||||
wallabag.paypal_url: "https://liberapay.com/wallabag/donate"
|
||||
wallabag.languages:
|
||||
en: 'English'
|
||||
|
||||
11
assets/bootstrap.js
vendored
Normal file
11
assets/bootstrap.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import { startStimulusApp } from '@symfony/stimulus-bridge';
|
||||
|
||||
// Registers Stimulus controllers from controllers.json and in the controllers/ directory
|
||||
export default startStimulusApp(require.context(
|
||||
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
|
||||
true,
|
||||
/\.[jt]sx?$/,
|
||||
));
|
||||
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
4
assets/controllers.json
Normal file
4
assets/controllers.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"controllers": [],
|
||||
"entrypoints": []
|
||||
}
|
||||
13
assets/controllers/add_tag_controller.js
Normal file
13
assets/controllers/add_tag_controller.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['input'];
|
||||
|
||||
toggle() {
|
||||
this.element.classList.toggle('hidden');
|
||||
|
||||
if (!this.element.classList.contains('hidden')) {
|
||||
this.inputTarget.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
57
assets/controllers/annotations_controller.js
Normal file
57
assets/controllers/annotations_controller.js
Normal file
@ -0,0 +1,57 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import annotator from 'annotator';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
entryId: Number,
|
||||
createUrl: String,
|
||||
updateUrl: String,
|
||||
destroyUrl: String,
|
||||
searchUrl: String,
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.app = new annotator.App();
|
||||
|
||||
this.app.include(annotator.ui.main, {
|
||||
element: this.element,
|
||||
});
|
||||
|
||||
const authorization = {
|
||||
permits() { return true; },
|
||||
};
|
||||
this.app.registry.registerUtility(authorization, 'authorizationPolicy');
|
||||
|
||||
this.app.include(annotator.storage.http, {
|
||||
prefix: '',
|
||||
urls: {
|
||||
create: this.createUrlValue,
|
||||
update: this.updateUrlValue,
|
||||
destroy: this.destroyUrlValue,
|
||||
search: this.searchUrlValue,
|
||||
},
|
||||
entryId: this.entryIdValue,
|
||||
onError(msg, xhr) {
|
||||
if (!Object.prototype.hasOwnProperty.call(xhr, 'responseJSON')) {
|
||||
annotator.notification.banner('An error occurred', 'error');
|
||||
return;
|
||||
}
|
||||
Object.values(xhr.responseJSON.children).forEach((v) => {
|
||||
if (v.errors) {
|
||||
Object.values(v.errors).forEach((errorText) => {
|
||||
annotator.notification.banner(errorText, 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
this.app.start().then(() => {
|
||||
this.app.annotations.load({ entry: this.entryIdValue });
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.app.destroy();
|
||||
}
|
||||
}
|
||||
15
assets/controllers/batch_edit_controller.js
Normal file
15
assets/controllers/batch_edit_controller.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['item', 'tagAction'];
|
||||
|
||||
toggleSelection(e) {
|
||||
this.itemTargets.forEach((item) => {
|
||||
item.checked = e.currentTarget.checked; // eslint-disable-line no-param-reassign
|
||||
});
|
||||
}
|
||||
|
||||
tagSelection() {
|
||||
this.element.requestSubmit(this.tagActionTarget);
|
||||
}
|
||||
}
|
||||
16
assets/controllers/clipboard_controller.js
Normal file
16
assets/controllers/clipboard_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import ClipboardJS from 'clipboard';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.clipboard = new ClipboardJS(this.element);
|
||||
|
||||
this.clipboard.on('success', (e) => {
|
||||
e.clearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.clipboard.destroy();
|
||||
}
|
||||
}
|
||||
16
assets/controllers/config_controller.js
Normal file
16
assets/controllers/config_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['previewArticle', 'previewContent', 'font', 'fontSize', 'lineHeight', 'maxWidth'];
|
||||
|
||||
connect() {
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
updatePreview() {
|
||||
this.previewArticleTarget.style.maxWidth = `${this.maxWidthTarget.value}em`;
|
||||
this.previewContentTarget.style.fontFamily = this.fontTarget.value;
|
||||
this.previewContentTarget.style.fontSize = `${this.fontSizeTarget.value}em`;
|
||||
this.previewContentTarget.style.lineHeight = `${this.lineHeightTarget.value}em`;
|
||||
}
|
||||
}
|
||||
39
assets/controllers/dark_theme_controller.js
Normal file
39
assets/controllers/dark_theme_controller.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.#choose();
|
||||
|
||||
this.mql = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.mql.addEventListener('change', this.#choose.bind(this));
|
||||
}
|
||||
|
||||
useLight() {
|
||||
this.element.classList.remove('dark-theme');
|
||||
document.cookie = 'theme=light;samesite=Lax;path=/;max-age=31536000';
|
||||
}
|
||||
|
||||
useDark() {
|
||||
this.element.classList.add('dark-theme');
|
||||
document.cookie = 'theme=dark;samesite=Lax;path=/;max-age=31536000';
|
||||
}
|
||||
|
||||
useAuto() {
|
||||
document.cookie = 'theme=auto;samesite=Lax;path=/;max-age=0';
|
||||
this.#choose();
|
||||
}
|
||||
|
||||
#choose() {
|
||||
const themeCookieExists = document.cookie.split(';').some((cookie) => cookie.trim().startsWith('theme='));
|
||||
|
||||
if (themeCookieExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mql.matches) {
|
||||
this.element.classList.add('dark-theme');
|
||||
} else {
|
||||
this.element.classList.remove('dark-theme');
|
||||
}
|
||||
}
|
||||
}
|
||||
58
assets/controllers/entries_navigation_controller.js
Normal file
58
assets/controllers/entries_navigation_controller.js
Normal file
@ -0,0 +1,58 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['card', 'paginationWrapper'];
|
||||
|
||||
connect() {
|
||||
this.pagination = this.paginationWrapperTarget.querySelector('.pagination');
|
||||
|
||||
this.cardIndex = 0;
|
||||
this.lastCardIndex = this.cardTargets.length - 1;
|
||||
|
||||
/* If we come from next page */
|
||||
if (window.location.hash === '#prev') {
|
||||
this.cardIndex = this.lastCardIndex;
|
||||
}
|
||||
|
||||
this.currentCard = this.cardTargets[this.cardIndex];
|
||||
|
||||
this.currentCard.classList.add('z-depth-4');
|
||||
}
|
||||
|
||||
selectRightCard() {
|
||||
if (this.cardIndex >= 0 && this.cardIndex < this.lastCardIndex) {
|
||||
this.currentCard.classList.remove('z-depth-4');
|
||||
this.cardIndex += 1;
|
||||
this.currentCard = this.cardTargets[this.cardIndex];
|
||||
this.currentCard.classList.add('z-depth-4');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pagination && this.pagination.querySelector('a[rel="next"]')) {
|
||||
window.location.href = this.pagination.querySelector('a[rel="next"]').href;
|
||||
}
|
||||
}
|
||||
|
||||
selectLeftCard() {
|
||||
if (this.cardIndex > 0 && this.cardIndex <= this.lastCardIndex) {
|
||||
this.currentCard.classList.remove('z-depth-4');
|
||||
this.cardIndex -= 1;
|
||||
this.currentCard = this.cardTargets[this.cardIndex];
|
||||
this.currentCard.classList.add('z-depth-4');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pagination && this.pagination.querySelector('a[rel="prev"]')) {
|
||||
window.location.href = `${this.pagination.querySelector('a[rel="prev"]').href}#prev`;
|
||||
}
|
||||
}
|
||||
|
||||
selectCurrentCard() {
|
||||
const url = this.currentCard.querySelector('a.card-title').href;
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
assets/controllers/fake_radio_controller.js
Normal file
13
assets/controllers/fake_radio_controller.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['emailTwoFactor', 'googleTwoFactor'];
|
||||
|
||||
uncheckGoogle() {
|
||||
this.googleTwoFactorTarget.checked = false;
|
||||
}
|
||||
|
||||
uncheckEmail() {
|
||||
this.emailTwoFactorTarget.checked = false;
|
||||
}
|
||||
}
|
||||
11
assets/controllers/highlight_controller.js
Normal file
11
assets/controllers/highlight_controller.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import 'highlight.js/styles/atom-one-light.css';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.querySelectorAll('pre code').forEach((element) => {
|
||||
hljs.highlightElement(element);
|
||||
});
|
||||
}
|
||||
}
|
||||
7
assets/controllers/leftbar_controller.js
Normal file
7
assets/controllers/leftbar_controller.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
toggleAddTagForm() {
|
||||
this.dispatch('toggleAddTagForm');
|
||||
}
|
||||
}
|
||||
16
assets/controllers/materialize/collapsible_controller.js
Normal file
16
assets/controllers/materialize/collapsible_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
accordion: { type: Boolean, default: true },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.instance = M.Collapsible.init(this.element, { accordion: this.accordionValue });
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
16
assets/controllers/materialize/dropdown_controller.js
Normal file
16
assets/controllers/materialize/dropdown_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.Dropdown.init(this.element, {
|
||||
hover: false,
|
||||
coverTrigger: false,
|
||||
constrainWidth: false,
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
32
assets/controllers/materialize/fab_controller.js
Normal file
32
assets/controllers/materialize/fab_controller.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
edge: { type: String, default: 'left' },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.instance = M.FloatingActionButton.init(this.element);
|
||||
}
|
||||
|
||||
autoDisplay() {
|
||||
const scrolled = (window.innerHeight + window.scrollY) >= document.body.offsetHeight;
|
||||
|
||||
if (scrolled) {
|
||||
this.toggleScroll = true;
|
||||
this.instance.open();
|
||||
} else if (this.toggleScroll === true) {
|
||||
this.toggleScroll = false;
|
||||
this.instance.close();
|
||||
}
|
||||
}
|
||||
|
||||
click() {
|
||||
this.dispatch('click');
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
12
assets/controllers/materialize/form_select_controller.js
Normal file
12
assets/controllers/materialize/form_select_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.FormSelect.init(this.element.querySelector('select'));
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
24
assets/controllers/materialize/sidenav_controller.js
Normal file
24
assets/controllers/materialize/sidenav_controller.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
const mobileMaxWidth = 993;
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
edge: { type: String, default: 'left' },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.instance = M.Sidenav.init(this.element, { edge: this.edgeValue });
|
||||
}
|
||||
|
||||
close() {
|
||||
if (window.innerWidth < mobileMaxWidth) {
|
||||
this.instance.close();
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
12
assets/controllers/materialize/tabs_controller.js
Normal file
12
assets/controllers/materialize/tabs_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.Tabs.init(this.element);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
12
assets/controllers/materialize/toast_controller.js
Normal file
12
assets/controllers/materialize/toast_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.toast({ text: this.element.innerText });
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.dismissAll();
|
||||
}
|
||||
}
|
||||
12
assets/controllers/materialize/tooltip_controller.js
Normal file
12
assets/controllers/materialize/tooltip_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.Tooltip.init(this.element);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
10
assets/controllers/qrcode_controller.js
Normal file
10
assets/controllers/qrcode_controller.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import jrQrcode from 'jr-qrcode';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { url: String };
|
||||
|
||||
connect() {
|
||||
this.element.setAttribute('src', jrQrcode.getQrBase64(this.urlValue));
|
||||
}
|
||||
}
|
||||
10
assets/controllers/scroll_indicator_controller.js
Normal file
10
assets/controllers/scroll_indicator_controller.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
updateWidth() {
|
||||
const referenceHeight = document.body.offsetHeight - window.innerHeight;
|
||||
const scrollPercent = (window.scrollY / referenceHeight) * 100;
|
||||
|
||||
this.element.style.width = `${scrollPercent}%`;
|
||||
}
|
||||
}
|
||||
19
assets/controllers/scroll_storage_controller.js
Normal file
19
assets/controllers/scroll_storage_controller.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { entryId: Number };
|
||||
|
||||
connect() {
|
||||
window.scrollTo({
|
||||
top: window.innerHeight * localStorage[`wallabag.article.${this.entryIdValue}.percent`],
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
saveScroll() {
|
||||
const scrollPercent = window.scrollY / window.innerHeight;
|
||||
const scrollPercentRounded = Math.round(scrollPercent * 100) / 100;
|
||||
|
||||
localStorage[`wallabag.article.${this.entryIdValue}.percent`] = scrollPercentRounded;
|
||||
}
|
||||
}
|
||||
141
assets/controllers/shortcuts_controller.js
Normal file
141
assets/controllers/shortcuts_controller.js
Normal file
@ -0,0 +1,141 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import Mousetrap from 'mousetrap';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['openOriginal', 'markAsFavorite', 'markAsRead', 'deleteEntry', 'showAddUrl', 'showSearch', 'showActions'];
|
||||
|
||||
static outlets = ['entries-navigation'];
|
||||
|
||||
connect() {
|
||||
/* Go to */
|
||||
Mousetrap.bind('g u', () => {
|
||||
window.location.href = Routing.generate('homepage');
|
||||
});
|
||||
Mousetrap.bind('g s', () => {
|
||||
window.location.href = Routing.generate('starred');
|
||||
});
|
||||
Mousetrap.bind('g r', () => {
|
||||
window.location.href = Routing.generate('archive');
|
||||
});
|
||||
Mousetrap.bind('g a', () => {
|
||||
window.location.href = Routing.generate('all');
|
||||
});
|
||||
Mousetrap.bind('g t', () => {
|
||||
window.location.href = Routing.generate('tag');
|
||||
});
|
||||
Mousetrap.bind('g c', () => {
|
||||
window.location.href = Routing.generate('config');
|
||||
});
|
||||
Mousetrap.bind('g i', () => {
|
||||
window.location.href = Routing.generate('import');
|
||||
});
|
||||
Mousetrap.bind('g d', () => {
|
||||
window.location.href = Routing.generate('developer');
|
||||
});
|
||||
Mousetrap.bind('?', () => {
|
||||
window.location.href = Routing.generate('howto');
|
||||
});
|
||||
Mousetrap.bind('g l', () => {
|
||||
window.location.href = Routing.generate('fos_user_security_logout');
|
||||
});
|
||||
|
||||
/* open original article */
|
||||
Mousetrap.bind('o', () => {
|
||||
if (!this.hasOpenOriginalTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openOriginalTarget.click();
|
||||
});
|
||||
|
||||
/* mark as favorite */
|
||||
Mousetrap.bind('f', () => {
|
||||
if (!this.hasMarkAsFavoriteTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.markAsFavoriteTarget.click();
|
||||
});
|
||||
|
||||
/* mark as read */
|
||||
Mousetrap.bind('a', () => {
|
||||
if (!this.hasMarkAsReadTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.markAsReadTarget.click();
|
||||
});
|
||||
|
||||
/* delete */
|
||||
Mousetrap.bind('del', () => {
|
||||
if (!this.hasDeleteEntryTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deleteEntryTarget.click();
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
Mousetrap.bind('g n', (e) => {
|
||||
if (!this.hasShowAddUrlTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.showAddUrlTarget.click();
|
||||
});
|
||||
|
||||
Mousetrap.bind('s', (e) => {
|
||||
if (!this.hasShowSearchTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.showSearchTarget.click();
|
||||
});
|
||||
|
||||
Mousetrap.bind('esc', (e) => {
|
||||
if (!this.hasShowActionsTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.showActionsTarget.click();
|
||||
});
|
||||
|
||||
const originalStopCallback = Mousetrap.prototype.stopCallback;
|
||||
|
||||
Mousetrap.prototype.stopCallback = (e, element, combo) => {
|
||||
// allow esc key to be used in input fields of topbar
|
||||
if (combo === 'esc' && element.dataset.topbarTarget !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return originalStopCallback(e, element);
|
||||
};
|
||||
|
||||
Mousetrap.bind('right', () => {
|
||||
if (!this.hasEntriesNavigationOutlet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.entriesNavigationOutlet.selectRightCard();
|
||||
});
|
||||
|
||||
Mousetrap.bind('left', () => {
|
||||
if (!this.hasEntriesNavigationOutlet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.entriesNavigationOutlet.selectLeftCard();
|
||||
});
|
||||
|
||||
Mousetrap.bind('enter', () => {
|
||||
if (!this.hasEntriesNavigationOutlet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.entriesNavigationOutlet.selectCurrentCard();
|
||||
});
|
||||
}
|
||||
}
|
||||
7
assets/controllers/sticky_nav_controller.js
Normal file
7
assets/controllers/sticky_nav_controller.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
toggle() {
|
||||
this.element.classList.toggle('entry-nav-top--sticky');
|
||||
}
|
||||
}
|
||||
12
assets/controllers/tag_controller.js
Normal file
12
assets/controllers/tag_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['link', 'edit', 'form', 'input'];
|
||||
|
||||
showForm() {
|
||||
this.formTarget.classList.remove('hidden');
|
||||
this.editTarget.classList.add('hidden');
|
||||
this.linkTarget.classList.add('hidden');
|
||||
this.inputTarget.focus();
|
||||
}
|
||||
}
|
||||
31
assets/controllers/topbar_controller.js
Normal file
31
assets/controllers/topbar_controller.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['addUrl', 'addUrlInput', 'search', 'searchInput', 'actions'];
|
||||
|
||||
showAddUrl() {
|
||||
this.actionsTarget.style.display = 'none';
|
||||
this.addUrlTarget.style.display = 'flex';
|
||||
this.searchTarget.style.display = 'none';
|
||||
this.addUrlInputTarget.focus();
|
||||
}
|
||||
|
||||
submittingUrl(e) {
|
||||
e.currentTarget.disabled = true;
|
||||
this.addUrlInputTarget.readOnly = true;
|
||||
this.addUrlInputTarget.blur();
|
||||
}
|
||||
|
||||
showSearch() {
|
||||
this.actionsTarget.style.display = 'none';
|
||||
this.addUrlTarget.style.display = 'none';
|
||||
this.searchTarget.style.display = 'flex';
|
||||
this.searchInputTarget.focus();
|
||||
}
|
||||
|
||||
showActions() {
|
||||
this.actionsTarget.style.display = 'flex';
|
||||
this.addUrlTarget.style.display = 'none';
|
||||
this.searchTarget.style.display = 'none';
|
||||
}
|
||||
}
|
||||
377
assets/index.js
377
assets/index.js
@ -1,18 +1,11 @@
|
||||
import $ from 'jquery';
|
||||
import './bootstrap';
|
||||
|
||||
/* Materialize imports */
|
||||
import '@materializecss/materialize/dist/css/materialize.css';
|
||||
import M from '@materializecss/materialize/dist/js/materialize';
|
||||
import '@materializecss/materialize/dist/js/materialize';
|
||||
|
||||
/* Annotations */
|
||||
import annotator from 'annotator';
|
||||
|
||||
import ClipboardJS from 'clipboard';
|
||||
import 'mathjax/es5/tex-svg';
|
||||
|
||||
/* jrQrcode */
|
||||
import jrQrcode from 'jr-qrcode';
|
||||
|
||||
/* Fonts */
|
||||
import 'material-design-icons-iconfont/dist/material-design-icons.css';
|
||||
import 'lato-font/css/lato-font.css';
|
||||
@ -22,371 +15,5 @@ import '@fontsource/eb-garamond';
|
||||
import '@fontsource/montserrat';
|
||||
import '@fontsource/oswald';
|
||||
|
||||
/* Highlight */
|
||||
import './js/highlight';
|
||||
|
||||
/* Tools */
|
||||
import {
|
||||
savePercent, retrievePercent, initExport, initFilters, initRandom, initPreviewText,
|
||||
} from './js/tools';
|
||||
|
||||
/* Import shortcuts */
|
||||
import './js/shortcuts/main';
|
||||
import './js/shortcuts/entry';
|
||||
|
||||
/* Theme style */
|
||||
import './scss/index.scss';
|
||||
|
||||
const mobileMaxWidth = 993;
|
||||
|
||||
/* ==========================================================================
|
||||
Annotations & Remember position
|
||||
========================================================================== */
|
||||
|
||||
$(document).ready(() => {
|
||||
if ($('#article').length) {
|
||||
const app = new annotator.App();
|
||||
|
||||
app.include(annotator.ui.main, {
|
||||
element: document.querySelector('article'),
|
||||
});
|
||||
|
||||
const authorization = {
|
||||
permits() { return true; },
|
||||
};
|
||||
app.registry.registerUtility(authorization, 'authorizationPolicy');
|
||||
|
||||
const x = JSON.parse($('#annotationroutes').html());
|
||||
app.include(annotator.storage.http, $.extend({}, x, {
|
||||
onError(msg, xhr) {
|
||||
if (!Object.prototype.hasOwnProperty.call(xhr, 'responseJSON')) {
|
||||
annotator.notification.banner('An error occurred', 'error');
|
||||
return;
|
||||
}
|
||||
$.each(xhr.responseJSON.children, (k, v) => {
|
||||
if (v.errors) {
|
||||
$.each(v.errors, (n, errorText) => {
|
||||
annotator.notification.banner(errorText, 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
app.start().then(() => {
|
||||
app.annotations.load({ entry: x.entryId });
|
||||
});
|
||||
|
||||
$(window).scroll(() => {
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const docHeight = $(document).height();
|
||||
const scrollPercent = (scrollTop) / (docHeight);
|
||||
const scrollPercentRounded = Math.round(scrollPercent * 100) / 100;
|
||||
savePercent(x.entryId, scrollPercentRounded);
|
||||
});
|
||||
|
||||
retrievePercent(x.entryId);
|
||||
|
||||
$(window).resize(() => {
|
||||
retrievePercent(x.entryId, true);
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-handler=tag-rename]').forEach((item) => {
|
||||
const current = item;
|
||||
current.wallabag_edit_mode = false;
|
||||
current.onclick = (event) => {
|
||||
const target = event.currentTarget;
|
||||
|
||||
if (target.wallabag_edit_mode === false) {
|
||||
$(target.parentNode.querySelector('[data-handle=tag-link]')).addClass('hidden');
|
||||
$(target.parentNode.querySelector('[data-handle=tag-rename-form]')).removeClass('hidden');
|
||||
target.parentNode.querySelector('[data-handle=tag-rename-form] input').focus();
|
||||
target.querySelector('.material-icons').innerHTML = 'done';
|
||||
|
||||
target.wallabag_edit_mode = true;
|
||||
} else {
|
||||
target.parentNode.querySelector('[data-handle=tag-rename-form]').submit();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// mimic radio button because emailTwoFactor is a boolean
|
||||
$('#update_user_googleTwoFactor').on('change', () => {
|
||||
$('#update_user_emailTwoFactor').prop('checked', false);
|
||||
});
|
||||
|
||||
$('#update_user_emailTwoFactor').on('change', () => {
|
||||
$('#update_user_googleTwoFactor').prop('checked', false);
|
||||
});
|
||||
|
||||
// same mimic for super admin
|
||||
$('#user_googleTwoFactor').on('change', () => {
|
||||
$('#user_emailTwoFactor').prop('checked', false);
|
||||
});
|
||||
|
||||
$('#user_emailTwoFactor').on('change', () => {
|
||||
$('#user_googleTwoFactor').prop('checked', false);
|
||||
});
|
||||
|
||||
// handle copy to clipboard for developer stuff
|
||||
const clipboard = new ClipboardJS('.btn');
|
||||
clipboard.on('success', (e) => {
|
||||
e.clearSelection();
|
||||
});
|
||||
});
|
||||
|
||||
(function darkTheme() {
|
||||
const rootEl = document.querySelector('html');
|
||||
const themeDom = {
|
||||
darkClass: 'dark-theme',
|
||||
|
||||
toggleClass(el) {
|
||||
return el.classList.toggle(this.darkClass);
|
||||
},
|
||||
|
||||
addClass(el) {
|
||||
return el.classList.add(this.darkClass);
|
||||
},
|
||||
|
||||
removeClass(el) {
|
||||
return el.classList.remove(this.darkClass);
|
||||
},
|
||||
};
|
||||
const themeCookie = {
|
||||
values: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
},
|
||||
|
||||
name: 'theme',
|
||||
|
||||
getValue(isDarkTheme) {
|
||||
return isDarkTheme ? this.values.dark : this.values.light;
|
||||
},
|
||||
|
||||
setCookie(isDarkTheme) {
|
||||
const value = this.getValue(isDarkTheme);
|
||||
document.cookie = `${this.name}=${value};samesite=Lax;path=/;max-age=31536000`;
|
||||
},
|
||||
|
||||
removeCookie() {
|
||||
document.cookie = `${this.name}=auto;samesite=Lax;path=/;max-age=0`;
|
||||
},
|
||||
|
||||
exists() {
|
||||
return document.cookie.split(';').some((cookie) => cookie.trim().startsWith(`${this.name}=`));
|
||||
},
|
||||
};
|
||||
const preferedColorScheme = {
|
||||
choose() {
|
||||
const themeCookieExists = themeCookie.exists();
|
||||
if (this.isAvailable() && !themeCookieExists) {
|
||||
const isPreferedColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)').matches === true;
|
||||
if (!themeCookieExists) {
|
||||
themeDom[isPreferedColorSchemeDark ? 'addClass' : 'removeClass'](rootEl);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
isAvailable() {
|
||||
return typeof window.matchMedia === 'function';
|
||||
},
|
||||
|
||||
init() {
|
||||
if (!this.isAvailable()) {
|
||||
return false;
|
||||
}
|
||||
this.choose();
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addListener(() => {
|
||||
this.choose();
|
||||
});
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
const addDarkThemeListeners = () => {
|
||||
$(document).ready(() => {
|
||||
const lightThemeButtons = document.querySelectorAll('.js-theme-toggle[data-theme="light"]');
|
||||
[...lightThemeButtons].map((lightThemeButton) => {
|
||||
lightThemeButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
themeDom.removeClass(rootEl);
|
||||
themeCookie.setCookie(false);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
const darkThemeButtons = document.querySelectorAll('.js-theme-toggle[data-theme="dark"]');
|
||||
[...darkThemeButtons].map((darkThemeButton) => {
|
||||
darkThemeButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
themeDom.addClass(rootEl);
|
||||
themeCookie.setCookie(true);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
const autoThemeButtons = document.querySelectorAll('.js-theme-toggle[data-theme="auto"]');
|
||||
[...autoThemeButtons].map((autoThemeButton) => {
|
||||
autoThemeButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
themeCookie.removeCookie();
|
||||
preferedColorScheme.choose();
|
||||
});
|
||||
return true;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
preferedColorScheme.init();
|
||||
addDarkThemeListeners();
|
||||
}());
|
||||
|
||||
const stickyNav = () => {
|
||||
const nav = $('.js-entry-nav-top');
|
||||
$('[data-toggle="actions"]').click(() => {
|
||||
nav.toggleClass('entry-nav-top--sticky');
|
||||
});
|
||||
};
|
||||
|
||||
const articleScroll = () => {
|
||||
const articleEl = $('#article');
|
||||
if (articleEl.length > 0) {
|
||||
$(window).scroll(() => {
|
||||
const s = $(window).scrollTop();
|
||||
const d = $(document).height();
|
||||
const c = $(window).height();
|
||||
const articleElBottom = articleEl.offset().top + articleEl.height();
|
||||
const scrollPercent = (s / (d - c)) * 100;
|
||||
$('.progress .determinate').css('width', `${scrollPercent}%`);
|
||||
const fixedActionBtn = $('.js-fixed-action-btn');
|
||||
const toggleScrollDataName = 'toggle-auto';
|
||||
if ((s + c) > articleElBottom) {
|
||||
fixedActionBtn.data(toggleScrollDataName, true);
|
||||
fixedActionBtn.floatingActionButton('open');
|
||||
} else if (fixedActionBtn.data(toggleScrollDataName) === true) {
|
||||
fixedActionBtn.data(toggleScrollDataName, false);
|
||||
fixedActionBtn.floatingActionButton('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
// sidenav
|
||||
$('.sidenav').sidenav();
|
||||
$('select').formSelect();
|
||||
$('.collapsible[data-collapsible="accordion"]').collapsible();
|
||||
$('.collapsible[data-collapsible="expandable"]').collapsible({
|
||||
accordion: false,
|
||||
});
|
||||
|
||||
$('.dropdown-trigger').dropdown({ hover: false });
|
||||
$('.dropdown-trigger[data-covertrigger="false"][data-constrainwidth="false"]').dropdown({
|
||||
hover: false,
|
||||
coverTrigger: false,
|
||||
constrainWidth: false,
|
||||
});
|
||||
|
||||
$('.tabs').tabs();
|
||||
$('.tooltipped').tooltip();
|
||||
$('.fixed-action-btn').floatingActionButton();
|
||||
|
||||
initFilters();
|
||||
initExport();
|
||||
initRandom();
|
||||
stickyNav();
|
||||
articleScroll();
|
||||
initPreviewText();
|
||||
|
||||
const toggleNav = (toShow, toFocus) => {
|
||||
$('.nav-panel-actions').hide();
|
||||
$(toShow).show();
|
||||
$(toFocus).focus();
|
||||
};
|
||||
|
||||
$('#nav-btn-add-tag').on('click', () => {
|
||||
$('.nav-panel-add-tag').toggle();
|
||||
$('.nav-panel-menu').addClass('hidden');
|
||||
if (window.innerWidth < mobileMaxWidth) {
|
||||
$('.sidenav').sidenav('close');
|
||||
}
|
||||
$('#tag_label').focus();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#nav-btn-add').on('click', () => {
|
||||
toggleNav('.nav-panel-add', '#entry_url');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#config_fontsize').on('input', () => {
|
||||
const value = $('#config_fontsize').val();
|
||||
const css = `${value}em`;
|
||||
$('#preview-content').css('font-size', css);
|
||||
});
|
||||
|
||||
$('#config_font').on('change', () => {
|
||||
const value = $('#config_font').val();
|
||||
$('#preview-content').css('font-family', value);
|
||||
});
|
||||
|
||||
$('#config_lineHeight').on('input', () => {
|
||||
const value = $('#config_lineHeight').val();
|
||||
const css = `${value}em`;
|
||||
$('#preview-content').css('line-height', css);
|
||||
});
|
||||
|
||||
$('#config_maxWidth').on('input', () => {
|
||||
const value = $('#config_maxWidth').val();
|
||||
const css = `${value}em`;
|
||||
$('#preview-article').css('max-width', css);
|
||||
});
|
||||
|
||||
const materialAddForm = $('.nav-panel-add');
|
||||
materialAddForm.on('submit', () => {
|
||||
materialAddForm.addClass('disabled');
|
||||
$('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur');
|
||||
});
|
||||
|
||||
$('#nav-btn-search').on('click', () => {
|
||||
toggleNav('.nav-panel-search', '#search_entry_term');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.close').on('click', (e) => {
|
||||
$(e.target).parent('.nav-panel-item').hide();
|
||||
$('.nav-panel-actions').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
const mainCheckboxes = document.querySelectorAll('[data-js="checkboxes-toggle"]');
|
||||
if (mainCheckboxes.length) {
|
||||
[...mainCheckboxes].forEach((el) => {
|
||||
el.addEventListener('click', () => {
|
||||
const checkboxes = document.querySelectorAll(el.dataset.toggle);
|
||||
[...checkboxes].forEach((checkbox) => {
|
||||
const checkboxClone = checkbox;
|
||||
checkboxClone.checked = el.checked;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
$('form[name="form_mass_action"] input[name="tags"]').on('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
$('form[name="form_mass_action"] button[name="tag"]').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('img.jr-qrcode').forEach((qrcode) => {
|
||||
const src = jrQrcode.getQrBase64(qrcode.getAttribute('data-url'));
|
||||
|
||||
qrcode.setAttribute('src', src);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.material-toast').forEach((toast) => {
|
||||
M.toast({
|
||||
text: toast.innerText,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import 'highlight.js/styles/atom-one-light.css';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
document.querySelectorAll('pre').forEach((element) => {
|
||||
hljs.highlightElement(element);
|
||||
});
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
import Mousetrap from 'mousetrap';
|
||||
import $ from 'jquery';
|
||||
|
||||
$(document).ready(() => {
|
||||
if ($('#article').length > 0) {
|
||||
/* open original article */
|
||||
Mousetrap.bind('o', () => {
|
||||
$('ul.sidenav a.original i')[0].click();
|
||||
});
|
||||
|
||||
/* mark as favorite */
|
||||
Mousetrap.bind('f', () => {
|
||||
$('ul.sidenav a.favorite i')[0].click();
|
||||
});
|
||||
|
||||
/* mark as read */
|
||||
Mousetrap.bind('a', () => {
|
||||
$('ul.sidenav a.markasread i')[0].click();
|
||||
});
|
||||
|
||||
/* delete */
|
||||
Mousetrap.bind('del', () => {
|
||||
$('ul.sidenav a.delete i')[0].click();
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1,104 +0,0 @@
|
||||
import Mousetrap from 'mousetrap';
|
||||
import $ from 'jquery';
|
||||
|
||||
/* Go to */
|
||||
Mousetrap.bind('g u', () => { window.location.href = Routing.generate('homepage'); });
|
||||
Mousetrap.bind('g s', () => { window.location.href = Routing.generate('starred'); });
|
||||
Mousetrap.bind('g r', () => { window.location.href = Routing.generate('archive'); });
|
||||
Mousetrap.bind('g a', () => { window.location.href = Routing.generate('all'); });
|
||||
Mousetrap.bind('g t', () => { window.location.href = Routing.generate('tag'); });
|
||||
Mousetrap.bind('g c', () => { window.location.href = Routing.generate('config'); });
|
||||
Mousetrap.bind('g i', () => { window.location.href = Routing.generate('import'); });
|
||||
Mousetrap.bind('g d', () => { window.location.href = Routing.generate('developer'); });
|
||||
Mousetrap.bind('?', () => { window.location.href = Routing.generate('howto'); });
|
||||
Mousetrap.bind('g l', () => { window.location.href = Routing.generate('fos_user_security_logout'); });
|
||||
|
||||
function toggleFocus(cardToToogleFocus) {
|
||||
if (cardToToogleFocus) {
|
||||
$(cardToToogleFocus).toggleClass('z-depth-4');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
const cards = $('#content').find('.card');
|
||||
const cardNumber = cards.length;
|
||||
let cardIndex = 0;
|
||||
/* If we come from next page */
|
||||
if (window.location.hash === '#prev') {
|
||||
cardIndex = cardNumber - 1;
|
||||
}
|
||||
let card = cards[cardIndex];
|
||||
const pagination = $('.pagination');
|
||||
|
||||
/* Show nothing on quickstart */
|
||||
if ($('#content > div.quickstart').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Show nothing on login/register page */
|
||||
if ($('#username').length > 0 || $('#fos_user_registration_form_username').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Show nothing on login/register page */
|
||||
if ($('#username').length > 0 || $('#fos_user_registration_form_username').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Focus current card */
|
||||
toggleFocus(card);
|
||||
|
||||
/* Actions */
|
||||
Mousetrap.bind('g n', () => {
|
||||
$('#nav-btn-add').trigger('click');
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('s', () => {
|
||||
$('#nav-btn-search').trigger('click');
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('esc', () => {
|
||||
$('.close').trigger('click');
|
||||
});
|
||||
|
||||
/* Select right card. If there's a next page, go to next page */
|
||||
Mousetrap.bind('right', () => {
|
||||
if (cardIndex >= 0 && cardIndex < cardNumber - 1) {
|
||||
toggleFocus(card);
|
||||
cardIndex += 1;
|
||||
card = cards[cardIndex];
|
||||
toggleFocus(card);
|
||||
return;
|
||||
}
|
||||
if (pagination.length > 0 && pagination.find('li.next:not(.disabled)').length > 0 && cardIndex === cardNumber - 1) {
|
||||
window.location.href = window.location.origin + $(pagination).find('li.next a').attr('href');
|
||||
}
|
||||
});
|
||||
|
||||
/* Select previous card. If there's a previous page, go to next page */
|
||||
Mousetrap.bind('left', () => {
|
||||
if (cardIndex > 0 && cardIndex < cardNumber) {
|
||||
toggleFocus(card);
|
||||
cardIndex -= 1;
|
||||
card = cards[cardIndex];
|
||||
toggleFocus(card);
|
||||
return;
|
||||
}
|
||||
if (pagination.length > 0 && $(pagination).find('li.prev:not(.disabled)').length > 0 && cardIndex === 0) {
|
||||
window.location.href = `${window.location.origin + $(pagination).find('li.prev a').attr('href')}#prev`;
|
||||
}
|
||||
});
|
||||
|
||||
Mousetrap.bind('enter', () => {
|
||||
if (typeof card !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = $(card).find('.card-title a').attr('href');
|
||||
if (typeof url === 'string' && url.length > 0) {
|
||||
window.location.href = window.location.origin + url;
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -1,86 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
function supportsLocalStorage() {
|
||||
try {
|
||||
return 'localStorage' in window && window.localStorage !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function savePercent(id, percent) {
|
||||
if (!supportsLocalStorage()) { return false; }
|
||||
localStorage[`wallabag.article.${id}.percent`] = percent;
|
||||
return true;
|
||||
}
|
||||
|
||||
function retrievePercent(id, resized) {
|
||||
if (!supportsLocalStorage()) { return false; }
|
||||
|
||||
const bheight = $(document).height();
|
||||
const percent = localStorage[`wallabag.article.${id}.percent`];
|
||||
const scroll = bheight * percent;
|
||||
|
||||
if (!resized) {
|
||||
window.scrollTo({
|
||||
top: scroll,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function initFilters() {
|
||||
// no display if filters not available
|
||||
if ($('div').is('#filters')) {
|
||||
$('#button_filters').show();
|
||||
$('#filters.sidenav').sidenav({ edge: 'right' });
|
||||
$('#clear_form_filters').on('click', () => {
|
||||
$('#filters input').val('');
|
||||
$('#filters :checked').removeAttr('checked');
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initExport() {
|
||||
// no display if export not available
|
||||
if ($('div').is('#export')) {
|
||||
$('#button_export').show();
|
||||
$('#export.sidenav').sidenav({ edge: 'right' });
|
||||
}
|
||||
}
|
||||
|
||||
function initRandom() {
|
||||
// no display if export (ie: entries) not available
|
||||
if ($('div').is('#export')) {
|
||||
$('#button_random').show();
|
||||
}
|
||||
}
|
||||
|
||||
function initPreviewText() {
|
||||
// no display if preview_text not available
|
||||
if ($('div').is('#preview-article')) {
|
||||
const defaultFontFamily = $('#config_font').val();
|
||||
const defaultFontSize = $('#config_fontsize').val();
|
||||
const defaultLineHeight = $('#config_lineHeight').val();
|
||||
const defaultMaxWidth = $('#config_maxWidth').val();
|
||||
const previewContent = $('#preview-content');
|
||||
|
||||
previewContent.css('font-family', defaultFontFamily);
|
||||
previewContent.css('font-size', `${defaultFontSize}em`);
|
||||
previewContent.css('line-height', `${defaultLineHeight}em`);
|
||||
$('#preview-article').css('max-width', `${defaultMaxWidth}em`);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
savePercent,
|
||||
retrievePercent,
|
||||
initExport,
|
||||
initFilters,
|
||||
initRandom,
|
||||
initPreviewText,
|
||||
};
|
||||
@ -154,6 +154,15 @@ a.original:not(.waves-effect) {
|
||||
}
|
||||
}
|
||||
|
||||
.card .card-content .card-title,
|
||||
.card-stacked .card-content .card-title {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.card-entry-labels li,
|
||||
.card-tag-labels li {
|
||||
margin: 10px 10px 10px auto;
|
||||
@ -179,6 +188,7 @@ a.original:not(.waves-effect) {
|
||||
.card-entry-tags a,
|
||||
.card-entry-labels a,
|
||||
.card-tag-labels a,
|
||||
.card-tag-labels button,
|
||||
.card-entry-labels-hidden a,
|
||||
#list .chip a {
|
||||
text-decoration: none;
|
||||
@ -186,6 +196,14 @@ a.original:not(.waves-effect) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-tag-labels button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-weight: normal;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-tag-link {
|
||||
width: calc(100% - 24px);
|
||||
line-height: 1.3;
|
||||
@ -196,6 +214,7 @@ a.original:not(.waves-effect) {
|
||||
|
||||
.card-tag-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 100px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@ -63,7 +63,9 @@
|
||||
.input-field input:focus,
|
||||
.results-item,
|
||||
.sidenav li > a,
|
||||
.sidenav li > a > i.material-icons {
|
||||
.sidenav li > a > i.material-icons,
|
||||
.sidenav li button,
|
||||
.sidenav li button > i.material-icons {
|
||||
color: #dfdfdf;
|
||||
}
|
||||
|
||||
@ -88,6 +90,7 @@
|
||||
|
||||
.mass-action-tags .mass-action-tags-input.mass-action-tags-input,
|
||||
.sidenav li:not(.logo) > a:hover,
|
||||
.sidenav li:not(.logo) button:hover,
|
||||
.sidenav .collapsible-header:hover,
|
||||
.sidenav.sidenav-fixed .collapsible-header:hover {
|
||||
background-color: #1d1d1d;
|
||||
|
||||
@ -6,11 +6,32 @@ nav {
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
// adapted from anchor styles from node_modules/@materializecss/materialize/sass/components/_navbar.scss
|
||||
nav ul button {
|
||||
transition: background-color .3s;
|
||||
font-size: 1rem;
|
||||
color: #fff;
|
||||
display: block;
|
||||
padding: 0 15px;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
|
||||
&:focus {
|
||||
background: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0 0 0 / 10%);
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
input {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
ul button:hover,
|
||||
ul a:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
@ -34,6 +55,7 @@ nav {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
button,
|
||||
a {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
@ -143,14 +165,6 @@ nav {
|
||||
margin: 0 1%;
|
||||
}
|
||||
|
||||
.button-filters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-export {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.entry-nav-top--sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
background: initial;
|
||||
}
|
||||
|
||||
& button > i.material-icons.theme-toggle-icon,
|
||||
& > a > i.material-icons.theme-toggle-icon {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
@ -22,6 +23,7 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.sidenav-fixed button,
|
||||
&.sidenav-fixed a {
|
||||
font-size: 13px;
|
||||
line-height: 44px;
|
||||
@ -41,7 +43,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bold > a {
|
||||
// adapted from anchor styles from node_modules/@materializecss/materialize/sass/components/_sidenav.scss
|
||||
.sidenav li button {
|
||||
color: rgba(0 0 0 / 87%);
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
padding: 0 (16px * 2);
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0 0 0 / 5%);
|
||||
}
|
||||
|
||||
& > i,
|
||||
& > i.material-icons {
|
||||
float: left;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
margin: 0 (16px * 2) 0 0;
|
||||
width: 24px;
|
||||
color: rgba(0 0 0 / 54%);
|
||||
}
|
||||
}
|
||||
|
||||
.bold > a,
|
||||
.bold > button {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
@use "variables";
|
||||
|
||||
/* ==========================================================================
|
||||
* Various
|
||||
* ========================================================================== */
|
||||
@ -38,3 +40,18 @@ nav .input-field input {
|
||||
.tab {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
background: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
color: variables.$blue-accent-color;
|
||||
|
||||
&:focus {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ $config
|
||||
'friendsoftwig/twigcs',
|
||||
'incenteev/composer-parameter-handler',
|
||||
'j0k3r/graby-site-config',
|
||||
'j0k3r/php-readability',
|
||||
'laminas/laminas-code',
|
||||
'lcobucci/jwt',
|
||||
'mgargano/simplehtmldom',
|
||||
@ -37,13 +38,11 @@ $config
|
||||
'phpstan/phpstan-symfony',
|
||||
'psr/http-client',
|
||||
'psr/http-factory',
|
||||
'rulerz-php/doctrine-orm',
|
||||
'scheb/2fa-qr-code',
|
||||
'rector/rector',
|
||||
'scheb/2fa-trusted-device',
|
||||
'shipmonk/composer-dependency-analyser',
|
||||
'symfony/asset',
|
||||
'symfony/css-selector',
|
||||
'symfony/doctrine-bridge',
|
||||
'symfony/google-mailer',
|
||||
'symfony/intl',
|
||||
'symfony/phpunit-bridge',
|
||||
@ -64,10 +63,9 @@ $config
|
||||
'symfony/web-profiler-bundle',
|
||||
'symfony/web-server-bundle',
|
||||
], [ErrorType::DEV_DEPENDENCY_IN_PROD])
|
||||
->ignoreErrorsOnPackages([
|
||||
'gedmo/doctrine-extensions',
|
||||
], [ErrorType::SHADOW_DEPENDENCY])
|
||||
;
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
$config->ignoreErrorsOnPackage('symfony/polyfill-php80', [ErrorType::UNUSED_DEPENDENCY]);
|
||||
}
|
||||
|
||||
return $config;
|
||||
|
||||
213
composer.json
213
composer.json
@ -38,7 +38,7 @@
|
||||
"issues": "https://github.com/wallabag/wallabag/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"php": ">=8.2",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
@ -57,135 +57,147 @@
|
||||
"ext-tidy": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-xml": "*",
|
||||
"babdev/pagerfanta-bundle": "^3.8",
|
||||
"babdev/pagerfanta-bundle": "^4.5",
|
||||
"craue/config-bundle": "^2.7.0",
|
||||
"defuse/php-encryption": "^2.4",
|
||||
"doctrine/collections": "^1.8",
|
||||
"doctrine/common": "^3.4.3",
|
||||
"doctrine/dbal": "^3.8.2",
|
||||
"doctrine/doctrine-bundle": "^2.11.3",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.3",
|
||||
"doctrine/collections": "^2.3",
|
||||
"doctrine/common": "^3.5.0",
|
||||
"doctrine/dbal": "^3.9.4",
|
||||
"doctrine/doctrine-bundle": "^2.13.2",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4.1",
|
||||
"doctrine/event-manager": "^1.2",
|
||||
"doctrine/migrations": "^3.5.5",
|
||||
"doctrine/orm": "^2.18.1",
|
||||
"doctrine/persistence": "^3.2",
|
||||
"egulias/email-validator": "^3.2.6",
|
||||
"doctrine/migrations": "^3.8.2",
|
||||
"doctrine/orm": "^2.20.2",
|
||||
"doctrine/persistence": "^3.4",
|
||||
"egulias/email-validator": "^4.0.4",
|
||||
"enshrined/svg-sanitize": "^0.21",
|
||||
"friendsofsymfony/jsrouting-bundle": "^2.8",
|
||||
"friendsofsymfony/jsrouting-bundle": "^3.5",
|
||||
"friendsofsymfony/oauth-server-bundle": "dev-master#dc8ff343363cf794d30eb1a123610d186a43f162",
|
||||
"friendsofsymfony/rest-bundle": "^3.6",
|
||||
"friendsofsymfony/user-bundle": "^3.2.1",
|
||||
"guzzlehttp/psr7": "^2.6.2",
|
||||
"html2text/html2text": "^4.3.1",
|
||||
"friendsofsymfony/rest-bundle": "^3.8",
|
||||
"friendsofsymfony/user-bundle": "^3.4.0",
|
||||
"guzzlehttp/psr7": "^2.7.0",
|
||||
"html2text/html2text": "^4.3.2",
|
||||
"incenteev/composer-parameter-handler": "^2.2",
|
||||
"j0k3r/graby": "^2.4.5",
|
||||
"j0k3r/graby-site-config": "^1.0",
|
||||
"j0k3r/graby": "^2.4.6",
|
||||
"j0k3r/graby-site-config": "^1.0.197",
|
||||
"j0k3r/php-readability": "^1.2.13",
|
||||
"javibravo/simpleue": "^2.1",
|
||||
"jms/serializer": "^3.29.1",
|
||||
"jms/serializer-bundle": "^5.4",
|
||||
"laminas/laminas-code": "^4.7.1",
|
||||
"jms/serializer": "^3.32.3",
|
||||
"jms/serializer-bundle": "^5.5.1",
|
||||
"laminas/laminas-code": "^4.16",
|
||||
"lcobucci/jwt": "^4.3",
|
||||
"league/html-to-markdown": "^5.1",
|
||||
"league/html-to-markdown": "^5.1.1",
|
||||
"mgargano/simplehtmldom": "^1.5",
|
||||
"mnapoli/piwik-twig-extension": "^3.0",
|
||||
"monolog/monolog": "^2.9",
|
||||
"nelmio/api-doc-bundle": "^4.20.0",
|
||||
"nelmio/cors-bundle": "^2.4",
|
||||
"monolog/monolog": "^2.10",
|
||||
"nelmio/api-doc-bundle": "^4.38.1",
|
||||
"nelmio/cors-bundle": "^2.5",
|
||||
"ocramius/proxy-manager": "^2.1.1",
|
||||
"pagerfanta/core": "^3.8",
|
||||
"pagerfanta/doctrine-orm-adapter": "^3.8",
|
||||
"pagerfanta/twig": "^3.8",
|
||||
"php-amqplib/php-amqplib": "^3.6.1",
|
||||
"php-amqplib/rabbitmq-bundle": "^2.14.0",
|
||||
"pagerfanta/doctrine-orm-adapter": "^4.7",
|
||||
"pagerfanta/twig": "^4.7",
|
||||
"php-amqplib/php-amqplib": "^3.7.3",
|
||||
"php-amqplib/rabbitmq-bundle": "^2.17.3",
|
||||
"pragmarx/recovery": "^0.2.1",
|
||||
"predis/predis": "^2.2.2",
|
||||
"predis/predis": "^2.3.0",
|
||||
"psr/http-client": "^1.0.3",
|
||||
"psr/http-factory": "^1.0.2",
|
||||
"psr/http-factory": "^1.1.0",
|
||||
"psr/http-message": "^2.0",
|
||||
"psr/log": "^1.1.4",
|
||||
"rulerz-php/doctrine-orm": "dev-master",
|
||||
"scheb/2fa-backup-code": "^5.13.2",
|
||||
"scheb/2fa-bundle": "^5.13.2",
|
||||
"scheb/2fa-email": "^5.13.2",
|
||||
"scheb/2fa-google-authenticator": "^5.13.2",
|
||||
"scheb/2fa-qr-code": "^5.13.2",
|
||||
"scheb/2fa-trusted-device": "^5.13.2",
|
||||
"scssphp/scssphp": "^1.12.1",
|
||||
"scssphp/scssphp": "^2.0.1",
|
||||
"sensio/framework-extra-bundle": "^6.2.10",
|
||||
"sentry/sentry-symfony": "^5.0.1",
|
||||
"spiriitlabs/form-filter-bundle": "^10.0",
|
||||
"stof/doctrine-extensions-bundle": "^1.11.0",
|
||||
"symfony/asset": "^5.4.35",
|
||||
"symfony/browser-kit": "^5.4.35",
|
||||
"symfony/config": "^5.4.35",
|
||||
"symfony/console": "^5.4.35",
|
||||
"symfony/dependency-injection": "^5.4.35",
|
||||
"symfony/doctrine-bridge": "^5.4.35",
|
||||
"symfony/dom-crawler": "^5.4.35",
|
||||
"symfony/error-handler": "^5.4.35",
|
||||
"symfony/event-dispatcher": "^5.4.35",
|
||||
"symfony/event-dispatcher-contracts": "^2.5.2",
|
||||
"symfony/expression-language": "^5.4.35",
|
||||
"symfony/filesystem": "^5.4",
|
||||
"symfony/finder": "^5.4.35",
|
||||
"symfony/form": "^5.4.35",
|
||||
"symfony/framework-bundle": "^5.4.35",
|
||||
"symfony/google-mailer": "^5.4.35",
|
||||
"symfony/http-client": "^5.4.35",
|
||||
"symfony/http-client-contracts": "^2.5",
|
||||
"symfony/http-foundation": "^5.4.35",
|
||||
"symfony/http-kernel": "^5.4.35",
|
||||
"symfony/intl": "^5.4.35",
|
||||
"symfony/mailer": "^5.4.35",
|
||||
"symfony/mime": "^5.4.35",
|
||||
"sentry/sentry-symfony": "^5.2.0",
|
||||
"spiriitlabs/form-filter-bundle": "^10.0.2",
|
||||
"stof/doctrine-extensions-bundle": "^1.13.0",
|
||||
"symfony/asset": "^5.4.45",
|
||||
"symfony/browser-kit": "^5.4.45",
|
||||
"symfony/config": "^5.4.46",
|
||||
"symfony/console": "^5.4.47",
|
||||
"symfony/dependency-injection": "^5.4.48",
|
||||
"symfony/doctrine-bridge": "^5.4.48",
|
||||
"symfony/dom-crawler": "^5.4.48",
|
||||
"symfony/error-handler": "^5.4.46",
|
||||
"symfony/event-dispatcher": "^5.4.45",
|
||||
"symfony/event-dispatcher-contracts": "^2.5.4",
|
||||
"symfony/expression-language": "^5.4.45",
|
||||
"symfony/filesystem": "^5.4.45",
|
||||
"symfony/finder": "^5.4.45",
|
||||
"symfony/form": "^5.4.45",
|
||||
"symfony/framework-bundle": "^5.4.45",
|
||||
"symfony/google-mailer": "^5.4.45",
|
||||
"symfony/http-client": "^5.4.49",
|
||||
"symfony/http-client-contracts": "^2.5.5",
|
||||
"symfony/http-foundation": "^5.4.48",
|
||||
"symfony/http-kernel": "^5.4.48",
|
||||
"symfony/intl": "^5.4.47",
|
||||
"symfony/mailer": "^5.4.45",
|
||||
"symfony/mime": "^5.4.45",
|
||||
"symfony/monolog-bundle": "^3.10",
|
||||
"symfony/options-resolver": "^5.4.21",
|
||||
"symfony/polyfill-php80": "^1.29",
|
||||
"symfony/proxy-manager-bridge": "^5.4.21",
|
||||
"symfony/routing": "^5.4.35",
|
||||
"symfony/security-bundle": "^5.4.35",
|
||||
"symfony/security-core": "^5.4.35",
|
||||
"symfony/security-http": "^5.4.35",
|
||||
"symfony/templating": "^5.4.35",
|
||||
"symfony/translation-contracts": "^2.5.2",
|
||||
"symfony/twig-bundle": "^5.4.35",
|
||||
"symfony/validator": "^5.4.35",
|
||||
"symfony/webpack-encore-bundle": "^1.17",
|
||||
"tecnickcom/tcpdf": "^6.6.5",
|
||||
"twig/extra-bundle": "^3.8",
|
||||
"twig/string-extra": "^3.8",
|
||||
"twig/twig": "^3.8.0",
|
||||
"symfony/options-resolver": "^5.4.45",
|
||||
"symfony/proxy-manager-bridge": "^5.4.45",
|
||||
"symfony/routing": "^5.4.48",
|
||||
"symfony/security-bundle": "^5.4.45",
|
||||
"symfony/security-core": "^5.4.48",
|
||||
"symfony/security-http": "^5.4.47",
|
||||
"symfony/templating": "^5.4.45",
|
||||
"symfony/translation-contracts": "^2.5.4",
|
||||
"symfony/twig-bundle": "^5.4.45",
|
||||
"symfony/validator": "^5.4.48",
|
||||
"symfony/webpack-encore-bundle": "^1.17.2",
|
||||
"tecnickcom/tcpdf": "^6.8.2",
|
||||
"twig/extra-bundle": "^3.20",
|
||||
"twig/string-extra": "^3.20",
|
||||
"twig/twig": "^3.20.0",
|
||||
"wallabag/phpepub": "^4.0.10",
|
||||
"wallabag/rulerz": "dev-master",
|
||||
"wallabag/rulerz-bundle": "dev-master",
|
||||
"willdurand/hateoas": "^3.10",
|
||||
"willdurand/hateoas-bundle": "^2.6"
|
||||
"willdurand/hateoas": "^3.12",
|
||||
"willdurand/hateoas-bundle": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"dama/doctrine-test-bundle": "^8.0.2",
|
||||
"doctrine/data-fixtures": "^1.7",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.5.1",
|
||||
"ergebnis/composer-normalize": "^2.42.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.49",
|
||||
"friendsoftwig/twigcs": "^6.1",
|
||||
"dama/doctrine-test-bundle": "^8.2.2",
|
||||
"doctrine/data-fixtures": "^2.0.2",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.7.1",
|
||||
"ergebnis/composer-normalize": "^2.45.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.70.2",
|
||||
"friendsoftwig/twigcs": "^6.5",
|
||||
"m6web/redis-mock": "^5.6",
|
||||
"php-http/mock-client": "^1.6",
|
||||
"phpstan/extension-installer": "^1.3.1",
|
||||
"phpstan/phpstan": "^1.10.59",
|
||||
"phpstan/phpstan-doctrine": "^1.3.62",
|
||||
"phpstan/phpstan-phpunit": "^1.3.16",
|
||||
"phpstan/phpstan-symfony": "^1.3.7",
|
||||
"phpunit/phpunit": "^9.6.17",
|
||||
"shipmonk/composer-dependency-analyser": "^1.7",
|
||||
"symfony/css-selector": "^5.4.35",
|
||||
"symfony/debug-bundle": "^5.4.35",
|
||||
"symfony/maker-bundle": "^1.43",
|
||||
"symfony/phpunit-bridge": "^7.0.3",
|
||||
"symfony/process": "^5.4",
|
||||
"symfony/var-dumper": "^5.4.35",
|
||||
"symfony/web-profiler-bundle": "^5.4.35",
|
||||
"php-http/mock-client": "^1.6.1",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan": "^1.12.20",
|
||||
"phpstan/phpstan-doctrine": "^1.5.7",
|
||||
"phpstan/phpstan-phpunit": "^1.4.2",
|
||||
"phpstan/phpstan-symfony": "^1.4.13",
|
||||
"phpunit/phpunit": "^9.6.22",
|
||||
"rector/rector": "^1.2",
|
||||
"shipmonk/composer-dependency-analyser": "^1.8.2",
|
||||
"symfony/css-selector": "^5.4.45",
|
||||
"symfony/debug-bundle": "^5.4.45",
|
||||
"symfony/maker-bundle": "^1.50",
|
||||
"symfony/phpunit-bridge": "^7.2.0",
|
||||
"symfony/process": "^5.4.47",
|
||||
"symfony/var-dumper": "^5.4.48",
|
||||
"symfony/web-profiler-bundle": "^5.4.48",
|
||||
"symfony/web-server-bundle": "^4.4.44"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-php54": "*",
|
||||
"symfony/polyfill-php55": "*",
|
||||
"symfony/polyfill-php56": "*",
|
||||
"symfony/polyfill-php70": "*",
|
||||
"symfony/polyfill-php71": "*",
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php82": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-imagick": "To keep GIF animation when downloading image is enabled"
|
||||
},
|
||||
@ -216,9 +228,6 @@
|
||||
"phpstan/extension-installer": true
|
||||
},
|
||||
"bin-dir": "bin",
|
||||
"platform": {
|
||||
"php": "7.4.29"
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
|
||||
2762
composer.lock
generated
2762
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,13 @@
|
||||
FROM php:8.1-fpm AS rootless
|
||||
FROM golang as envsubst
|
||||
|
||||
ARG ENVSUBST_VERSION=v1.3.0
|
||||
|
||||
# envsubst from gettext can not replace env vars with default values
|
||||
# this package is not available for ARM32 and we have to build it from source code
|
||||
# flag -ldflags "-s -w" produces a smaller executable
|
||||
RUN go install -ldflags "-s -w" -v github.com/a8m/envsubst/cmd/envsubst@${ENVSUBST_VERSION}
|
||||
|
||||
FROM php:8.2-fpm AS rootless
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG NODE_VERSION=20
|
||||
@ -50,14 +59,13 @@ RUN docker-php-ext-install -j "$(nproc)" \
|
||||
tidy \
|
||||
zip
|
||||
|
||||
RUN pecl install redis; \
|
||||
pecl install imagick; \
|
||||
pecl install xdebug-3.1.6; \
|
||||
docker-php-ext-enable \
|
||||
RUN pecl install redis-6.1.0 \
|
||||
&& pecl install imagick-3.7.0 \
|
||||
&& pecl install xdebug-3.4.1 \
|
||||
&& docker-php-ext-enable \
|
||||
redis \
|
||||
imagick \
|
||||
xdebug \
|
||||
;
|
||||
xdebug
|
||||
|
||||
RUN version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;") \
|
||||
&& architecture=$(uname -m) \
|
||||
@ -76,10 +84,8 @@ RUN mkdir -p /tmp/blackfire \
|
||||
|
||||
RUN npm install -g yarn
|
||||
|
||||
RUN curl -L -o /usr/local/bin/envsubst https://github.com/a8m/envsubst/releases/download/v1.1.0/envsubst-`uname -s`-`uname -m`; \
|
||||
chmod +x /usr/local/bin/envsubst
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
||||
COPY --from=envsubst /go/bin/envsubst /usr/local/bin/envsubst
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
COPY config/ /opt/wallabag/config/
|
||||
|
||||
@ -43,7 +43,7 @@ class AnnotationFixtures extends Fixture implements DependentFixtureInterface
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies()
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
EntryFixtures::class,
|
||||
|
||||
@ -55,7 +55,7 @@ class ConfigFixtures extends Fixture implements DependentFixtureInterface
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies()
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
UserFixtures::class,
|
||||
|
||||
@ -155,7 +155,7 @@ class EntryFixtures extends Fixture implements DependentFixtureInterface
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies()
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
UserFixtures::class,
|
||||
|
||||
@ -8,11 +8,9 @@ use Wallabag\Entity\IgnoreOriginInstanceRule;
|
||||
|
||||
class IgnoreOriginInstanceRuleFixtures extends Fixture
|
||||
{
|
||||
private array $defaultIgnoreOriginInstanceRules;
|
||||
|
||||
public function __construct(array $defaultIgnoreOriginInstanceRules)
|
||||
{
|
||||
$this->defaultIgnoreOriginInstanceRules = $defaultIgnoreOriginInstanceRules;
|
||||
public function __construct(
|
||||
private readonly array $defaultIgnoreOriginInstanceRules,
|
||||
) {
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
|
||||
@ -21,7 +21,7 @@ class IgnoreOriginUserRuleFixtures extends Fixture implements DependentFixtureIn
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies()
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
UserFixtures::class,
|
||||
|
||||
@ -8,11 +8,9 @@ use Wallabag\Entity\InternalSetting;
|
||||
|
||||
class InternalSettingFixtures extends Fixture
|
||||
{
|
||||
private array $defaultInternalSettings;
|
||||
|
||||
public function __construct(array $defaultInternalSettings)
|
||||
{
|
||||
$this->defaultInternalSettings = $defaultInternalSettings;
|
||||
public function __construct(
|
||||
private readonly array $defaultInternalSettings,
|
||||
) {
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
|
||||
@ -11,11 +11,9 @@ use Wallabag\Helper\CryptoProxy;
|
||||
|
||||
class SiteCredentialFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
private CryptoProxy $cryptoProxy;
|
||||
|
||||
public function __construct(CryptoProxy $cryptoProxy)
|
||||
{
|
||||
$this->cryptoProxy = $cryptoProxy;
|
||||
public function __construct(
|
||||
private readonly CryptoProxy $cryptoProxy,
|
||||
) {
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
@ -37,7 +35,7 @@ class SiteCredentialFixtures extends Fixture implements DependentFixtureInterfac
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies()
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
UserFixtures::class,
|
||||
|
||||
@ -58,7 +58,7 @@ class TaggingRuleFixtures extends Fixture implements DependentFixtureInterface
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies()
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
ConfigFixtures::class,
|
||||
|
||||
@ -27,7 +27,7 @@ class Version20161001072726 extends WallabagMigration
|
||||
// remove all FK from entry_tag
|
||||
switch (true) {
|
||||
case $platform instanceof MySQLPlatform:
|
||||
$query = $this->connection->query("
|
||||
$query = $this->connection->executeQuery("
|
||||
SELECT CONSTRAINT_NAME
|
||||
FROM information_schema.key_column_usage
|
||||
WHERE TABLE_NAME = '" . $this->getTable('entry_tag', WallabagMigration::UN_ESCAPED_TABLE) . "' AND CONSTRAINT_NAME LIKE 'FK_%'
|
||||
@ -40,7 +40,7 @@ class Version20161001072726 extends WallabagMigration
|
||||
break;
|
||||
case $platform instanceof PostgreSQLPlatform:
|
||||
// http://dba.stackexchange.com/questions/36979/retrieving-all-pk-and-fk
|
||||
$query = $this->connection->query("
|
||||
$query = $this->connection->executeQuery("
|
||||
SELECT conrelid::regclass AS table_from
|
||||
,conname
|
||||
,pg_get_constraintdef(c.oid)
|
||||
@ -64,7 +64,7 @@ class Version20161001072726 extends WallabagMigration
|
||||
|
||||
switch (true) {
|
||||
case $platform instanceof MySQLPlatform:
|
||||
$query = $this->connection->query("
|
||||
$query = $this->connection->executeQuery("
|
||||
SELECT CONSTRAINT_NAME
|
||||
FROM information_schema.key_column_usage
|
||||
WHERE TABLE_NAME = '" . $this->getTable('annotation', WallabagMigration::UN_ESCAPED_TABLE) . "'
|
||||
@ -79,7 +79,7 @@ class Version20161001072726 extends WallabagMigration
|
||||
break;
|
||||
case $platform instanceof PostgreSQLPlatform:
|
||||
// http://dba.stackexchange.com/questions/36979/retrieving-all-pk-and-fk
|
||||
$query = $this->connection->query("
|
||||
$query = $this->connection->executeQuery("
|
||||
SELECT conrelid::regclass AS table_from
|
||||
,conname
|
||||
,pg_get_constraintdef(c.oid)
|
||||
|
||||
@ -20,7 +20,7 @@ class Version20170719231144 extends WallabagMigration
|
||||
}
|
||||
|
||||
// Find tags which need to be merged
|
||||
$dupTags = $this->connection->query('
|
||||
$dupTags = $this->connection->executeQuery('
|
||||
SELECT LOWER(label) AS lower_label
|
||||
FROM ' . $this->getTable('tag') . '
|
||||
GROUP BY LOWER(label)
|
||||
@ -31,7 +31,7 @@ class Version20170719231144 extends WallabagMigration
|
||||
$label = $duplicates['lower_label'];
|
||||
|
||||
// Retrieve all duplicate tags for a given tag
|
||||
$tags = $this->connection->query('
|
||||
$tags = $this->connection->executeQuery('
|
||||
SELECT id
|
||||
FROM ' . $this->getTable('tag') . '
|
||||
WHERE LOWER(label) = :label
|
||||
|
||||
@ -50,7 +50,7 @@ final class Version20190826204730 extends WallabagMigration
|
||||
->fetchOne('SELECT * FROM ' . $this->getTable('ignore_origin_instance_rule') . " WHERE rule = '" . $entity['rule'] . "'");
|
||||
|
||||
if (false === $previous_rule) {
|
||||
$this->addSql('INSERT INTO ' . $this->getTable('ignore_origin_instance_rule') . " (rule) VALUES ('" . $entity['rule'] . "');");
|
||||
$this->connection->executeQuery('INSERT INTO ' . $this->getTable('ignore_origin_instance_rule') . " (rule) VALUES ('" . $entity['rule'] . "');");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
package.json
34
package.json
@ -41,26 +41,26 @@
|
||||
"url": "https://github.com/wallabag/wallabag/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.9",
|
||||
"@babel/eslint-parser": "^7.26.8",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/core": "^7.27.7",
|
||||
"@babel/eslint-parser": "^7.27.5",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@symfony/webpack-encore": "^5.1.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^10.0.0",
|
||||
"core-js": "^3.41.0",
|
||||
"core-js": "^3.43.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-webpack-plugin": "^4.2.0",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"lato-font": "^3.0.0",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"postcss": "^8.5.3",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"sass-embedded": "^1.85.1",
|
||||
"sass-embedded": "^1.89.2",
|
||||
"sass-loader": "^16.0.5",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^15.11.0",
|
||||
@ -69,25 +69,25 @@
|
||||
"stylelint-scss": "^5.3.2",
|
||||
"terser-webpack-plugin": "^5.3.14",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.98.0",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-manifest-plugin": "^5.0.0",
|
||||
"webpack-manifest-plugin": "^5.0.1",
|
||||
"webpack-merge": "^6.0.1",
|
||||
"webpack-notifier": "^1.15.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/atkinson-hyperlegible": "^5.2.5",
|
||||
"@fontsource/eb-garamond": "^5.2.5",
|
||||
"@fontsource/montserrat": "^5.2.5",
|
||||
"@fontsource/oswald": "^5.2.5",
|
||||
"@fontsource/atkinson-hyperlegible": "^5.2.6",
|
||||
"@fontsource/eb-garamond": "^5.2.6",
|
||||
"@fontsource/montserrat": "^5.2.6",
|
||||
"@fontsource/oswald": "^5.2.6",
|
||||
"@hotwired/stimulus": "^3.2.2",
|
||||
"@materializecss/materialize": "^1.2.2",
|
||||
"@symfony/stimulus-bridge": "^4.0.1",
|
||||
"annotator": "wallabag/annotator#master",
|
||||
"clipboard": "^2.0.11",
|
||||
"hammerjs": "^2.0.8",
|
||||
"highlight.js": "^11.11.1",
|
||||
"icomoon-free-npm": "^0.0.0",
|
||||
"jquery": "^3.7.1",
|
||||
"jquery.cookie": "^1.4.1",
|
||||
"jr-qrcode": "^1.2.1",
|
||||
"material-design-icons-iconfont": "^6.7.0",
|
||||
"mathjax": "^3.2.2",
|
||||
@ -100,7 +100,7 @@
|
||||
"build:dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build:prod": "encore production --progress",
|
||||
"lint:js": "eslint assets/*.js assets/js/*.js assets/js/**/*.js",
|
||||
"lint:js": "eslint assets/*.js assets/controllers/*.js",
|
||||
"lint:scss": "stylelint assets/scss/*.scss assets/scss/**/*.scss"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,76 +1,6 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Method Wallabag\\\\Controller\\\\AnnotationController\\:\\:postAnnotationAction\\(\\) should return Symfony\\\\Component\\\\HttpFoundation\\\\JsonResponse but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\<mixed\\>\\.$#"
|
||||
message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/AnnotationController.php
|
||||
|
||||
-
|
||||
message: "#^Method Wallabag\\\\Controller\\\\AnnotationController\\:\\:putAnnotationAction\\(\\) should return Symfony\\\\Component\\\\HttpFoundation\\\\JsonResponse but returns Symfony\\\\Component\\\\Form\\\\FormInterface\\<null\\>\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/AnnotationController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Wallabag\\\\Entity\\\\RuleInterface\\:\\:getConfig\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/ConfigController.php
|
||||
|
||||
-
|
||||
message: "#^Method FOS\\\\UserBundle\\\\Model\\\\UserManagerInterface\\:\\:updateUser\\(\\) invoked with 2 parameters, 1 required\\.$#"
|
||||
count: 6
|
||||
path: src/Controller/ConfigController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Wallabag\\\\Import\\\\ImportInterface\\:\\:setFilepath\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Import/BrowserController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Wallabag\\\\Import\\\\ImportInterface\\:\\:setUser\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Import/BrowserController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Wallabag\\\\Import\\\\ImportInterface\\:\\:setFilepath\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Import/HtmlController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Wallabag\\\\Import\\\\ImportInterface\\:\\:setUser\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Import/HtmlController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Wallabag\\\\Import\\\\ImportInterface\\:\\:setFilepath\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Import/WallabagController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Wallabag\\\\Import\\\\ImportInterface\\:\\:setUser\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Import/WallabagController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Spiriit\\\\Bundle\\\\FormFilterBundle\\\\Filter\\\\Query\\\\QueryInterface\\:\\:getExpressionBuilder\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/Event/Subscriber/CustomDoctrineORMSubscriber.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Spiriit\\\\Bundle\\\\FormFilterBundle\\\\Filter\\\\Query\\\\QueryInterface\\:\\:getExpr\\(\\)\\.$#"
|
||||
count: 10
|
||||
path: src/Form/Type/EntryFilterType.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Scheb\\\\TwoFactorBundle\\\\Model\\\\Email\\\\TwoFactorInterface\\:\\:getName\\(\\)\\.$#"
|
||||
count: 2
|
||||
path: src/Mailer/AuthCodeMailer.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc type Symfony\\\\Component\\\\Mailer\\\\MailerInterface of property Wallabag\\\\Mailer\\\\UserMailer\\:\\:\\$mailer is not covariant with PHPDoc type Swift_Mailer of overridden property FOS\\\\UserBundle\\\\Mailer\\\\TwigSwiftMailer\\:\\:\\$mailer\\.$#"
|
||||
count: 1
|
||||
path: src/Mailer/UserMailer.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: tests/Controller/FeedControllerTest.php
|
||||
path: src/ParamConverter/UsernameFeedTokenConverter.php
|
||||
|
||||
@ -2,7 +2,7 @@ includes:
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
level: 3
|
||||
level: 5
|
||||
paths:
|
||||
- src
|
||||
- tests
|
||||
|
||||
28
rector.php
Normal file
28
rector.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
|
||||
|
||||
return RectorConfig::configure()
|
||||
->withPaths([
|
||||
__DIR__ . '/app',
|
||||
__DIR__ . '/fixtures',
|
||||
__DIR__ . '/src',
|
||||
__DIR__ . '/tests',
|
||||
__DIR__ . '/web',
|
||||
])
|
||||
->withRootFiles()
|
||||
->withImportNames(importShortClasses: false)
|
||||
->withAttributesSets(symfony: true, doctrine: true, gedmo: true, jms: true, sensiolabs: true)
|
||||
->withConfiguredRule(ClassPropertyAssignToConstructorPromotionRector::class, [
|
||||
'inline_public' => true,
|
||||
])
|
||||
->withSkip([
|
||||
ClassPropertyAssignToConstructorPromotionRector::class => [
|
||||
__DIR__ . '/src/Entity/*',
|
||||
],
|
||||
])
|
||||
->withPhpSets()
|
||||
->withTypeCoverageLevel(0);
|
||||
@ -16,14 +16,10 @@ class CleanDownloadedImagesCommand extends Command
|
||||
protected static $defaultName = 'wallabag:clean-downloaded-images';
|
||||
protected static $defaultDescription = 'Cleans downloaded images which are no more associated to an entry';
|
||||
|
||||
private EntryRepository $entryRepository;
|
||||
private DownloadImages $downloadImages;
|
||||
|
||||
public function __construct(EntryRepository $entryRepository, DownloadImages $downloadImages)
|
||||
{
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->downloadImages = $downloadImages;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntryRepository $entryRepository,
|
||||
private readonly DownloadImages $downloadImages,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,9 @@ use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Wallabag\Entity\Entry;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Wallabag\Entity\User;
|
||||
use Wallabag\Event\EntryDeletedEvent;
|
||||
use Wallabag\Repository\EntryRepository;
|
||||
use Wallabag\Repository\UserRepository;
|
||||
|
||||
@ -21,16 +22,13 @@ class CleanDuplicatesCommand extends Command
|
||||
|
||||
protected SymfonyStyle $io;
|
||||
protected int $duplicates = 0;
|
||||
private EntityManagerInterface $entityManager;
|
||||
private EntryRepository $entryRepository;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, EntryRepository $entryRepository, UserRepository $userRepository)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly EntryRepository $entryRepository,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly EventDispatcherInterface $eventDispatcher,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -55,7 +53,7 @@ class CleanDuplicatesCommand extends Command
|
||||
try {
|
||||
$user = $this->getUser($username);
|
||||
$this->cleanDuplicates($user);
|
||||
} catch (NoResultException $e) {
|
||||
} catch (NoResultException) {
|
||||
$this->io->error(\sprintf('User "%s" not found.', $username));
|
||||
|
||||
return 1;
|
||||
@ -90,7 +88,12 @@ class CleanDuplicatesCommand extends Command
|
||||
if (\in_array($url, $urls, true)) {
|
||||
++$duplicatesCount;
|
||||
|
||||
$this->entityManager->remove($this->entryRepository->find($entry['id']));
|
||||
$entryToDelete = $this->entryRepository->find($entry['id']);
|
||||
|
||||
// entry deleted, dispatch event about it!
|
||||
$this->eventDispatcher->dispatch(new EntryDeletedEvent($entryToDelete), EntryDeletedEvent::NAME);
|
||||
|
||||
$this->entityManager->remove($entryToDelete);
|
||||
$this->entityManager->flush(); // Flushing at the end of the loop would require the instance not being online
|
||||
} else {
|
||||
$urls[] = $entry['url'];
|
||||
@ -104,8 +107,8 @@ class CleanDuplicatesCommand extends Command
|
||||
|
||||
private function similarUrl($url)
|
||||
{
|
||||
if (\in_array(substr($url, -1), ['/', '#'], true)) { // get rid of "/" and "#" and the end of urls
|
||||
return substr($url, 0, \strlen($url));
|
||||
if (\in_array(substr((string) $url, -1), ['/', '#'], true)) { // get rid of "/" and "#" and the end of urls
|
||||
return substr((string) $url, 0, \strlen((string) $url));
|
||||
}
|
||||
|
||||
return $url;
|
||||
|
||||
@ -17,18 +17,12 @@ class ExportCommand extends Command
|
||||
protected static $defaultName = 'wallabag:export';
|
||||
protected static $defaultDescription = 'Export all entries for an user';
|
||||
|
||||
private EntryRepository $entryRepository;
|
||||
private UserRepository $userRepository;
|
||||
private EntriesExport $entriesExport;
|
||||
private string $projectDir;
|
||||
|
||||
public function __construct(EntryRepository $entryRepository, UserRepository $userRepository, EntriesExport $entriesExport, string $projectDir)
|
||||
{
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->entriesExport = $entriesExport;
|
||||
$this->projectDir = $projectDir;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntryRepository $entryRepository,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly EntriesExport $entriesExport,
|
||||
private readonly string $projectDir,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
@ -19,16 +19,12 @@ class GenerateUrlHashesCommand extends Command
|
||||
protected static $defaultDescription = 'Generates hashed urls for each entry';
|
||||
|
||||
protected OutputInterface $output;
|
||||
private EntityManagerInterface $entityManager;
|
||||
private EntryRepository $entryRepository;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, EntryRepository $entryRepository, UserRepository $userRepository)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly EntryRepository $entryRepository,
|
||||
private readonly UserRepository $userRepository,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -49,7 +45,7 @@ class GenerateUrlHashesCommand extends Command
|
||||
try {
|
||||
$user = $this->getUser($username);
|
||||
$this->generateHashedUrls($user);
|
||||
} catch (NoResultException $e) {
|
||||
} catch (NoResultException) {
|
||||
$output->writeln(\sprintf('<error>User "%s" not found.</error>', $username));
|
||||
|
||||
return 1;
|
||||
|
||||
@ -21,6 +21,7 @@ use Wallabag\Import\FirefoxImport;
|
||||
use Wallabag\Import\InstapaperImport;
|
||||
use Wallabag\Import\OmnivoreImport;
|
||||
use Wallabag\Import\PinboardImport;
|
||||
use Wallabag\Import\PocketCsvImport;
|
||||
use Wallabag\Import\PocketHtmlImport;
|
||||
use Wallabag\Import\ReadabilityImport;
|
||||
use Wallabag\Import\ShaarliImport;
|
||||
@ -33,55 +34,24 @@ class ImportCommand extends Command
|
||||
protected static $defaultName = 'wallabag:import';
|
||||
protected static $defaultDescription = 'Import entries from a JSON export';
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private TokenStorageInterface $tokenStorage;
|
||||
private UserRepository $userRepository;
|
||||
private WallabagV2Import $wallabagV2Import;
|
||||
private FirefoxImport $firefoxImport;
|
||||
private ChromeImport $chromeImport;
|
||||
private ReadabilityImport $readabilityImport;
|
||||
private InstapaperImport $instapaperImport;
|
||||
private PinboardImport $pinboardImport;
|
||||
private DeliciousImport $deliciousImport;
|
||||
private OmnivoreImport $omnivoreImport;
|
||||
private WallabagV1Import $wallabagV1Import;
|
||||
private ElcuratorImport $elcuratorImport;
|
||||
private ShaarliImport $shaarliImport;
|
||||
private PocketHtmlImport $pocketHtmlImport;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
UserRepository $userRepository,
|
||||
WallabagV2Import $wallabagV2Import,
|
||||
FirefoxImport $firefoxImport,
|
||||
ChromeImport $chromeImport,
|
||||
ReadabilityImport $readabilityImport,
|
||||
InstapaperImport $instapaperImport,
|
||||
PinboardImport $pinboardImport,
|
||||
DeliciousImport $deliciousImport,
|
||||
WallabagV1Import $wallabagV1Import,
|
||||
ElcuratorImport $elcuratorImport,
|
||||
ShaarliImport $shaarliImport,
|
||||
PocketHtmlImport $pocketHtmlImport,
|
||||
OmnivoreImport $omnivoreImport
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly TokenStorageInterface $tokenStorage,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly WallabagV2Import $wallabagV2Import,
|
||||
private readonly FirefoxImport $firefoxImport,
|
||||
private readonly ChromeImport $chromeImport,
|
||||
private readonly ReadabilityImport $readabilityImport,
|
||||
private readonly InstapaperImport $instapaperImport,
|
||||
private readonly PinboardImport $pinboardImport,
|
||||
private readonly DeliciousImport $deliciousImport,
|
||||
private readonly WallabagV1Import $wallabagV1Import,
|
||||
private readonly ElcuratorImport $elcuratorImport,
|
||||
private readonly ShaarliImport $shaarliImport,
|
||||
private readonly PocketHtmlImport $pocketHtmlImport,
|
||||
private readonly PocketCsvImport $pocketCsvImport,
|
||||
private readonly OmnivoreImport $omnivoreImport,
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->wallabagV2Import = $wallabagV2Import;
|
||||
$this->firefoxImport = $firefoxImport;
|
||||
$this->chromeImport = $chromeImport;
|
||||
$this->readabilityImport = $readabilityImport;
|
||||
$this->instapaperImport = $instapaperImport;
|
||||
$this->pinboardImport = $pinboardImport;
|
||||
$this->deliciousImport = $deliciousImport;
|
||||
$this->omnivoreImport = $omnivoreImport;
|
||||
$this->wallabagV1Import = $wallabagV1Import;
|
||||
$this->elcuratorImport = $elcuratorImport;
|
||||
$this->shaarliImport = $shaarliImport;
|
||||
$this->pocketHtmlImport = $pocketHtmlImport;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -90,7 +60,7 @@ class ImportCommand extends Command
|
||||
$this
|
||||
->addArgument('username', InputArgument::REQUIRED, 'User to populate')
|
||||
->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
|
||||
->addOption('importer', null, InputOption::VALUE_OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, delicious, readability, firefox, chrome, elcurator, shaarli or pocket', 'v1')
|
||||
->addOption('importer', null, InputOption::VALUE_OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, delicious, readability, firefox, chrome, elcurator, shaarli, pocket or pocket_csv', 'v1')
|
||||
->addOption('markAsRead', null, InputOption::VALUE_OPTIONAL, 'Mark all entries as read', false)
|
||||
->addOption('useUserId', null, InputOption::VALUE_NONE, 'Use user id instead of username to find account')
|
||||
->addOption('disableContentUpdate', null, InputOption::VALUE_NONE, 'Disable fetching updated content from URL')
|
||||
@ -107,9 +77,7 @@ class ImportCommand extends Command
|
||||
|
||||
// Turning off doctrine default logs queries for saving memory
|
||||
$middlewares = $this->entityManager->getConnection()->getConfiguration()->getMiddlewares();
|
||||
$middlewaresWithoutLogging = array_filter($middlewares, function (Middleware $middleware) {
|
||||
return !$middleware instanceof LoggingMiddleware;
|
||||
});
|
||||
$middlewaresWithoutLogging = array_filter($middlewares, fn (Middleware $middleware) => !$middleware instanceof LoggingMiddleware);
|
||||
$this->entityManager->getConnection()->getConfiguration()->setMiddlewares($middlewaresWithoutLogging);
|
||||
|
||||
if ($input->getOption('useUserId')) {
|
||||
@ -130,44 +98,23 @@ class ImportCommand extends Command
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
\assert($user instanceof User);
|
||||
|
||||
switch ($input->getOption('importer')) {
|
||||
case 'v2':
|
||||
$import = $this->wallabagV2Import;
|
||||
break;
|
||||
case 'firefox':
|
||||
$import = $this->firefoxImport;
|
||||
break;
|
||||
case 'chrome':
|
||||
$import = $this->chromeImport;
|
||||
break;
|
||||
case 'readability':
|
||||
$import = $this->readabilityImport;
|
||||
break;
|
||||
case 'instapaper':
|
||||
$import = $this->instapaperImport;
|
||||
break;
|
||||
case 'pinboard':
|
||||
$import = $this->pinboardImport;
|
||||
break;
|
||||
case 'delicious':
|
||||
$import = $this->deliciousImport;
|
||||
break;
|
||||
case 'elcurator':
|
||||
$import = $this->elcuratorImport;
|
||||
break;
|
||||
case 'shaarli':
|
||||
$import = $this->shaarliImport;
|
||||
break;
|
||||
case 'pocket':
|
||||
$import = $this->pocketHtmlImport;
|
||||
break;
|
||||
case 'omnivore':
|
||||
$import = $this->omnivoreImport;
|
||||
break;
|
||||
default:
|
||||
$import = $this->wallabagV1Import;
|
||||
}
|
||||
$import = match ($input->getOption('importer')) {
|
||||
'v2' => $this->wallabagV2Import,
|
||||
'firefox' => $this->firefoxImport,
|
||||
'chrome' => $this->chromeImport,
|
||||
'readability' => $this->readabilityImport,
|
||||
'instapaper' => $this->instapaperImport,
|
||||
'pinboard' => $this->pinboardImport,
|
||||
'delicious' => $this->deliciousImport,
|
||||
'elcurator' => $this->elcuratorImport,
|
||||
'shaarli' => $this->shaarliImport,
|
||||
'pocket' => $this->pocketHtmlImport,
|
||||
'pocket_csv' => $this->pocketCsvImport,
|
||||
'omnivore' => $this->omnivoreImport,
|
||||
default => $this->wallabagV1Import,
|
||||
};
|
||||
|
||||
$import->setMarkAsRead($input->getOption('markAsRead'));
|
||||
$import->setDisableContentUpdate($input->getOption('disableContentUpdate'));
|
||||
|
||||
@ -16,12 +16,9 @@ class RedisWorkerCommand extends Command
|
||||
protected static $defaultName = 'wallabag:import:redis-worker';
|
||||
protected static $defaultDescription = 'Launch Redis worker';
|
||||
|
||||
private $container;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
public function __construct(
|
||||
private readonly ContainerInterface $container,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
118
src/Command/Import/UrlCommand.php
Normal file
118
src/Command/Import/UrlCommand.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\Command\Import;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
use Doctrine\DBAL\Logging\Middleware as LoggingMiddleware;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Config\Definition\Exception\Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Wallabag\Entity\Entry;
|
||||
use Wallabag\Entity\User;
|
||||
use Wallabag\Helper\ContentProxy;
|
||||
use Wallabag\Helper\TagsAssigner;
|
||||
use Wallabag\Repository\UserRepository;
|
||||
|
||||
class UrlCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly TokenStorageInterface $tokenStorage,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly ContentProxy $contentProxy,
|
||||
private readonly TagsAssigner $tagsAssigner,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('wallabag:import:url')
|
||||
->setDescription('Import a single URL')
|
||||
->addArgument('username', InputArgument::REQUIRED, 'User to add the URL to (value: username or id)')
|
||||
->addArgument('url', InputArgument::REQUIRED, 'URL to import')
|
||||
->addArgument('tags', InputArgument::OPTIONAL, 'Comma-separated list of tags to add')
|
||||
->addOption('markAsRead', null, InputOption::VALUE_OPTIONAL, 'Mark entry as read', false)
|
||||
->addOption('useUserId', null, InputOption::VALUE_NONE, 'Use user id instead of username to find account')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// Turning off doctrine default logs queries for saving memory
|
||||
$middlewares = $this->entityManager->getConnection()->getConfiguration()->getMiddlewares();
|
||||
$middlewaresWithoutLogging = array_filter($middlewares, fn (Middleware $middleware) => !$middleware instanceof LoggingMiddleware);
|
||||
$this->entityManager->getConnection()->getConfiguration()->setMiddlewares($middlewaresWithoutLogging);
|
||||
|
||||
if ($input->getOption('useUserId')) {
|
||||
$entityUser = $this->userRepository->findOneById($input->getArgument('username'));
|
||||
} else {
|
||||
$entityUser = $this->userRepository->findOneByUsername($input->getArgument('username'));
|
||||
}
|
||||
|
||||
if (!\is_object($entityUser)) {
|
||||
throw new Exception(\sprintf('User "%s" not found', $input->getArgument('username')));
|
||||
}
|
||||
|
||||
// Authenticate user for paywalled websites
|
||||
$token = new UsernamePasswordToken(
|
||||
$entityUser,
|
||||
'main',
|
||||
$entityUser->getRoles()
|
||||
);
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
\assert($user instanceof User);
|
||||
|
||||
$url = $input->getArgument('url');
|
||||
|
||||
$existingEntry = $this->entityManager
|
||||
->getRepository(Entry::class)
|
||||
->findByUrlAndUserId($url, $user->getId());
|
||||
|
||||
if (false !== $existingEntry) {
|
||||
$output->writeln(\sprintf('The URL %s is already in user’s entries.', $url));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$entry = new Entry($user);
|
||||
|
||||
try {
|
||||
$this->contentProxy->updateEntry($entry, $url);
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln(\sprintf('Error trying to import the URL %s: %s.', $url, $e->getMessage()));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($input->getOption('markAsRead')) {
|
||||
$entry->updateArchived(true);
|
||||
}
|
||||
|
||||
$this->entityManager->persist($entry);
|
||||
|
||||
$tags = explode(',', $input->getArgument('tags'));
|
||||
if (\count($tags) > 1) {
|
||||
$this->tagsAssigner->assignTagsToEntry(
|
||||
$entry,
|
||||
$tags,
|
||||
$this->entityManager->getUnitOfWork()->getScheduledEntityInsertions()
|
||||
);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$output->writeln(\sprintf('URL %s successfully imported.', $url));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -37,24 +37,15 @@ class InstallCommand extends Command
|
||||
'curl_multi_init',
|
||||
];
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
private UserManagerInterface $userManager;
|
||||
private TableMetadataStorageConfiguration $tableMetadataStorageConfiguration;
|
||||
private string $databaseDriver;
|
||||
private array $defaultSettings;
|
||||
private array $defaultIgnoreOriginInstanceRules;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, EventDispatcherInterface $dispatcher, UserManagerInterface $userManager, TableMetadataStorageConfiguration $tableMetadataStorageConfiguration, string $databaseDriver, array $defaultSettings, array $defaultIgnoreOriginInstanceRules)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->userManager = $userManager;
|
||||
$this->tableMetadataStorageConfiguration = $tableMetadataStorageConfiguration;
|
||||
$this->databaseDriver = $databaseDriver;
|
||||
$this->defaultSettings = $defaultSettings;
|
||||
$this->defaultIgnoreOriginInstanceRules = $defaultIgnoreOriginInstanceRules;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly EventDispatcherInterface $dispatcher,
|
||||
private readonly UserManagerInterface $userManager,
|
||||
private readonly TableMetadataStorageConfiguration $tableMetadataStorageConfiguration,
|
||||
private readonly string $databaseDriver,
|
||||
private readonly array $defaultSettings,
|
||||
private readonly array $defaultIgnoreOriginInstanceRules,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -138,7 +129,7 @@ class InstallCommand extends Command
|
||||
|
||||
// now check if MySQL isn't too old to handle utf8mb4
|
||||
if ($conn->isConnected() && $conn->getDatabasePlatform() instanceof MySQLPlatform) {
|
||||
$version = $conn->query('select version()')->fetchOne();
|
||||
$version = $conn->executeQuery('select version()')->fetchOne();
|
||||
$minimalVersion = '5.5.4';
|
||||
|
||||
if (false === version_compare($version, $minimalVersion, '>')) {
|
||||
@ -151,9 +142,9 @@ class InstallCommand extends Command
|
||||
// testing if PostgreSQL > 9.1
|
||||
if ($conn->isConnected() && $conn->getDatabasePlatform() instanceof PostgreSQLPlatform) {
|
||||
// return version should be like "PostgreSQL 9.5.4 on x86_64-apple-darwin15.6.0, compiled by Apple LLVM version 8.0.0 (clang-800.0.38), 64-bit"
|
||||
$version = $conn->query('SELECT version();')->fetchOne();
|
||||
$version = $conn->executeQuery('SELECT version();')->fetchOne();
|
||||
|
||||
preg_match('/PostgreSQL ([0-9\.]+)/i', $version, $matches);
|
||||
preg_match('/PostgreSQL ([0-9\.]+)/i', (string) $version, $matches);
|
||||
|
||||
if (isset($matches[1]) & version_compare($matches[1], '9.2.0', '<')) {
|
||||
$fulfilled = false;
|
||||
@ -411,7 +402,7 @@ class InstallCommand extends Command
|
||||
|
||||
try {
|
||||
return \in_array($databaseName, $schemaManager->listDatabases(), true);
|
||||
} catch (DriverException $e) {
|
||||
} catch (DriverException) {
|
||||
// it means we weren't able to get database list, assume the database doesn't exist
|
||||
|
||||
return false;
|
||||
|
||||
@ -15,12 +15,9 @@ class ListUserCommand extends Command
|
||||
protected static $defaultName = 'wallabag:user:list';
|
||||
protected static $defaultDescription = 'List all users';
|
||||
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(UserRepository $userRepository)
|
||||
{
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
public function __construct(
|
||||
private readonly UserRepository $userRepository,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
@ -21,20 +21,13 @@ class ReloadEntryCommand extends Command
|
||||
protected static $defaultName = 'wallabag:entry:reload';
|
||||
protected static $defaultDescription = 'Reload entries';
|
||||
|
||||
private EntryRepository $entryRepository;
|
||||
private UserRepository $userRepository;
|
||||
private EntityManagerInterface $entityManager;
|
||||
private ContentProxy $contentProxy;
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct(EntryRepository $entryRepository, UserRepository $userRepository, EntityManagerInterface $entityManager, ContentProxy $contentProxy, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->contentProxy = $contentProxy;
|
||||
$this->dispatcher = $dispatcher;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntryRepository $entryRepository,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly ContentProxy $contentProxy,
|
||||
private readonly EventDispatcherInterface $dispatcher,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -62,7 +55,7 @@ class ReloadEntryCommand extends Command
|
||||
$userId = $this->userRepository
|
||||
->findOneByUserName($username)
|
||||
->getId();
|
||||
} catch (NoResultException $e) {
|
||||
} catch (NoResultException) {
|
||||
$io->error(\sprintf('User "%s" not found.', $username));
|
||||
|
||||
return 1;
|
||||
|
||||
@ -17,12 +17,10 @@ class ShowUserCommand extends Command
|
||||
protected static $defaultDescription = 'Show user details';
|
||||
|
||||
protected SymfonyStyle $io;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(UserRepository $userRepository)
|
||||
{
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
public function __construct(
|
||||
private readonly UserRepository $userRepository,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -46,7 +44,7 @@ class ShowUserCommand extends Command
|
||||
try {
|
||||
$user = $this->getUser($username);
|
||||
$this->showUser($user);
|
||||
} catch (NoResultException $e) {
|
||||
} catch (NoResultException) {
|
||||
$this->io->error(\sprintf('User "%s" not found.', $username));
|
||||
|
||||
return 1;
|
||||
|
||||
@ -18,16 +18,11 @@ class TagAllCommand extends Command
|
||||
protected static $defaultName = 'wallabag:tag:all';
|
||||
protected static $defaultDescription = 'Tag all entries using the tagging rules.';
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private RuleBasedTagger $ruleBasedTagger;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, RuleBasedTagger $ruleBasedTagger, UserRepository $userRepository)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->ruleBasedTagger = $ruleBasedTagger;
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly RuleBasedTagger $ruleBasedTagger,
|
||||
private readonly UserRepository $userRepository,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -48,7 +43,7 @@ class TagAllCommand extends Command
|
||||
|
||||
try {
|
||||
$user = $this->getUser($input->getArgument('username'));
|
||||
} catch (NoResultException $e) {
|
||||
} catch (NoResultException) {
|
||||
$io->error(\sprintf('User "%s" not found.', $input->getArgument('username')));
|
||||
|
||||
return 1;
|
||||
|
||||
@ -15,15 +15,11 @@ class UpdatePicturesPathCommand extends Command
|
||||
protected static $defaultName = 'wallabag:update-pictures-path';
|
||||
protected static $defaultDescription = 'Update the path of the pictures for each entry when you changed your wallabag instance URL.';
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private EntryRepository $entryRepository;
|
||||
private string $wallabagUrl;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, EntryRepository $entryRepository, $wallabagUrl)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->wallabagUrl = $wallabagUrl;
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly EntryRepository $entryRepository,
|
||||
private readonly string $wallabagUrl,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@ use PhpAmqpLib\Message\AMQPMessage;
|
||||
|
||||
class AMQPEntryConsumer extends AbstractConsumer implements ConsumerInterface
|
||||
{
|
||||
public function execute(AMQPMessage $msg)
|
||||
public function execute(AMQPMessage $msg): int|bool
|
||||
{
|
||||
return $this->handleMessage($msg->body);
|
||||
return $this->handleMessage($msg->getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,25 +7,19 @@ use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Wallabag\Entity\Entry;
|
||||
use Wallabag\Entity\Tag;
|
||||
use Wallabag\Event\EntrySavedEvent;
|
||||
use Wallabag\Import\AbstractImport;
|
||||
use Wallabag\Repository\UserRepository;
|
||||
|
||||
abstract class AbstractConsumer
|
||||
{
|
||||
protected $em;
|
||||
protected $userRepository;
|
||||
protected $import;
|
||||
protected $eventDispatcher;
|
||||
protected $logger;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, UserRepository $userRepository, AbstractImport $import, EventDispatcherInterface $eventDispatcher, ?LoggerInterface $logger = null)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->import = $import;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
public function __construct(
|
||||
protected EntityManagerInterface $em,
|
||||
protected UserRepository $userRepository,
|
||||
protected AbstractImport $import,
|
||||
protected EventDispatcherInterface $eventDispatcher,
|
||||
protected ?LoggerInterface $logger = null,
|
||||
) {
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
@ -74,9 +68,7 @@ abstract class AbstractConsumer
|
||||
// entry saved, dispatch event about it!
|
||||
$this->eventDispatcher->dispatch(new EntrySavedEvent($entry), EntrySavedEvent::NAME);
|
||||
|
||||
// clear only affected entities
|
||||
$this->em->clear(Entry::class);
|
||||
$this->em->clear(Tag::class);
|
||||
$this->em->clear();
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->warning('Unable to save entry', ['entry' => $storedEntry, 'exception' => $e]);
|
||||
|
||||
|
||||
@ -10,48 +10,22 @@ use OldSound\RabbitMqBundle\RabbitMq\Consumer;
|
||||
*/
|
||||
class RabbitMQConsumerTotalProxy
|
||||
{
|
||||
private Consumer $pocketConsumer;
|
||||
private Consumer $readabilityConsumer;
|
||||
private Consumer $wallabagV1Consumer;
|
||||
private Consumer $wallabagV2Consumer;
|
||||
private Consumer $firefoxConsumer;
|
||||
private Consumer $chromeConsumer;
|
||||
private Consumer $instapaperConsumer;
|
||||
private Consumer $pinboardConsumer;
|
||||
private Consumer $deliciousConsumer;
|
||||
private Consumer $elcuratorConsumer;
|
||||
private Consumer $shaarliConsumer;
|
||||
private Consumer $pocketHtmlConsumer;
|
||||
private Consumer $omnivoreConsumer;
|
||||
|
||||
public function __construct(
|
||||
Consumer $pocketConsumer,
|
||||
Consumer $readabilityConsumer,
|
||||
Consumer $wallabagV1Consumer,
|
||||
Consumer $wallabagV2Consumer,
|
||||
Consumer $firefoxConsumer,
|
||||
Consumer $chromeConsumer,
|
||||
Consumer $instapaperConsumer,
|
||||
Consumer $pinboardConsumer,
|
||||
Consumer $deliciousConsumer,
|
||||
Consumer $elcuratorConsumer,
|
||||
Consumer $shaarliConsumer,
|
||||
Consumer $pocketHtmlConsumer,
|
||||
Consumer $omnivoreConsumer
|
||||
private readonly Consumer $pocketConsumer,
|
||||
private readonly Consumer $readabilityConsumer,
|
||||
private readonly Consumer $wallabagV1Consumer,
|
||||
private readonly Consumer $wallabagV2Consumer,
|
||||
private readonly Consumer $firefoxConsumer,
|
||||
private readonly Consumer $chromeConsumer,
|
||||
private readonly Consumer $instapaperConsumer,
|
||||
private readonly Consumer $pinboardConsumer,
|
||||
private readonly Consumer $deliciousConsumer,
|
||||
private readonly Consumer $elcuratorConsumer,
|
||||
private readonly Consumer $shaarliConsumer,
|
||||
private readonly Consumer $pocketHtmlConsumer,
|
||||
private readonly Consumer $pocketCsvConsumer,
|
||||
private readonly Consumer $omnivoreConsumer,
|
||||
) {
|
||||
$this->pocketConsumer = $pocketConsumer;
|
||||
$this->readabilityConsumer = $readabilityConsumer;
|
||||
$this->wallabagV1Consumer = $wallabagV1Consumer;
|
||||
$this->wallabagV2Consumer = $wallabagV2Consumer;
|
||||
$this->firefoxConsumer = $firefoxConsumer;
|
||||
$this->chromeConsumer = $chromeConsumer;
|
||||
$this->instapaperConsumer = $instapaperConsumer;
|
||||
$this->pinboardConsumer = $pinboardConsumer;
|
||||
$this->deliciousConsumer = $deliciousConsumer;
|
||||
$this->elcuratorConsumer = $elcuratorConsumer;
|
||||
$this->shaarliConsumer = $shaarliConsumer;
|
||||
$this->pocketHtmlConsumer = $pocketHtmlConsumer;
|
||||
$this->omnivoreConsumer = $omnivoreConsumer;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,6 +76,9 @@ class RabbitMQConsumerTotalProxy
|
||||
case 'pocket_html':
|
||||
$consumer = $this->pocketHtmlConsumer;
|
||||
break;
|
||||
case 'pocket_csv':
|
||||
$consumer = $this->pocketCsvConsumer;
|
||||
break;
|
||||
case 'omnivore':
|
||||
$consumer = $this->omnivoreConsumer;
|
||||
break;
|
||||
@ -115,6 +92,6 @@ class RabbitMQConsumerTotalProxy
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $message->delivery_info['message_count'] + 1;
|
||||
return $message->getMessageCount() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ namespace Wallabag\Controller;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use FOS\RestBundle\Controller\AbstractFOSRestController;
|
||||
use JMS\Serializer\SerializerInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@ -19,15 +20,11 @@ use Wallabag\Repository\AnnotationRepository;
|
||||
|
||||
class AnnotationController extends AbstractFOSRestController
|
||||
{
|
||||
protected EntityManagerInterface $entityManager;
|
||||
protected SerializerInterface $serializer;
|
||||
protected FormFactoryInterface $formFactory;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, SerializerInterface $serializer, FormFactoryInterface $formFactory)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->serializer = $serializer;
|
||||
$this->formFactory = $formFactory;
|
||||
public function __construct(
|
||||
protected EntityManagerInterface $entityManager,
|
||||
protected SerializerInterface $serializer,
|
||||
protected FormFactoryInterface $formFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,10 +32,10 @@ class AnnotationController extends AbstractFOSRestController
|
||||
*
|
||||
* @see Api\WallabagRestController
|
||||
*
|
||||
* @Route("/annotations/{entry}.{_format}", name="annotations_get_annotations", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/annotations/{entry}.{_format}', name: 'annotations_get_annotations', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('LIST_ANNOTATIONS', subject: 'entry')]
|
||||
public function getAnnotationsAction(Entry $entry, AnnotationRepository $annotationRepository)
|
||||
{
|
||||
$annotationRows = $annotationRepository->findByEntryIdAndUserId($entry->getId(), $this->getUser()->getId());
|
||||
@ -56,10 +53,10 @@ class AnnotationController extends AbstractFOSRestController
|
||||
*
|
||||
* @see Api\WallabagRestController
|
||||
*
|
||||
* @Route("/annotations/{entry}.{_format}", name="annotations_post_annotation", methods={"POST"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/annotations/{entry}.{_format}', name: 'annotations_post_annotation', methods: ['POST'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('CREATE_ANNOTATIONS', subject: 'entry')]
|
||||
public function postAnnotationAction(Request $request, Entry $entry)
|
||||
{
|
||||
$data = json_decode($request->getContent(), true);
|
||||
@ -82,7 +79,7 @@ class AnnotationController extends AbstractFOSRestController
|
||||
return JsonResponse::fromJsonString($json);
|
||||
}
|
||||
|
||||
return $form;
|
||||
return new JsonResponse(status: 400);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,15 +87,13 @@ class AnnotationController extends AbstractFOSRestController
|
||||
*
|
||||
* @see Api\WallabagRestController
|
||||
*
|
||||
* @Route("/annotations/{annotation}.{_format}", name="annotations_put_annotation", methods={"PUT"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function putAnnotationAction(Request $request, AnnotationRepository $annotationRepository, int $annotation)
|
||||
#[Route(path: '/annotations/{annotation}.{_format}', name: 'annotations_put_annotation', methods: ['PUT'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('EDIT', subject: 'annotation')]
|
||||
public function putAnnotationAction(Request $request, Annotation $annotation)
|
||||
{
|
||||
try {
|
||||
$annotation = $this->validateAnnotation($annotationRepository, $annotation, $this->getUser()->getId());
|
||||
|
||||
$data = json_decode($request->getContent(), true, 512, \JSON_THROW_ON_ERROR);
|
||||
|
||||
$form = $this->formFactory->createNamed('', EditAnnotationType::class, $annotation, [
|
||||
@ -116,7 +111,7 @@ class AnnotationController extends AbstractFOSRestController
|
||||
return JsonResponse::fromJsonString($json);
|
||||
}
|
||||
|
||||
return $form;
|
||||
return new JsonResponse(status: 400);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new NotFoundHttpException($e);
|
||||
}
|
||||
@ -127,15 +122,13 @@ class AnnotationController extends AbstractFOSRestController
|
||||
*
|
||||
* @see Api\WallabagRestController
|
||||
*
|
||||
* @Route("/annotations/{annotation}.{_format}", name="annotations_delete_annotation", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function deleteAnnotationAction(AnnotationRepository $annotationRepository, int $annotation)
|
||||
#[Route(path: '/annotations/{annotation}.{_format}', name: 'annotations_delete_annotation', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('DELETE', subject: 'annotation')]
|
||||
public function deleteAnnotationAction(Annotation $annotation)
|
||||
{
|
||||
try {
|
||||
$annotation = $this->validateAnnotation($annotationRepository, $annotation, $this->getUser()->getId());
|
||||
|
||||
$this->entityManager->remove($annotation);
|
||||
$this->entityManager->flush();
|
||||
|
||||
@ -157,15 +150,4 @@ class AnnotationController extends AbstractFOSRestController
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function validateAnnotation(AnnotationRepository $annotationRepository, int $annotationId, int $userId)
|
||||
{
|
||||
$annotation = $annotationRepository->findOneByIdAndUserId($annotationId, $userId);
|
||||
|
||||
if (null === $annotation) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
return $annotation;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ namespace Wallabag\Controller\Api;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\Operation;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@ -34,14 +35,12 @@ class AnnotationRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/annotations/{entry}.{_format}", name="api_get_annotations", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/api/annotations/{entry}.{_format}', name: 'api_get_annotations', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('LIST_ANNOTATIONS', subject: 'entry')]
|
||||
public function getAnnotationsAction(Entry $entry)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
return $this->forward('Wallabag\Controller\AnnotationController::getAnnotationsAction', [
|
||||
'entry' => $entry,
|
||||
]);
|
||||
@ -100,14 +99,12 @@ class AnnotationRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/annotations/{entry}.{_format}", name="api_post_annotation", methods={"POST"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/api/annotations/{entry}.{_format}', name: 'api_post_annotation', methods: ['POST'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('CREATE_ANNOTATIONS', subject: 'entry')]
|
||||
public function postAnnotationAction(Request $request, Entry $entry)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
return $this->forward('Wallabag\Controller\AnnotationController::postAnnotationAction', [
|
||||
'request' => $request,
|
||||
'entry' => $entry,
|
||||
@ -136,14 +133,12 @@ class AnnotationRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/annotations/{annotation}.{_format}", name="api_put_annotation", methods={"PUT"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function putAnnotationAction(int $annotation, Request $request)
|
||||
#[Route(path: '/api/annotations/{annotation}.{_format}', name: 'api_put_annotation', methods: ['PUT'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('EDIT', subject: 'annotation')]
|
||||
public function putAnnotationAction(Annotation $annotation, Request $request)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
return $this->forward('Wallabag\Controller\AnnotationController::putAnnotationAction', [
|
||||
'annotation' => $annotation,
|
||||
'request' => $request,
|
||||
@ -172,14 +167,12 @@ class AnnotationRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/annotations/{annotation}.{_format}", name="api_delete_annotation", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteAnnotationAction(int $annotation)
|
||||
#[Route(path: '/api/annotations/{annotation}.{_format}', name: 'api_delete_annotation', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('DELETE', subject: 'annotation')]
|
||||
public function deleteAnnotationAction(Annotation $annotation)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
return $this->forward('Wallabag\Controller\AnnotationController::deleteAnnotationAction', [
|
||||
'annotation' => $annotation,
|
||||
]);
|
||||
|
||||
@ -23,10 +23,9 @@ class ConfigRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/config.{_format}", name="api_get_config", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/config.{_format}', name: 'api_get_config', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
public function getConfigAction(SerializerInterface $serializer)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
@ -6,6 +6,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Wallabag\Controller\AbstractController;
|
||||
@ -18,10 +19,9 @@ class DeveloperController extends AbstractController
|
||||
/**
|
||||
* List all clients and link to create a new one.
|
||||
*
|
||||
* @Route("/developer", name="developer", methods={"GET"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/developer', name: 'developer', methods: ['GET'])]
|
||||
public function indexAction(ClientRepository $repo)
|
||||
{
|
||||
$clients = $repo->findByUser($this->getUser()->getId());
|
||||
@ -34,10 +34,9 @@ class DeveloperController extends AbstractController
|
||||
/**
|
||||
* Create a client (an app).
|
||||
*
|
||||
* @Route("/developer/client/create", name="developer_create_client", methods={"GET", "POST"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/developer/client/create', name: 'developer_create_client', methods: ['GET', 'POST'])]
|
||||
public function createClientAction(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator)
|
||||
{
|
||||
$client = new Client($this->getUser());
|
||||
@ -69,14 +68,13 @@ class DeveloperController extends AbstractController
|
||||
/**
|
||||
* Remove a client.
|
||||
*
|
||||
* @Route("/developer/client/delete/{id}", name="developer_delete_client", methods={"POST"}, requirements={"id" = "\d+"})
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
#[Route(path: '/developer/client/delete/{id}', name: 'developer_delete_client', methods: ['POST'], requirements: ['id' => '\d+'])]
|
||||
public function deleteClientAction(Request $request, Client $client, EntityManagerInterface $entityManager, TranslatorInterface $translator)
|
||||
{
|
||||
if (!$this->isCsrfTokenValid('delete-client', $request->request->get('token'))) {
|
||||
throw $this->createAccessDeniedException('Bad CSRF token.');
|
||||
throw new BadRequestHttpException('Bad CSRF token.');
|
||||
}
|
||||
|
||||
if (null === $this->getUser() || $client->getUser()->getId() !== $this->getUser()->getId()) {
|
||||
@ -97,10 +95,9 @@ class DeveloperController extends AbstractController
|
||||
/**
|
||||
* Display developer how to use an existing app.
|
||||
*
|
||||
* @Route("/developer/howto/first-app", name="developer_howto_firstapp", methods={"GET"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/developer/howto/first-app', name: 'developer_howto_firstapp', methods: ['GET'])]
|
||||
public function howtoFirstAppAction()
|
||||
{
|
||||
return $this->render('Developer/howto_app.html.twig');
|
||||
|
||||
@ -8,6 +8,7 @@ use Nelmio\ApiDocBundle\Annotation\Operation;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@ -84,14 +85,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/exists.{_format}", name="api_get_entries_exists", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/exists.{_format}', name: 'api_get_entries_exists', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('LIST_ENTRIES')]
|
||||
public function getEntriesExistsAction(Request $request, EntryRepository $entryRepository)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
$returnId = (null === $request->query->get('return_id')) ? false : (bool) $request->query->get('return_id');
|
||||
|
||||
$hashedUrls = $request->query->all('hashed_urls');
|
||||
@ -132,9 +131,7 @@ class EntryRestController extends WallabagRestController
|
||||
}
|
||||
|
||||
if (false === $returnId) {
|
||||
$results = array_map(function ($v) {
|
||||
return null !== $v;
|
||||
}, $results);
|
||||
$results = array_map(fn ($v) => null !== $v, $results);
|
||||
}
|
||||
|
||||
$results = $this->replaceUrlHashes($results, $urlHashMap);
|
||||
@ -299,24 +296,22 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries.{_format}", name="api_get_entries", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries.{_format}', name: 'api_get_entries', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('LIST_ENTRIES')]
|
||||
public function getEntriesAction(Request $request, EntryRepository $entryRepository)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
$isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
|
||||
$isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
|
||||
$isPublic = (null === $request->query->get('public')) ? null : (bool) $request->query->get('public');
|
||||
$isNotParsed = (null === $request->query->get('notParsed')) ? null : (bool) $request->query->get('notParsed');
|
||||
$sort = strtolower($request->query->get('sort', 'created'));
|
||||
$order = strtolower($request->query->get('order', 'desc'));
|
||||
$page = (int) $request->query->get('page', 1);
|
||||
$perPage = (int) $request->query->get('perPage', 30);
|
||||
$page = $request->query->getInt('page', 1);
|
||||
$perPage = $request->query->getInt('perPage', 30);
|
||||
$tags = \is_array($request->query->all()['tags'] ?? '') ? '' : (string) $request->query->get('tags', '');
|
||||
$since = $request->query->get('since', 0);
|
||||
$since = $request->query->getInt('since');
|
||||
$detail = strtolower($request->query->get('detail', 'full'));
|
||||
$domainName = (null === $request->query->get('domain_name')) ? '' : (string) $request->query->get('domain_name');
|
||||
$httpStatus = (!\array_key_exists((int) $request->query->get('http_status'), Response::$statusTexts)) ? null : (int) $request->query->get('http_status');
|
||||
@ -391,15 +386,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/{entry}.{_format}", name="api_get_entry", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/{entry}.{_format}', name: 'api_get_entry', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('VIEW', subject: 'entry')]
|
||||
public function getEntryAction(Entry $entry)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
$this->validateUserAccess($entry->getUser()->getId());
|
||||
|
||||
return $this->sendResponse($entry);
|
||||
}
|
||||
|
||||
@ -435,15 +427,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/{entry}/export.{_format}", name="api_get_entry_export", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/api/entries/{entry}/export.{_format}', name: 'api_get_entry_export', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('VIEW', subject: 'entry')]
|
||||
public function getEntryExportAction(Entry $entry, Request $request, EntriesExport $entriesExport)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
$this->validateUserAccess($entry->getUser()->getId());
|
||||
|
||||
return $entriesExport
|
||||
->setEntries($entry)
|
||||
->updateTitle('entry')
|
||||
@ -470,14 +459,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/list.{_format}", name="api_delete_entries_list", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/list.{_format}', name: 'api_delete_entries_list', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('DELETE_ENTRIES')]
|
||||
public function deleteEntriesListAction(Request $request, EntryRepository $entryRepository, EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
$urls = json_decode($request->query->get('urls', '[]'));
|
||||
|
||||
if (empty($urls)) {
|
||||
@ -495,7 +482,7 @@ class EntryRestController extends WallabagRestController
|
||||
|
||||
$results[$key]['url'] = $url;
|
||||
|
||||
if (false !== $entry) {
|
||||
if (false !== $entry && $this->authorizationChecker->isGranted('DELETE', $entry)) {
|
||||
// entry deleted, dispatch event about it!
|
||||
$eventDispatcher->dispatch(new EntryDeletedEvent($entry), EntryDeletedEvent::NAME);
|
||||
|
||||
@ -528,16 +515,14 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/lists.{_format}", name="api_post_entries_list", methods={"POST"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @throws HttpException When limit is reached
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/lists.{_format}', name: 'api_post_entries_list', methods: ['POST'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('CREATE_ENTRIES')]
|
||||
public function postEntriesListAction(Request $request, EntryRepository $entryRepository, EventDispatcherInterface $eventDispatcher, ContentProxy $contentProxy)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
$urls = json_decode($request->query->get('urls', '[]'));
|
||||
|
||||
$limit = $this->getParameter('wallabag.api_limit_mass_actions');
|
||||
@ -569,7 +554,7 @@ class EntryRestController extends WallabagRestController
|
||||
$this->entityManager->persist($entry);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
|
||||
$results[$key]['entry'] = $entry->getId();
|
||||
|
||||
// entry saved, dispatch event about it!
|
||||
$eventDispatcher->dispatch(new EntrySavedEvent($entry), EntrySavedEvent::NAME);
|
||||
@ -713,10 +698,10 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries.{_format}", name="api_post_entries", methods={"POST"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries.{_format}', name: 'api_post_entries', methods: ['POST'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('CREATE_ENTRIES')]
|
||||
public function postEntriesAction(
|
||||
Request $request,
|
||||
EntryRepository $entryRepository,
|
||||
@ -724,10 +709,8 @@ class EntryRestController extends WallabagRestController
|
||||
LoggerInterface $logger,
|
||||
TagsAssigner $tagsAssigner,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
ValidatorInterface $validator
|
||||
ValidatorInterface $validator,
|
||||
) {
|
||||
$this->validateAuthentication();
|
||||
|
||||
$url = $request->request->get('url');
|
||||
|
||||
$entry = $entryRepository->findByUrlAndUserId(
|
||||
@ -751,7 +734,7 @@ class EntryRestController extends WallabagRestController
|
||||
'html' => !empty($data['content']) ? $data['content'] : $entry->getContent(),
|
||||
'url' => $entry->getUrl(),
|
||||
'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(),
|
||||
'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(),
|
||||
'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt()?->format('Y-m-d H:i:s') ?? '',
|
||||
// faking the open graph preview picture
|
||||
'image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
|
||||
'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(),
|
||||
@ -938,15 +921,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/{entry}.{_format}", name="api_patch_entries", methods={"PATCH"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/{entry}.{_format}', name: 'api_patch_entries', methods: ['PATCH'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('EDIT', subject: 'entry')]
|
||||
public function patchEntriesAction(Entry $entry, Request $request, ContentProxy $contentProxy, LoggerInterface $logger, TagsAssigner $tagsAssigner, EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
$this->validateUserAccess($entry->getUser()->getId());
|
||||
|
||||
$data = $this->retrieveValueFromRequest($request);
|
||||
|
||||
// this is a special case where user want to manually update the entry content
|
||||
@ -1055,15 +1035,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/{entry}/reload.{_format}", name="api_patch_entries_reload", methods={"PATCH"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/{entry}/reload.{_format}', name: 'api_patch_entries_reload', methods: ['PATCH'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('RELOAD', subject: 'entry')]
|
||||
public function patchEntriesReloadAction(Entry $entry, ContentProxy $contentProxy, LoggerInterface $logger, EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
$this->validateUserAccess($entry->getUser()->getId());
|
||||
|
||||
try {
|
||||
$contentProxy->updateEntry($entry, $entry->getUrl());
|
||||
} catch (\Exception $e) {
|
||||
@ -1112,18 +1089,16 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/{entry}.{_format}", name="api_delete_entries", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/{entry}.{_format}', name: 'api_delete_entries', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('DELETE', subject: 'entry')]
|
||||
public function deleteEntriesAction(Entry $entry, Request $request, EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$expect = $request->query->get('expect', 'entry');
|
||||
if (!\in_array($expect, ['id', 'entry'], true)) {
|
||||
throw new BadRequestHttpException(\sprintf("expect: 'id' or 'entry' expected, %s given", $expect));
|
||||
}
|
||||
$this->validateAuthentication();
|
||||
$this->validateUserAccess($entry->getUser()->getId());
|
||||
|
||||
$response = $this->sendResponse([
|
||||
'id' => $entry->getId(),
|
||||
@ -1165,15 +1140,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/{entry}/tags.{_format}", name="api_get_entries_tags", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/{entry}/tags.{_format}', name: 'api_get_entries_tags', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('LIST_TAGS', subject: 'entry')]
|
||||
public function getEntriesTagsAction(Entry $entry)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
$this->validateUserAccess($entry->getUser()->getId());
|
||||
|
||||
return $this->sendResponse($entry->getTags());
|
||||
}
|
||||
|
||||
@ -1209,15 +1181,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/{entry}/tags.{_format}", name="api_post_entries_tags", methods={"POST"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/{entry}/tags.{_format}', name: 'api_post_entries_tags', methods: ['POST'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('TAG', subject: 'entry')]
|
||||
public function postEntriesTagsAction(Request $request, Entry $entry, TagsAssigner $tagsAssigner)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
$this->validateUserAccess($entry->getUser()->getId());
|
||||
|
||||
$tags = $request->request->get('tags', '');
|
||||
if (!empty($tags)) {
|
||||
$tagsAssigner->assignTagsToEntry($entry, $tags);
|
||||
@ -1261,15 +1230,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/{entry}/tags/{tag}.{_format}", name="api_delete_entries_tags", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/{entry}/tags/{tag}.{_format}', name: 'api_delete_entries_tags', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('UNTAG', subject: 'entry')]
|
||||
public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
$this->validateUserAccess($entry->getUser()->getId());
|
||||
|
||||
$entry->removeTag($tag);
|
||||
|
||||
$this->entityManager->persist($entry);
|
||||
@ -1297,14 +1263,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/tags/list.{_format}", name="api_delete_entries_tags_list", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/tags/list.{_format}', name: 'api_delete_entries_tags_list', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('DELETE_TAGS')]
|
||||
public function deleteEntriesTagsListAction(Request $request, TagRepository $tagRepository, EntryRepository $entryRepository)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
$list = json_decode($request->query->get('list', '[]'));
|
||||
|
||||
if (empty($list)) {
|
||||
@ -1325,7 +1289,7 @@ class EntryRestController extends WallabagRestController
|
||||
|
||||
$tags = $element->tags;
|
||||
|
||||
if (false !== $entry && !(empty($tags))) {
|
||||
if (false !== $entry && !(empty($tags)) && $this->authorizationChecker->isGranted('UNTAG', $entry)) {
|
||||
$tags = explode(',', $tags);
|
||||
foreach ($tags as $label) {
|
||||
$label = trim($label);
|
||||
@ -1364,14 +1328,12 @@ class EntryRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/entries/tags/lists.{_format}", name="api_post_entries_tags_list", methods={"POST"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/entries/tags/lists.{_format}', name: 'api_post_entries_tags_list', methods: ['POST'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('CREATE_TAGS')]
|
||||
public function postEntriesTagsListAction(Request $request, EntryRepository $entryRepository, TagsAssigner $tagsAssigner)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
$list = json_decode($request->query->get('list', '[]'));
|
||||
|
||||
if (empty($list)) {
|
||||
@ -1392,7 +1354,7 @@ class EntryRestController extends WallabagRestController
|
||||
|
||||
$tags = $element->tags;
|
||||
|
||||
if (false !== $entry && !(empty($tags))) {
|
||||
if (false !== $entry && !(empty($tags)) && $this->authorizationChecker->isGranted('TAG', $entry)) {
|
||||
$tagsAssigner->assignTagsToEntry($entry, $tags);
|
||||
|
||||
$this->entityManager->persist($entry);
|
||||
|
||||
@ -8,6 +8,7 @@ use Nelmio\ApiDocBundle\Annotation\Operation;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Pagerfanta\Doctrine\ORM\QueryAdapter as DoctrineORMAdapter;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@ -54,17 +55,15 @@ class SearchRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/search.{_format}", name="api_get_search", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/search.{_format}', name: 'api_get_search', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
#[IsGranted('LIST_ENTRIES')]
|
||||
public function getSearchAction(Request $request, EntryRepository $entryRepository)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
$term = $request->query->get('term');
|
||||
$page = (int) $request->query->get('page', 1);
|
||||
$perPage = (int) $request->query->get('perPage', 30);
|
||||
$page = $request->query->getInt('page', 1);
|
||||
$perPage = $request->query->getInt('perPage', 30);
|
||||
|
||||
$qb = $entryRepository->getBuilderForSearchByUser(
|
||||
$this->getUser()->getId(),
|
||||
|
||||
@ -26,10 +26,9 @@ class TagRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/tags.{_format}", name="api_get_tags", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/tags.{_format}', name: 'api_get_tags', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
public function getTagsAction(TagRepository $tagRepository)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
@ -63,10 +62,9 @@ class TagRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/tag/label.{_format}", name="api_delete_tag_label", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/tag/label.{_format}', name: 'api_delete_tag_label', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
public function deleteTagLabelAction(Request $request, TagRepository $tagRepository, EntryRepository $entryRepository)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
@ -111,10 +109,9 @@ class TagRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/tags/label.{_format}", name="api_delete_tags_label", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/tags/label.{_format}', name: 'api_delete_tags_label', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
public function deleteTagsLabelAction(Request $request, TagRepository $tagRepository, EntryRepository $entryRepository)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
@ -158,10 +155,9 @@ class TagRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/tags/{tag}.{_format}", name="api_delete_tag", methods={"DELETE"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/api/tags/{tag}.{_format}', name: 'api_delete_tag', methods: ['DELETE'], defaults: ['_format' => 'json'])]
|
||||
public function deleteTagAction(Tag $tag, TagRepository $tagRepository, EntryRepository $entryRepository)
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
@ -23,10 +23,9 @@ class TaggingRuleRestController extends WallabagRestController
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Route("/api/taggingrule/export.{_format}", name="api_get_taggingrule_export", methods={"GET"}, defaults={"_format": "json"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/api/taggingrule/export.{_format}', name: 'api_get_taggingrule_export', methods: ['GET'], defaults: ['_format' => 'json'])]
|
||||
public function getTaggingruleExportAction()
|
||||
{
|
||||
$this->validateAuthentication();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user