Add PHPStan and other quality checks

Similar to FreshRSS core
Contributes to https://github.com/FreshRSS/Extensions/issues/184
This commit is contained in:
Alexandre Alapetite 2023-11-23 22:41:09 +01:00
parent a86467db48
commit b49596818c
No known key found for this signature in database
GPG key ID: A24378C38E812B23
59 changed files with 1173 additions and 404 deletions

View file

@ -1,4 +1,6 @@
*.min.js
.git/
*.min.js
node_modules/
symbolic/
tmp/
vendor/

7
.gitignore vendored
View file

@ -1,3 +1,6 @@
tmp
/node_modules/
.vscode/
bin/
node_modules/
symbolic/
tmp/
vendor/

View file

@ -1,3 +1,5 @@
.git/
node_modules/
symbolic/
tmp/
vendor/

5
.markdownlintignore Normal file
View file

@ -0,0 +1,5 @@
.git/
node_modules/
symbolic/
tmp/
vendor/

View file

@ -1,3 +1,5 @@
.git/
node_modules/
symbolic/
tmp/
vendor/

View file

@ -2,7 +2,8 @@
"extends": "stylelint-config-recommended-scss",
"plugins": [
"stylelint-order",
"stylelint-scss"
"stylelint-scss",
"stylelint-stylistic"
],
"rules": {
"at-rule-empty-line-before": [
@ -10,27 +11,27 @@
"ignoreAtRules": [ "after-comment", "else" ]
}
],
"at-rule-name-space-after": [
"stylistic/at-rule-name-space-after": [
"always", {
"ignoreAtRules": [ "after-comment" ]
}
],
"block-closing-brace-newline-after": [
"stylistic/block-closing-brace-newline-after": [
"always", {
"ignoreAtRules": [ "if", "else" ]
}
],
"block-closing-brace-newline-before": "always-multi-line",
"block-opening-brace-newline-after": "always-multi-line",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"stylistic/block-closing-brace-newline-before": "always-multi-line",
"stylistic/block-opening-brace-newline-after": "always-multi-line",
"stylistic/block-opening-brace-space-before": "always",
"stylistic/color-hex-case": "lower",
"color-hex-length": "short",
"color-no-invalid-hex": true,
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"indentation": "tab",
"stylistic/declaration-colon-space-after": "always",
"stylistic/declaration-colon-space-before": "never",
"stylistic/indentation": "tab",
"no-descending-specificity": null,
"no-eol-whitespace": true,
"stylistic/no-eol-whitespace": true,
"property-no-vendor-prefix": true,
"rule-empty-line-before": [
"always", {

23
.typos.toml Normal file
View file

@ -0,0 +1,23 @@
[default.extend-identifiers]
ot = "ot"
Ths2 = "Ths2"
[default.extend-words]
referer = "referer"
[files]
extend-exclude = [
".git/",
"*.fr.md",
"*.map",
"*.min.js",
"*.rtl.css",
"*/i18n/de",
"*/i18n/fr",
"bin/",
"node_modules/",
"symbolic/",
"tmp/",
"vendor/",
"xExtension-ReadingTime/README.md"
]

View file

@ -37,3 +37,79 @@ generate: ## Generate the extensions.json file
.PHONY: help
help:
@grep --extended-regexp '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
######################
## Tests and linter ##
######################
.PHONY: test
test: vendor/bin/phpunit ## Run the test suite
$(PHP) vendor/bin/phpunit --bootstrap ./tests/bootstrap.php ./tests
.PHONY: lint
lint: vendor/bin/phpcs ## Run the linter on the PHP files
$(PHP) vendor/bin/phpcs . -p -s
.PHONY: lint-fix
lint-fix: vendor/bin/phpcbf ## Fix the errors detected by the linter
$(PHP) vendor/bin/phpcbf . -p -s
bin/composer:
mkdir -p bin/
wget 'https://raw.githubusercontent.com/composer/getcomposer.org/a19025d6c0a1ff9fc1fac341128b2823193be462/web/installer' -O - -q | php -- --quiet --install-dir='./bin/' --filename='composer'
vendor/bin/phpunit: bin/composer
bin/composer install --prefer-dist --no-progress
ln -s ../vendor/bin/phpunit bin/phpunit
vendor/bin/phpcs: bin/composer
bin/composer install --prefer-dist --no-progress
ln -s ../vendor/bin/phpcs bin/phpcs
vendor/bin/phpcbf: bin/composer
bin/composer install --prefer-dist --no-progress
ln -s ../vendor/bin/phpcbf bin/phpcbf
bin/typos:
mkdir -p bin/
cd bin ; \
wget -q 'https://github.com/crate-ci/typos/releases/download/v1.16.21/typos-v1.16.21-x86_64-unknown-linux-musl.tar.gz' && \
tar -xvf *.tar.gz './typos' && \
chmod +x typos && \
rm *.tar.gz ; \
cd ..
node_modules/.bin/eslint:
npm install
node_modules/.bin/rtlcss:
npm install
vendor/bin/phpstan: bin/composer
bin/composer install --prefer-dist --no-progress
.PHONY: composer-test
composer-test: vendor/bin/phpstan
bin/composer run-script test
.PHONY: composer-fix
composer-fix:
bin/composer run-script fix
.PHONY: npm-test
npm-test: node_modules/.bin/eslint
npm test
.PHONY: npm-fix
npm-fix: node_modules/.bin/eslint
npm run fix
.PHONY: typos-test
typos-test: bin/typos
bin/typos
# TODO: Add shellcheck, shfmt, hadolint
.PHONY: test-all
test-all: composer-test npm-test typos-test
.PHONY: fix-all
fix-all: composer-fix npm-fix

79
composer.json Normal file
View file

@ -0,0 +1,79 @@
{
"name": "freshrss.org/freshrss-extensions",
"description": "Extensions for FreshRSS",
"type": "project",
"homepage": "https://freshrss.org/",
"license": "AGPL-3.0",
"support": {
"docs": "https://freshrss.github.io/FreshRSS/",
"issues": "https://github.com/FreshRSS/Extensions/issues",
"source": "https://github.com/FreshRSS/Extensions/"
},
"keywords": [
"news",
"aggregator",
"RSS",
"Atom",
"WebSub"
],
"require": {
"php": ">=7.4",
"ext-ctype": "*",
"ext-curl": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gmp": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_sqlite": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*"
},
"suggest": {
"ext-iconv": "*",
"ext-pdo_mysql": "*",
"ext-pdo_pgsql": "*"
},
"require-dev": {
"php": ">=8.0",
"ext-phar": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-strict-rules": "^1.5",
"squizlabs/php_codesniffer": "^3.7"
},
"scripts": {
"php-lint": "find . -type d -name 'vendor' -prune -o -name '*.php' -print0 | xargs -0 -n1 -P4 php -l 1>/dev/null",
"phtml-lint": "find . -type d -name 'vendor' -prune -o -name '*.phtml' -print0 | xargs -0 -n1 -P4 php -l 1>/dev/null",
"phpcs": "phpcs . -s",
"phpcbf": "phpcbf . -p -s",
"phpstan": "phpstan analyse --memory-limit 512M .",
"phpstan-next": "phpstan analyse --level 9 --memory-limit 512M $(find . -type d -name 'vendor' -prune -o -name '*.php' -o -name '*.phtml' | grep -Fxvf ./tests/phpstan-next.txt | sort | paste -s -)",
"test": [
"@php-lint",
"@phtml-lint",
"@phpcs",
"@phpstan",
"@phpstan-next"
],
"fix": [
"@phpcbf"
]
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": false
}
}
}

214
composer.lock generated Normal file
View file

@ -0,0 +1,214 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2c89b2e0c08f09d94faa394270fc16a9",
"packages": [],
"packages-dev": [
{
"name": "phpstan/phpstan",
"version": "1.10.44",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "bf84367c53a23f759513985c54ffe0d0c249825b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/bf84367c53a23f759513985c54ffe0d0c249825b",
"reference": "bf84367c53a23f759513985c54ffe0d0c249825b",
"shasum": ""
},
"require": {
"php": "^7.2|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
},
"bin": [
"phpstan",
"phpstan.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
"static analysis"
],
"support": {
"docs": "https://phpstan.org/user-guide/getting-started",
"forum": "https://github.com/phpstan/phpstan/discussions",
"issues": "https://github.com/phpstan/phpstan/issues",
"security": "https://github.com/phpstan/phpstan/security/policy",
"source": "https://github.com/phpstan/phpstan-src"
},
"funding": [
{
"url": "https://github.com/ondrejmirtes",
"type": "github"
},
{
"url": "https://github.com/phpstan",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
"type": "tidelift"
}
],
"time": "2023-11-21T16:30:46+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542",
"reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.10.34"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-deprecation-rules": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^9.5"
},
"type": "phpstan-extension",
"extra": {
"phpstan": {
"includes": [
"rules.neon"
]
}
},
"autoload": {
"psr-4": {
"PHPStan\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.2"
},
"time": "2023-10-30T14:35:06+00:00"
},
{
"name": "squizlabs/php_codesniffer",
"version": "3.7.2",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879",
"reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"bin": [
"bin/phpcs",
"bin/phpcbf"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Greg Sherwood",
"role": "lead"
}
],
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"keywords": [
"phpcs",
"standards",
"static analysis"
],
"support": {
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
},
"time": "2023-02-22T23:07:41+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.4",
"ext-ctype": "*",
"ext-curl": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gmp": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_sqlite": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*"
},
"platform-dev": {
"php": ">=8.0",
"ext-phar": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*"
},
"plugin-api-version": "2.6.0"
}

View file

@ -18,17 +18,20 @@ if (file_exists($tempFolder)) {
// Parse the repositories.json file to extract extension locations //
// --------------------------------------------------------------- //
try {
$repositories = json_decode(file_get_contents('repositories.json'), true, 512, JSON_THROW_ON_ERROR);
$repositories = json_decode(file_get_contents('repositories.json') ?: '', true, 512, JSON_THROW_ON_ERROR);
if (!is_array($repositories)) {
throw new ParseError('Not an array!');
}
} catch (Exception $exception) {
echo 'The repositories.json file is not a valid JSON file.', PHP_EOL;
exit(1);
}
foreach ($repositories as $repository) {
if (null === $url = $repository['url'] ?? null) {
if (null === $url = ($repository['url'] ?? null)) {
continue;
}
if (TYPE_GIT === $repository['type'] ?? null) {
if (TYPE_GIT === ($repository['type'] ?? null)) {
$gitRepositories[sha1($url)] = $url;
}
}
@ -44,7 +47,10 @@ foreach ($gitRepositories as $key => $gitRepository) {
exec("find {$tempFolder}/{$key} -iname metadata.json", $metadataFiles);
foreach ($metadataFiles as $metadataFile) {
try {
$metadata = json_decode(file_get_contents($metadataFile), true, 512, JSON_THROW_ON_ERROR);
$metadata = json_decode(file_get_contents($metadataFile) ?: '', true, 512, JSON_THROW_ON_ERROR);
if (!is_array($metadata)) {
throw new ParseError('Not an array!');
}
$directory = basename(dirname($metadataFile));
$metadata['url'] = $gitRepository;
$metadata['method'] = TYPE_GIT;

482
package-lock.json generated
View file

@ -13,10 +13,12 @@
"eslint-plugin-n": "^16.0.1",
"eslint-plugin-promise": "^6.1.1",
"markdownlint-cli": "^0.37.0",
"rtlcss": "^4.1.1",
"sass": "^1.69.0",
"stylelint": "^15.10.3",
"stylelint-config-recommended-scss": "^13.0.0",
"stylelint-order": "^6.0.3"
"stylelint-config-recommended-scss": "^13.1.0",
"stylelint-order": "^6.0.3",
"stylelint-stylistic": "^0.4.3"
},
"engines": {
"node": ">=12"
@ -32,12 +34,12 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz",
"integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.22.13",
"@babel/highlight": "^7.23.4",
"chalk": "^2.4.2"
},
"engines": {
@ -125,9 +127,9 @@
}
},
"node_modules/@babel/highlight": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
@ -311,18 +313,18 @@
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz",
"integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==",
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
"dev": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@ -343,21 +345,21 @@
}
},
"node_modules/@eslint/js": {
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
"integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
"integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.1",
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
},
@ -379,9 +381,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"node_modules/@isaacs/cliui": {
@ -480,21 +482,27 @@
"dev": true
},
"node_modules/@types/minimist": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz",
"integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==",
"dev": true
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz",
"integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==",
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
"dev": true
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
"node_modules/acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@ -755,6 +763,18 @@
"node": ">=8"
}
},
"node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"dev": true,
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/builtins": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
@ -792,13 +812,14 @@
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.1",
"set-function-length": "^1.1.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -990,12 +1011,12 @@
}
},
"node_modules/css-functions-list": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.0.tgz",
"integrity": "sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.1.tgz",
"integrity": "sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==",
"dev": true,
"engines": {
"node": ">=12.22"
"node": ">=12 || >=16"
}
},
"node_modules/css-tree": {
@ -1102,9 +1123,9 @@
"dev": true
},
"node_modules/define-data-property": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz",
"integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.2.1",
@ -1190,26 +1211,26 @@
}
},
"node_modules/es-abstract": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz",
"integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==",
"version": "1.22.3",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
"integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==",
"dev": true,
"dependencies": {
"array-buffer-byte-length": "^1.0.0",
"arraybuffer.prototype.slice": "^1.0.2",
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
"call-bind": "^1.0.5",
"es-set-tostringtag": "^2.0.1",
"es-to-primitive": "^1.2.1",
"function.prototype.name": "^1.1.6",
"get-intrinsic": "^1.2.1",
"get-intrinsic": "^1.2.2",
"get-symbol-description": "^1.0.0",
"globalthis": "^1.0.3",
"gopd": "^1.0.1",
"has": "^1.0.3",
"has-property-descriptors": "^1.0.0",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0",
"internal-slot": "^1.0.5",
"is-array-buffer": "^3.0.2",
"is-callable": "^1.2.7",
@ -1219,7 +1240,7 @@
"is-string": "^1.0.7",
"is-typed-array": "^1.1.12",
"is-weakref": "^1.0.2",
"object-inspect": "^1.12.3",
"object-inspect": "^1.13.1",
"object-keys": "^1.1.1",
"object.assign": "^4.1.4",
"regexp.prototype.flags": "^1.5.1",
@ -1233,7 +1254,7 @@
"typed-array-byte-offset": "^1.0.0",
"typed-array-length": "^1.0.4",
"unbox-primitive": "^1.0.2",
"which-typed-array": "^1.1.11"
"which-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
@ -1243,26 +1264,26 @@
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
"integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.1.3",
"has": "^1.0.3",
"has-tostringtag": "^1.0.0"
"get-intrinsic": "^1.2.2",
"has-tostringtag": "^1.0.0",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-shim-unscopables": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
"integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
"integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
"hasown": "^2.0.0"
}
},
"node_modules/es-to-primitive": {
@ -1282,6 +1303,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -1295,18 +1325,19 @@
}
},
"node_modules/eslint": {
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz",
"integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2",
"@eslint/js": "8.51.0",
"@humanwhocodes/config-array": "^0.11.11",
"@eslint/eslintrc": "^2.1.3",
"@eslint/js": "8.54.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@ -1348,6 +1379,18 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-compat-utils": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz",
"integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==",
"dev": true,
"engines": {
"node": ">=12"
},
"peerDependencies": {
"eslint": ">=6.0.0"
}
},
"node_modules/eslint-config-standard": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
@ -1424,13 +1467,14 @@
}
},
"node_modules/eslint-plugin-es-x": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz",
"integrity": "sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==",
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.4.0.tgz",
"integrity": "sha512-WJa3RhYzBtl8I37ebY9p76s61UhZyi4KaFOnX2A5r32RPazkXj5yoT6PGnD02dhwzEUj0KwsUdqfKDd/OuvGsw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.1.2",
"@eslint-community/regexpp": "^4.6.0"
"@eslint-community/regexpp": "^4.6.0",
"eslint-compat-utils": "^0.1.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@ -1443,26 +1487,26 @@
}
},
"node_modules/eslint-plugin-import": {
"version": "2.28.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz",
"integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==",
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz",
"integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==",
"dev": true,
"dependencies": {
"array-includes": "^3.1.6",
"array.prototype.findlastindex": "^1.2.2",
"array.prototype.flat": "^1.3.1",
"array.prototype.flatmap": "^1.3.1",
"array-includes": "^3.1.7",
"array.prototype.findlastindex": "^1.2.3",
"array.prototype.flat": "^1.3.2",
"array.prototype.flatmap": "^1.3.2",
"debug": "^3.2.7",
"doctrine": "^2.1.0",
"eslint-import-resolver-node": "^0.3.7",
"eslint-import-resolver-node": "^0.3.9",
"eslint-module-utils": "^2.8.0",
"has": "^1.0.3",
"is-core-module": "^2.13.0",
"hasown": "^2.0.0",
"is-core-module": "^2.13.1",
"is-glob": "^4.0.3",
"minimatch": "^3.1.2",
"object.fromentries": "^2.0.6",
"object.groupby": "^1.0.0",
"object.values": "^1.1.6",
"object.fromentries": "^2.0.7",
"object.groupby": "^1.0.1",
"object.values": "^1.1.7",
"semver": "^6.3.1",
"tsconfig-paths": "^3.14.2"
},
@ -1495,9 +1539,9 @@
}
},
"node_modules/eslint-plugin-n": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.1.0.tgz",
"integrity": "sha512-3wv/TooBst0N4ND+pnvffHuz9gNPmk/NkLwAxOt2JykTl/hcuECe6yhTtLJcZjIxtZwN+GX92ACp/QTLpHA3Hg==",
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.3.1.tgz",
"integrity": "sha512-w46eDIkxQ2FaTHcey7G40eD+FhTXOdKudDXPUO2n9WNcslze/i/HT2qJ3GXjHngYSGDISIgPNhwGtgoix4zeOw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
@ -1505,6 +1549,7 @@
"eslint-plugin-es-x": "^7.1.0",
"get-tsconfig": "^4.7.0",
"ignore": "^5.2.4",
"is-builtin-module": "^3.2.1",
"is-core-module": "^2.12.1",
"minimatch": "^3.1.2",
"resolve": "^1.22.2",
@ -1653,9 +1698,9 @@
"dev": true
},
"node_modules/fast-glob": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
"integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@ -1751,9 +1796,9 @@
}
},
"node_modules/flat-cache": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz",
"integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
"integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
"dev": true,
"dependencies": {
"flatted": "^3.2.9",
@ -1761,7 +1806,7 @@
"rimraf": "^3.0.2"
},
"engines": {
"node": ">=12.0.0"
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/flatted": {
@ -1816,10 +1861,13 @@
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/function.prototype.name": {
"version": "1.1.6",
@ -1849,15 +1897,15 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3"
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -2088,15 +2136,6 @@
"node": ">=6"
}
},
"node_modules/has": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
"integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
"dev": true,
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-bigints": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
@ -2116,12 +2155,12 @@
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.1.1"
"get-intrinsic": "^1.2.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -2166,6 +2205,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@ -2203,9 +2254,9 @@
}
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
"dev": true,
"engines": {
"node": ">= 4"
@ -2289,13 +2340,13 @@
}
},
"node_modules/internal-slot": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
"integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
"integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.2.0",
"has": "^1.0.3",
"get-intrinsic": "^1.2.2",
"hasown": "^2.0.0",
"side-channel": "^1.0.4"
},
"engines": {
@ -2362,6 +2413,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-builtin-module": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
"dev": true,
"dependencies": {
"builtin-modules": "^3.3.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@ -2375,12 +2441,12 @@
}
},
"node_modules/is-core-module": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
"integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -2688,9 +2754,9 @@
}
},
"node_modules/known-css-properties": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz",
"integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==",
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz",
"integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==",
"dev": true
},
"node_modules/levn": {
@ -2749,9 +2815,9 @@
"dev": true
},
"node_modules/lru-cache": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz",
"integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz",
"integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
@ -2830,6 +2896,15 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/markdownlint-cli/node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/markdownlint-cli/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
@ -2996,9 +3071,9 @@
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
@ -3071,9 +3146,9 @@
}
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -3434,9 +3509,9 @@
}
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"engines": {
"node": ">=6"
@ -3588,9 +3663,9 @@
}
},
"node_modules/resolve": {
"version": "1.22.6",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
"integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==",
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"dependencies": {
"is-core-module": "^2.13.0",
@ -3667,6 +3742,24 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rtlcss": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz",
"integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==",
"dev": true,
"dependencies": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0",
"postcss": "^8.4.21",
"strip-json-comments": "^3.1.1"
},
"bin": {
"rtlcss": "bin/rtlcss.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/run-con": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz",
@ -3738,9 +3831,9 @@
}
},
"node_modules/sass": {
"version": "1.69.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.0.tgz",
"integrity": "sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==",
"version": "1.69.5",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz",
"integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
@ -3763,6 +3856,21 @@
"semver": "bin/semver.js"
}
},
"node_modules/set-function-length": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
"integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
"dev": true,
"dependencies": {
"define-data-property": "^1.1.1",
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/set-function-name": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
@ -4069,9 +4177,9 @@
"dev": true
},
"node_modules/stylelint": {
"version": "15.10.3",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.10.3.tgz",
"integrity": "sha512-aBQMMxYvFzJJwkmg+BUUg3YfPyeuCuKo2f+LOw7yYbU8AZMblibwzp9OV4srHVeQldxvSFdz0/Xu8blq2AesiA==",
"version": "15.11.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz",
"integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==",
"dev": true,
"dependencies": {
"@csstools/css-parser-algorithms": "^2.3.1",
@ -4081,12 +4189,12 @@
"balanced-match": "^2.0.0",
"colord": "^2.9.3",
"cosmiconfig": "^8.2.0",
"css-functions-list": "^3.2.0",
"css-functions-list": "^3.2.1",
"css-tree": "^2.3.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.1",
"fastest-levenshtein": "^1.0.16",
"file-entry-cache": "^6.0.1",
"file-entry-cache": "^7.0.0",
"global-modules": "^2.0.0",
"globby": "^11.1.0",
"globjoin": "^0.1.4",
@ -4095,13 +4203,13 @@
"import-lazy": "^4.0.0",
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
"known-css-properties": "^0.28.0",
"known-css-properties": "^0.29.0",
"mathml-tag-names": "^2.1.3",
"meow": "^10.1.5",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
"picocolors": "^1.0.0",
"postcss": "^8.4.27",
"postcss": "^8.4.28",
"postcss-resolve-nested-selector": "^0.1.1",
"postcss-safe-parser": "^6.0.0",
"postcss-selector-parser": "^6.0.13",
@ -4139,14 +4247,14 @@
}
},
"node_modules/stylelint-config-recommended-scss": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-13.0.0.tgz",
"integrity": "sha512-7AmMIsHTsuwUQm7I+DD5BGeIgCvqYZ4BpeYJJpb1cUXQwrJAKjA+GBotFZgUEGP8lAM+wmd91ovzOi8xfAyWEw==",
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-13.1.0.tgz",
"integrity": "sha512-8L5nDfd+YH6AOoBGKmhH8pLWF1dpfY816JtGMePcBqqSsLU+Ysawx44fQSlMOJ2xTfI9yTGpup5JU77c17w1Ww==",
"dev": true,
"dependencies": {
"postcss-scss": "^4.0.7",
"postcss-scss": "^4.0.9",
"stylelint-config-recommended": "^13.0.0",
"stylelint-scss": "^5.1.0"
"stylelint-scss": "^5.3.0"
},
"peerDependencies": {
"postcss": "^8.3.3",
@ -4172,12 +4280,12 @@
}
},
"node_modules/stylelint-scss": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.2.1.tgz",
"integrity": "sha512-ZoTJUM85/qqpQHfEppjW/St//8s6p9Qsg8deWlYlr56F9iUgC9vXeIDQvH4odkRRJLTLFQzYMALSOFCQ3MDkgw==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.1.tgz",
"integrity": "sha512-5I9ZDIm77BZrjOccma5WyW2nJEKjXDd4Ca8Kk+oBapSO4pewSlno3n+OyimcyVJJujQZkBN2D+xuMkIamSc6hA==",
"dev": true,
"dependencies": {
"known-css-properties": "^0.28.0",
"known-css-properties": "^0.29.0",
"postcss-media-query-parser": "^0.2.3",
"postcss-resolve-nested-selector": "^0.1.1",
"postcss-selector-parser": "^6.0.13",
@ -4187,6 +4295,22 @@
"stylelint": "^14.5.1 || ^15.0.0"
}
},
"node_modules/stylelint-stylistic": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/stylelint-stylistic/-/stylelint-stylistic-0.4.3.tgz",
"integrity": "sha512-WphmneK3MRrm5ixvRPWy7+c9+EQUh0FPvNMXW/N9VD85vyqtpxUejpD+mxubVVht0fRgidcqBxtW3s3tU2Ujhw==",
"dev": true,
"dependencies": {
"is-plain-object": "^5.0.0",
"postcss": "^8.4.21",
"postcss-media-query-parser": "^0.2.3",
"postcss-value-parser": "^4.2.0",
"style-search": "^0.1.0"
},
"peerDependencies": {
"stylelint": "^15.0.0"
}
},
"node_modules/stylelint/node_modules/balanced-match": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",
@ -4199,6 +4323,18 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/stylelint/node_modules/file-entry-cache": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz",
"integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==",
"dev": true,
"dependencies": {
"flat-cache": "^3.2.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/stylelint/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
@ -4532,13 +4668,13 @@
}
},
"node_modules/which-typed-array": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
"integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
"integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
"dev": true,
"dependencies": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
"call-bind": "^1.0.4",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-tostringtag": "^1.0.0"

View file

@ -23,21 +23,26 @@
"eslint_fix": "eslint --fix --ext .js .",
"markdownlint": "markdownlint '**/*.md'",
"markdownlint_fix": "markdownlint --fix '**/*.md'",
"stylelint": "stylelint '**/*.css' && stylelint --custom-syntax postcss-scss '**/*.scss'",
"stylelint_fix": "stylelint --fix '**/*.css' && stylelint --fix --custom-syntax postcss-scss '**/*.scss'",
"rtlcss": "npm run symbolic && rtlcss -d symbolic/ && find -L symbolic/ -type f -name '*.rtl.rtl.css' -delete",
"stylelint": "stylelint '**/*.css'",
"stylelint_fix": "stylelint --fix '**/*.css'",
"symbolic": "rm -r symbolic && mkdir symbolic && find . -maxdepth 1 -type d -name 'xExtension-*' -exec ln -sf \"$(pwd)/{}\" ./symbolic/ \\;",
"test": "npm run eslint && npm run stylelint && npm run markdownlint",
"fix": "npm run rtlcss && npm run stylelint_fix && npm run eslint_fix && npm run markdownlint_fix"
},
"devDependencies": {
"eslint": "^8.51.0",
"eslint": "^8.54.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-n": "^16.0.1",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-n": "^16.3.1",
"eslint-plugin-promise": "^6.1.1",
"markdownlint-cli": "^0.37.0",
"sass": "^1.69.0",
"stylelint": "^15.10.3",
"stylelint-config-recommended-scss": "^13.0.0",
"stylelint-order": "^6.0.3"
}
"rtlcss": "^4.1.1",
"sass": "^1.69.5",
"stylelint": "^15.11.0",
"stylelint-config-recommended-scss": "^13.1.0",
"stylelint-order": "^6.0.3",
"stylelint-stylistic": "^0.4.3"
},
"rtlcssConfig": {}
}

39
phpstan.neon Normal file
View file

@ -0,0 +1,39 @@
parameters:
# TODO: Increase rule-level https://phpstan.org/user-guide/rule-levels
level: 1
treatPhpDocTypesAsCertain: false
fileExtensions:
- php
- phtml
paths:
- ../FreshRSS
- .
excludePaths:
analyse:
- ../FreshRSS
- vendor/
analyseAndScan:
- .git/
- node_modules/
- symbolic/
- tmp/
- xExtension-TTRSS_API/
dynamicConstantNames:
- TYPE_GIT
reportMaybesInPropertyPhpDocTypes: false
strictRules:
allRules: false
booleansInConditions: false # TODO pass
closureUsesThis: true
disallowedConstructs: false
disallowedLooseComparison: false
matchingInheritedMethodNames: true
noVariableVariables: false # TODO pass
numericOperandsInArithmeticOperators: true
overwriteVariablesWithLoop: true
requireParentConstructorCall: true
strictCalls: true
switchConditionsMatchingType: true
uselessCast: true
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon

7
tests/phpstan-next.txt Normal file
View file

@ -0,0 +1,7 @@
# List of files, which are not yet passing PHPStan level 9 https://phpstan.org/user-guide/rule-levels
# Used for automated tests to avoid regressions in files already passing that level.
# Can be regenerated with something like:
# find . -type d -name 'vendor' -prune -o -name '*.php' -exec sh -c 'vendor/bin/phpstan analyse --level 9 --memory-limit 512M {} >/dev/null 2>/dev/null || echo {}' \;
./xExtension-ImageProxy/configure.phtml
./xExtension-ImageProxy/extension.php

View file

@ -1,8 +1,11 @@
# FreshRSS Colorful List
Generate light different background color for article list rows (relying on the feed name)
# Installation
## Installation
To use it, upload the *xExtension-ColorfulList* folder in your ./extensions directory and enable it on the extension panel in FreshRSS.
# Preview
## Preview
![snapshot](snapshot.png)

View file

@ -1,8 +1,10 @@
<?php
class ColorfulListExtension extends Minz_Extension {
declare(strict_types=1);
public function init():void {
Minz_View::appendScript($this->getFileUrl('script.js', 'js'),'','','');
}
class ColorfulListExtension extends Minz_Extension
{
public function init(): void {
Minz_View::appendScript($this->getFileUrl('script.js', 'js'));
}
}

View file

@ -1,40 +1,38 @@
document.addEventListener('DOMContentLoaded', function(){
function monitorEntry(monitorCallback) {
const targetNode = document.getElementById('stream');
const config = { attributes: false, childList: true, subtree: false};
const callback = function(mutationsList, observer) {
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
monitorCallback(mutationsList);
}
}
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
//observer.disconnect();
};
monitorEntry(colorize);
document.addEventListener('DOMContentLoaded', function () {
function monitorEntry(monitorCallback) {
const targetNode = document.getElementById('stream');
const config = { attributes: false, childList: true, subtree: false };
const callback = function (mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
monitorCallback(mutationsList);
}
}
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
// observer.disconnect();
}
monitorEntry(colorize);
});
function colorize(entries){
let entry = document.querySelectorAll('.flux_header');
entry.forEach((e,i)=>{
let cl = stringToColour(e.querySelector('.website').textContent)+'12';
e.style.background=cl;
});
};
function colorize(entries) {
const entry = document.querySelectorAll('.flux_header');
entry.forEach((e, i) => {
const cl = stringToColour(e.querySelector('.website').textContent) + '12';
e.style.background = cl;
});
}
const stringToColour = (str) => {
let hash = 0;
str.split('').forEach(char => {
hash = char.charCodeAt(0) + ((hash << 5) - hash)
})
let color = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff
color += value.toString(16).padStart(2, '0')
}
return color;
let hash = 0;
str.split('').forEach(char => {
hash = char.charCodeAt(0) + ((hash << 5) - hash);
});
let color = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
color += value.toString(16).padStart(2, '0');
}
return color;
};

View file

@ -16,7 +16,7 @@ To use it, upload this directory in your `./extensions` directory and enable it
The following CSS rules let you have a more comfortable mobile view by hiding some icons (read/unread article, mark as favorite and RSS feed's favicon) and by reducing text size. It also displays the name of the subscribed feed, instead of the favicon:
```css
@media (max-width: 840px)
@media (max-width: 840px)
{
.flux_header .item.website
{
@ -28,8 +28,8 @@ The following CSS rules let you have a more comfortable mobile view by hiding so
{
display:none;
}
.flux_header .item.website span
{
display:inline;
@ -46,29 +46,29 @@ Desktop screen resolution:
Mobile screen resolution:
![Mobile](mobile_resolution.png)
![Mobile](mobile_resolution.png)
#### Getting rid of Top Menu Items
The Top Menu within the mobile view might look a little bit cluttered, depending on the theme. The following CSS rules allow to hide unneccessary top menu buttons or input boxes.
The Top Menu within the mobile view might look a little bit cluttered, depending on the theme. The following CSS rules allow to hide unnecessary top menu buttons or input boxes.
```css
@media (max-width: 840px)
@media (max-width: 840px)
{
/* Hides "Actions" Menu in Mobile View */
#nav_menu_actions {
display: none;
}
/* Hides "Views" Menu in Mobile View */
#nav_menu_views {
display: none;
}
/* Hides "Search" Input Box in Mobile View */
.nav_menu .item.search {
display: none;
}
/* Hides the Dropdown Menu Button next to the "Mark all read" Button in Mobile View */
#mark-read-menu .dropdown {
display: none;
@ -77,6 +77,7 @@ The Top Menu within the mobile view might look a little bit cluttered, depending
```
### Sidebar: Move the unread count to the right side of a feed
Some people prefer to have the unread count number of a feed on the right side after the feed's name, instead placing it between the favicon and the feeds name, as this is also the common location in other tools (e.g. e-mail inbox folder). Use this CSS code to move the number to the right side.
```css
.feed .item-title:not([data-unread="0"])::before {

View file

@ -1,19 +1,23 @@
<form action="<?php echo _url('extension', 'configure', 'e', urlencode($this->getName())); ?>" method="post">
<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
<?php
declare(strict_types=1);
/** @var CustomCSSExtension $this */
?>
<form action="<?= _url('extension', 'configure', 'e', urlencode($this->getName())); ?>" method="post">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<div class="form-group">
<label class="group-name" for="css-rules"><?php echo _t('ext.custom_css.write_css'); ?></label>
<label class="group-name" for="css-rules"><?= _t('ext.custom_css.write_css') ?></label>
<div class="group-controls">
<textarea name="css-rules" id="css-rules"><?php echo $this->css_rules; ?></textarea>
<textarea name="css-rules" id="css-rules"><?= $this->css_rules ?></textarea>
</div>
</div>
<div class="form-group form-actions">
<?php if (isset($this->permission_problem)) { ?>
<p class="alert alert-error"><?php echo _t('ext.custom_css.permission_problem', $this->permission_problem); ?></p>
<?php if (!empty($this->permission_problem)) { ?>
<p class="alert alert-error"><?= _t('ext.custom_css.permission_problem', $this->permission_problem) ?></p>
<?php } else { ?>
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
<button type="reset" class="btn"><?php echo _t('gen.action.cancel'); ?></button>
<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
</div>
<?php } ?>
</div>

View file

@ -3,11 +3,14 @@
declare(strict_types=1);
class CustomCSSExtension extends Minz_Extension {
public function init() {
public string $css_rules;
public string $permission_problem = '';
public function init(): void {
$this->registerTranslates();
$current_user = Minz_Session::paramString('currentUser');
$filename = 'style.' . $current_user . '.css';
$filename = 'style.' . $current_user . '.css';
$filepath = join_path($this->getPath(), 'static', $filename);
if (file_exists($filepath)) {
@ -15,28 +18,28 @@ class CustomCSSExtension extends Minz_Extension {
}
}
public function handleConfigureAction() {
public function handleConfigureAction(): void {
$this->registerTranslates();
$current_user = Minz_Session::paramString('currentUser');
$filename = 'style.' . $current_user . '.css';
$filename = 'style.' . $current_user . '.css';
$staticPath = join_path($this->getPath(), 'static');
$filepath = join_path($staticPath, $filename);
if (!file_exists($filepath) && !is_writable($staticPath)) {
$tmpPath = explode(EXTENSIONS_PATH . '/', $staticPath);
$this->permission_problem = $tmpPath[1] . '/';
} else if (file_exists($filepath) && !is_writable($filepath)) {
} elseif (file_exists($filepath) && !is_writable($filepath)) {
$tmpPath = explode(EXTENSIONS_PATH . '/', $filepath);
$this->permission_problem = $tmpPath[1];
} else if (Minz_Request::isPost()) {
} elseif (Minz_Request::isPost()) {
$css_rules = html_entity_decode(Minz_Request::paramString('css-rules'));
file_put_contents($filepath, $css_rules);
}
$this->css_rules = '';
if (file_exists($filepath)) {
$this->css_rules = htmlentities(file_get_contents($filepath));
$this->css_rules = htmlentities(file_get_contents($filepath) ?: '');
}
}
}

View file

@ -7,4 +7,4 @@ To use it, upload this directory in your `./extensions` directory and enable it
## Changelog
- 0.2 added file permission check and german translation
- 0.1 initial version
- 0.1 initial version

View file

@ -1,19 +1,23 @@
<form action="<?php echo _url('extension', 'configure', 'e', urlencode($this->getName())); ?>" method="post">
<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
<?php
declare(strict_types=1);
/** @var CustomJSExtension $this */
?>
<form action="<?= _url('extension', 'configure', 'e', urlencode($this->getName())) ?>" method="post">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<div class="form-group">
<label class="group-name" for="js-rules"><?php echo _t('ext.custom_js.write_js'); ?></label>
<label class="group-name" for="js-rules"><?= _t('ext.custom_js.write_js') ?></label>
<div class="group-controls">
<textarea name="js-rules" id="js-rules"><?php echo $this->js_rules; ?></textarea>
<textarea name="js-rules" id="js-rules"><?= $this->js_rules ?></textarea>
</div>
</div>
<div class="form-group form-actions">
<?php if (isset($this->permission_problem)) { ?>
<p class="alert alert-error"><?php echo _t('ext.custom_js.permission_problem', $this->permission_problem); ?></p>
<p class="alert alert-error"><?= _t('ext.custom_js.permission_problem', $this->permission_problem) ?></p>
<?php } else { ?>
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
<button type="reset" class="btn"><?php echo _t('gen.action.cancel'); ?></button>
<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
</div>
<?php } ?>
</div>

View file

@ -3,11 +3,14 @@
declare(strict_types=1);
class CustomJSExtension extends Minz_Extension {
public function init() {
public string $js_rules;
public string $permission_problem = '';
public function init(): void {
$this->registerTranslates();
$current_user = Minz_Session::paramString('currentUser');
$filename = 'script.' . $current_user . '.js';
$filename = 'script.' . $current_user . '.js';
$filepath = join_path($this->getPath(), 'static', $filename);
if (file_exists($filepath)) {
@ -15,28 +18,28 @@ class CustomJSExtension extends Minz_Extension {
}
}
public function handleConfigureAction() {
public function handleConfigureAction(): void {
$this->registerTranslates();
$current_user = Minz_Session::paramString('currentUser');
$filename = 'script.' . $current_user . '.js';
$filename = 'script.' . $current_user . '.js';
$staticPath = join_path($this->getPath(), 'static');
$filepath = join_path($staticPath, $filename);
if (!file_exists($filepath) && !is_writable($staticPath)) {
$tmpPath = explode(EXTENSIONS_PATH . '/', $staticPath);
$this->permission_problem = $tmpPath[1] . '/';
} else if (file_exists($filepath) && !is_writable($filepath)) {
} elseif (file_exists($filepath) && !is_writable($filepath)) {
$tmpPath = explode(EXTENSIONS_PATH . '/', $filepath);
$this->permission_problem = $tmpPath[1];
} else if (Minz_Request::isPost()) {
} elseif (Minz_Request::isPost()) {
$js_rules = html_entity_decode(Minz_Request::paramString('js-rules'));
file_put_contents($filepath, $js_rules);
}
$this->js_rules = '';
if (file_exists($filepath)) {
$this->js_rules = htmlentities(file_get_contents($filepath));
$this->js_rules = htmlentities(file_get_contents($filepath) ?: '');
}
}
}

View file

@ -31,7 +31,7 @@ The source code for the images.weserv.nl proxy can be found at [github.com/andri
In order to use Apache [mod_rewrite](https://httpd.apache.org/docs/current/mod/mod_rewrite.html), you will need to set the following settings:
* `proxy_url` = **https://www.example.org/proxy/**
* `proxy_url` = **<https://www.example.org/proxy/>**
* `scheme_include` = **1**
@ -39,7 +39,7 @@ In order to use Apache [mod_rewrite](https://httpd.apache.org/docs/current/mod/m
Along the following Apache configuration for the `www.example.org` virtual host:
```
```apache
# WARNING: Multiple '/' in %{REQUEST_URI} are internally trimmed to a single one!
RewriteCond %{REQUEST_URI} ^/proxy/https:/+(.*)$
RewriteRule ^ https://%1 [QSA,P,L]
@ -74,7 +74,7 @@ In order to use nginx's [proxy
module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html), you will
need to set the following settings:
* `proxy_url` = **https://www.example.org/proxy?key=changeme&url=**
* `proxy_url` = **<https://www.example.org/proxy?key=changeme&url=>**
* `scheme_include` = **1**
* `url_encode` = **0**

View file

@ -1,28 +1,32 @@
<form action="<?php echo _url('extension', 'configure', 'e', urlencode($this->getName())); ?>" method="post">
<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
<?php
declare(strict_types=1);
/** @var ImageProxyExtension $this */
?>
<form action="<?= _url('extension', 'configure', 'e', urlencode($this->getName())) ?>" method="post">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<div class="form-group">
<label class="group-name" for="image_proxy_url"><?php echo _t('ext.imageproxy.proxy_url'); ?></label>
<label class="group-name" for="image_proxy_url"><?= _t('ext.imageproxy.proxy_url') ?></label>
<div class="group-controls">
<input type="url" name="image_proxy_url" id="image_proxy_url" value="<?php echo FreshRSS_Context::$user_conf->image_proxy_url; ?>">
<input type="url" name="image_proxy_url" id="image_proxy_url" value="<?= FreshRSS_Context::$user_conf->image_proxy_url ?>">
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_scheme_http"><?php echo _t('ext.imageproxy.scheme_http'); ?></label>
<label class="group-name" for="image_proxy_scheme_http"><?= _t('ext.imageproxy.scheme_http') ?></label>
<div class="group-controls">
<input type="checkbox" name="image_proxy_scheme_http" id="image_proxy_scheme_http" value="1" <?php echo (FreshRSS_Context::$user_conf->image_proxy_scheme_http ? 'checked' : ''); ?>>
<input type="checkbox" name="image_proxy_scheme_http" id="image_proxy_scheme_http" value="1" <?= FreshRSS_Context::$user_conf->image_proxy_scheme_http ? 'checked' : '' ?>>
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_scheme_https"><?php echo _t('ext.imageproxy.scheme_https'); ?></label>
<label class="group-name" for="image_proxy_scheme_https"><?= _t('ext.imageproxy.scheme_https'); ?></label>
<div class="group-controls">
<input type="checkbox" name="image_proxy_scheme_https" id="image_proxy_scheme_https" value="1" <?php echo (FreshRSS_Context::$user_conf->image_proxy_scheme_https ? 'checked' : ''); ?>>
<input type="checkbox" name="image_proxy_scheme_https" id="image_proxy_scheme_https" value="1" <?= FreshRSS_Context::$user_conf->image_proxy_scheme_https ? 'checked' : '' ?>>
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_scheme_default"><?php echo _t('ext.imageproxy.scheme_default'); ?></label>
<label class="group-name" for="image_proxy_scheme_default"><?= _t('ext.imageproxy.scheme_default'); ?></label>
<div class="group-controls">
<select name="image_proxy_scheme_default" id="image_proxy_scheme_default">
<option value="<?php echo htmlentities(FreshRSS_Context::$user_conf->image_proxy_scheme_default); ?>" selected="selected"><?php echo htmlentities(FreshRSS_Context::$user_conf->image_proxy_scheme_default); ?></option>
<option value="<?= htmlentities(FreshRSS_Context::$user_conf->image_proxy_scheme_default ?? '') ?>" selected="selected"><?= htmlentities(FreshRSS_Context::$user_conf->image_proxy_scheme_default ?? '') ?></option>
<option value="-">-</option>
<option value="auto">auto</option>
<option value="http">http</option>
@ -31,22 +35,22 @@
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_scheme_include"><?php echo _t('ext.imageproxy.scheme_include'); ?></label>
<label class="group-name" for="image_proxy_scheme_include"><?= _t('ext.imageproxy.scheme_include'); ?></label>
<div class="group-controls">
<input type="checkbox" name="image_proxy_scheme_include" id="image_proxy_scheme_include" value="1" <?php echo (FreshRSS_Context::$user_conf->image_proxy_scheme_include ? 'checked' : ''); ?>>
<input type="checkbox" name="image_proxy_scheme_include" id="image_proxy_scheme_include" value="1" <?= FreshRSS_Context::$user_conf->image_proxy_scheme_include ? 'checked' : '' ?>>
</div>
</div>
<div class="form-group">
<label class="group-name" for="image_proxy_url_encode"><?php echo _t('ext.imageproxy.url_encode'); ?></label>
<label class="group-name" for="image_proxy_url_encode"><?= _t('ext.imageproxy.url_encode'); ?></label>
<div class="group-controls">
<input type="checkbox" name="image_proxy_url_encode" id="image_proxy_url_encode" value="1" <?php echo (FreshRSS_Context::$user_conf->image_proxy_url_encode ? 'checked' : ''); ?>>
<input type="checkbox" name="image_proxy_url_encode" id="image_proxy_url_encode" value="1" <?= FreshRSS_Context::$user_conf->image_proxy_url_encode ? 'checked' : '' ?>>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
<button type="reset" class="btn"><?php echo _t('gen.action.cancel'); ?></button>
<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
</div>
</div>
</form>

View file

@ -2,18 +2,23 @@
declare(strict_types=1);
class ImageProxyExtension extends Minz_Extension {
final class ImageProxyExtension extends Minz_Extension {
// Defaults
const PROXY_URL = 'https://images.weserv.nl/?url=';
const SCHEME_HTTP = '1';
const SCHEME_HTTPS = '';
const SCHEME_DEFAULT = 'auto';
const SCHEME_INCLUDE = '';
const URL_ENCODE = '1';
private const PROXY_URL = 'https://images.weserv.nl/?url=';
private const SCHEME_HTTP = '1';
private const SCHEME_HTTPS = '';
private const SCHEME_DEFAULT = 'auto';
private const SCHEME_INCLUDE = '';
private const URL_ENCODE = '1';
public function init() {
$this->registerHook('entry_before_display',
array('ImageProxyExtension', 'setImageProxyHook'));
public function init(): void {
if (FreshRSS_Context::$system_conf === null) {
throw new FreshRSS_Context_Exception('System configuration not initialised!');
}
$this->registerHook(
'entry_before_display',
array('ImageProxyExtension', 'setImageProxyHook')
);
// Defaults
$save = false;
if (is_null(FreshRSS_Context::$user_conf->image_proxy_url)) {
@ -50,7 +55,7 @@ class ImageProxyExtension extends Minz_Extension {
}
}
public function handleConfigureAction() {
public function handleConfigureAction(): void {
$this->registerTranslates();
if (Minz_Request::isPost()) {
@ -64,37 +69,36 @@ class ImageProxyExtension extends Minz_Extension {
}
}
public static function getProxyImageUri($url) {
public static function getProxyImageUri(string $url): string {
$parsed_url = parse_url($url);
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] : null;
if ($scheme === 'http') {
if (!FreshRSS_Context::$user_conf->image_proxy_scheme_http) return $url;
if (!FreshRSS_Context::$user_conf->image_proxy_scheme_http) {
return $url;
}
if (!FreshRSS_Context::$user_conf->image_proxy_scheme_include) {
$url = substr($url, 7); // http://
}
}
else if ($scheme === 'https') {
if (!FreshRSS_Context::$user_conf->image_proxy_scheme_https) return $url;
} elseif ($scheme === 'https') {
if (!FreshRSS_Context::$user_conf->image_proxy_scheme_https) {
return $url;
}
if (!FreshRSS_Context::$user_conf->image_proxy_scheme_include) {
$url = substr($url, 8); // https://
}
}
else if (empty($scheme)) {
} elseif (empty($scheme)) {
if (FreshRSS_Context::$user_conf->image_proxy_scheme_default === 'auto') {
if (FreshRSS_Context::$user_conf->image_proxy_scheme_include) {
$url = ((!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS'])!== 'off') ? 'https:' : 'http:') . $url;
$url = ((!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') ? 'https:' : 'http:') . $url;
}
}
else if (substr(FreshRSS_Context::$user_conf->image_proxy_scheme_default, 0, 4) === 'http') {
} elseif (substr(FreshRSS_Context::$user_conf->image_proxy_scheme_default, 0, 4) === 'http') {
if (FreshRSS_Context::$user_conf->image_proxy_scheme_include) {
$url = FreshRSS_Context::$user_conf->image_proxy_scheme_default . ':' . $url;
}
}
else { // do not proxy unschemed ("//path/...") URLs
} else { // do not proxy unschemed ("//path/...") URLs
return $url;
}
}
else { // unknown/unsupported (non-http) scheme
} else { // unknown/unsupported (non-http) scheme
return $url;
}
if (FreshRSS_Context::$user_conf->image_proxy_url_encode) {
@ -103,11 +107,16 @@ class ImageProxyExtension extends Minz_Extension {
return FreshRSS_Context::$user_conf->image_proxy_url . $url;
}
public static function getSrcSetUris($matches) {
return str_replace($matches[1], self::getProxyImageUri($matches[1]), $matches[0]);
/**
* @param array<string> $matches
* @return array<string>
*/
public static function getSrcSetUris(array $matches): array {
$result = str_replace($matches[1], self::getProxyImageUri($matches[1]), $matches[0]);
return is_array($result) ? $result : [$result];
}
public static function swapUris($content) {
public static function swapUris(string $content): string {
if (empty($content)) {
return $content;
}
@ -122,15 +131,15 @@ class ImageProxyExtension extends Minz_Extension {
$img->setAttribute('src', $newSrc);
}
if ($img->hasAttribute('srcset')) {
$newSrcSet = preg_replace_callback('/(?:([^\s,]+)(\s*(?:\s+\d+[wx])(?:,\s*)?))/', 'self::getSrcSetUris', $img->getAttribute('srcset'));
$newSrcSet = preg_replace_callback('/(?:([^\s,]+)(\s*(?:\s+\d+[wx])(?:,\s*)?))/', fn($matches) => self::getSrcSetUris($matches), $img->getAttribute('srcset'));
$img->setAttribute('srcset', $newSrcSet);
}
}
return $doc->saveHTML();
return $doc->saveHTML() ?: '';
}
public static function setImageProxyHook($entry) {
public static function setImageProxyHook(FreshRSS_Entry $entry): FreshRSS_Entry {
$entry->_content(
self::swapUris($entry->content())
);

View file

@ -1,14 +1,22 @@
<?php
class FreshExtension_quickCollapse_Controller extends Minz_ActionController {
public function jsVarsAction() {
/** @var QuickCollapse\View */
protected $view;
public function __construct() {
parent::__construct(QuickCollapse\View::class);
}
public function jsVarsAction(): void {
$extension = Minz_ExtensionManager::findExtension('Quick Collapse');
$this->view->icon_url_in = $extension->getFileUrl('in.svg', 'svg');
$this->view->icon_url_out = $extension->getFileUrl('out.svg', 'svg');
if ($extension !== null) {
$this->view->icon_url_in = $extension->getFileUrl('in.svg', 'svg');
$this->view->icon_url_out = $extension->getFileUrl('out.svg', 'svg');
}
$this->view->i18n_toggle_collapse = _t('gen.js.toggle_collapse');
$this->view->_layout(false);
$this->view->_layout(null);
$this->view->_path('quickCollapse/vars.js');
header('Content-Type: application/javascript');
}

View file

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace QuickCollapse;
class View extends \Minz_View {
public string $icon_url_in = '';
public string $icon_url_out = '';
public string $i18n_toggle_collapse = '';
}

View file

@ -3,13 +3,13 @@
declare(strict_types=1);
class QuickCollapseExtension extends Minz_Extension {
public function init() {
public function init(): void {
$this->registerTranslates();
$this->registerViews();
$this->registerController('quickCollapse');
Minz_View::appendStyle($this->getFileUrl('style.css', 'css'));
Minz_View::appendScript(_url('quickCollapse', 'jsVars'), false, true, false);
Minz_View::appendScript(_url('quickCollapse', 'jsVars') ?: '', false, true, false);
Minz_View::appendScript($this->getFileUrl('script.js', 'js'), false, true, false);
}
}

View file

@ -1,11 +1,11 @@
#toggle-collapse .uncollapse {
display: none;
display: none;
}
#toggle-collapse.collapsed .collapse {
display: none;
display: none;
}
#toggle-collapse.collapsed .uncollapse {
display: inherit;
display: inherit;
}

View file

@ -0,0 +1,11 @@
#toggle-collapse .uncollapse {
display: none;
}
#toggle-collapse.collapsed .collapse {
display: none;
}
#toggle-collapse.collapsed .uncollapse {
display: inherit;
}

View file

@ -1,6 +1,6 @@
/* exported quick_collapse_vars */
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line no-unused-vars, no-var
var quick_collapse_vars = {
icon_url_in: '<?= $this->icon_url_in ?>',
icon_url_out: '<?= $this->icon_url_out ?>',

View file

@ -1,8 +1,8 @@
Extension pour FreshRSS (https://github.com/FreshRSS)
Extension pour FreshRSS (<https://github.com/FreshRSS>)
**v1.3**
> **v1.3**
Source: https://framagit.org/Lapineige/FreshRSS_Extension-ReadingTime
Source: <https://framagit.org/Lapineige/FreshRSS_Extension-ReadingTime>
Ajoute une estimation du temps de lecture à côté de chaque article.
Fonctionne sur les affichages de bureau et mobile.
@ -11,24 +11,22 @@ S'installe comme toute les extensions, soit via l'outil intégré dans l'interfa
Aucune module externe. Une fois activée dans les préférences, l'extension doit fonctionner après avoir recharger la page.
Un indicateur du temps de lecture doit s'afficher dans le nom du flux de chaque article.
Pour le moment la vitesse de lecture est réglée manuellement à 300 mots/min. À changer si besoin ici: https://framagit.org/Lapineige/FreshRSS_Extension-ReadingTime/blob/master/static/readingtime.js#L26
Pour le moment la vitesse de lecture est réglée manuellement à 300 mots/min. À changer si besoin ici: <https://framagit.org/Lapineige/FreshRSS_Extension-ReadingTime/blob/master/static/readingtime.js#L26>
---
Extension for FressRSS (https://github.com/FreshRSS)
Extension for FressRSS (<https://github.com/FreshRSS>)
**v1.3**
> **v1.3**
Source: https://framagit.org/Lapineige/FreshRSS_Extension-ReadingTime
Source: <https://framagit.org/Lapineige/FreshRSS_Extension-ReadingTime>
Add a reading time estimation next to each article.
Works on both desktop and mobile displays.
Install it the same way as any other extension, with the integrated tool in the interface (parameters icon -> extensions) or manualy by copying this repository directly in the `extensions` forlder of your FreshRSS install.
Install it the same way as any other extension, with the integrated tool in the interface (parameters icon -> extensions) or manually by copying this repository directly in the `extensions` folder of your FreshRSS install.
No external module. Once activated in the preferences, this extension should be working after reloading the page.
A reading time
For the moment the reading speed is manualy set to 300 words/min. If necessary, change it there: https://framagit.org/Lapineige/FreshRSS_Extension-ReadingTime/blob/master/static/readingtime.js#L26
For the moment the reading speed is manually set to 300 words/min. If necessary, change it there: <https://framagit.org/Lapineige/FreshRSS_Extension-ReadingTime/blob/master/static/readingtime.js#L26>

View file

@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
class ReadingTimeExtension extends Minz_Extension {
public function init() {
public function init(): void {
Minz_View::appendScript($this->getFileUrl('readingtime.js', 'js'));
}
}

View file

@ -20,14 +20,14 @@
reading_time.flux = flux_list[i];
reading_time.words_count = reading_time.flux_words_count(flux_list[i]); // count the words
// change this number (in words) to your prefered reading speed:
// change this number (in words) to your preferred reading speed:
reading_time.reading_time = reading_time.calc_read_time(reading_time.words_count, 300);
flux_list[i].dataset.readingTime = reading_time.reading_time;
const li = document.createElement('li');
li.setAttribute('class', 'item date');
li.style.width = "40px";
li.style.width = '40px';
li.style.overflow = 'hidden';
li.style.textAlign = 'right';
li.style.display = 'table-cell';

View file

@ -1,34 +1,51 @@
<?php
declare(strict_types=1);
class FreshExtension_shareByEmail_Controller extends Minz_ActionController {
public function init() {
public ?Minz_Extension $extension;
/** @var ShareByEmail\mailers\View */
protected $view;
public function __construct() {
parent::__construct(ShareByEmail\mailers\View::class);
}
public function init(): void {
$this->extension = Minz_ExtensionManager::findExtension('Share By Email');
}
public function shareAction() {
public function shareAction(): void {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
$id = Minz_Request::param('id', null);
if (null === $id) {
$id = Minz_Request::paramString('id');
if ($id === '') {
Minz_Error::error(404);
}
$entryDAO = FreshRSS_Factory::createEntryDao();
$entry = $entryDAO->searchById($id);
if (null === $entry) {
if ($entry === null) {
Minz_Error::error(404);
}
$this->view->entry = $entry;
$username = Minz_Session::param('currentUser', '_');
if (FreshRSS_Context::$system_conf === null) {
throw new FreshRSS_Context_Exception('System configuration not initialised!');
}
$username = Minz_Session::paramString('currentUser') ?: '_';
$service_name = FreshRSS_Context::$system_conf->title;
$service_url = FreshRSS_Context::$system_conf->base_url;
Minz_View::prependTitle(_t('shareByEmail.share.title') . ' · ');
Minz_View::appendStyle($this->extension->getFileUrl('shareByEmail.css', 'css'));
if ($this->extension !== null) {
Minz_View::appendStyle($this->extension->getFileUrl('shareByEmail.css', 'css'));
}
$this->view->_layout('simple');
$this->view->entry = $entry;
$this->view->to = '';
$this->view->subject = _t('shareByEmail.share.form.subject_default');
$this->view->content = _t(
@ -41,9 +58,9 @@ class FreshExtension_shareByEmail_Controller extends Minz_ActionController {
);
if (Minz_Request::isPost()) {
$this->view->to = $to = Minz_Request::param('to', '');
$this->view->subject = $subject = Minz_Request::param('subject', '');
$this->view->content = $content = Minz_Request::param('content', '');
$this->view->to = $to = Minz_Request::paramString('to');
$this->view->subject = $subject = Minz_Request::paramString('subject');
$this->view->content = $content = Minz_Request::paramString('content');
if ($to == "" || $subject == "" || $content == "") {
Minz_Request::bad(_t('shareByEmail.share.feedback.fields_required'), [

View file

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace ShareByEmail\mailers;
class View extends \Minz_View {
public ?\FreshRSS_Entry $entry = null;
public string $content = '';
public string $subject = '';
public string $to = '';
}

View file

@ -3,7 +3,8 @@
declare(strict_types=1);
class ShareByEmailExtension extends Minz_Extension {
public function init() {
public function init(): void {
$this->registerTranslates();
$this->registerController('shareByEmail');
@ -20,7 +21,7 @@ class ShareByEmailExtension extends Minz_Extension {
spl_autoload_register(array($this, 'loader'));
}
public function loader($class_name) {
public function loader(string $class_name): void {
if (strpos($class_name, 'ShareByEmail') === 0) {
$class_name = substr($class_name, 13);
$base_path = $this->getPath() . '/';

View file

@ -1,14 +1,28 @@
<?php
declare(strict_types=1);
namespace ShareByEmail\mailers;
class Share extends \Minz_Mailer {
public function send_article($to, $subject, $content) {
$this->view->_path('share_mailer/article.txt');
final class Share extends \Minz_Mailer {
/** @var View */
protected $view;
public function __construct() {
parent::__construct(View::class);
}
public function send_article(string $to, string $subject, string $content): bool {
$this->view->_path('share_mailer/article.txt.php');
$this->view->content = $content;
$subject_prefix = '[' . \FreshRSS_Context::$system_conf->title . ']';
if (isset(\FreshRSS_Context::$system_conf)) {
$subject_prefix = '[' . \FreshRSS_Context::$system_conf->title . ']';
} else {
$subject_prefix = '';
}
return $this->mail($to, $subject_prefix . ' ' . $subject);
}
}

View file

@ -1,4 +1,4 @@
.sbe-form-share textarea {
width: 600px;
height: 300px;
width: 600px;
height: 300px;
}

View file

@ -0,0 +1,4 @@
.sbe-form-share textarea {
width: 600px;
height: 300px;
}

View file

@ -1,12 +1,16 @@
<?php
declare(strict_types=1);
/** @var ShareByEmail\mailers\View $this */
?>
<div class="post">
<h1><?= _t('shareByEmail.share.title') ?></h1>
<p>
<?= _t('shareByEmail.share.intro', $this->entry->title()) ?>
<?= _t('shareByEmail.share.intro', isset($this->entry) ? $this->entry->title() : '') ?>
</p>
<form class="sbe-form-share" method="POST">
<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<div class="form-group">
<label class="group-name" for="to">

View file

@ -1 +0,0 @@
<?= $this->content ?>

View file

@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
/** @var ShareByEmail\mailers\View $this */
echo $this->content;

View file

@ -4,6 +4,6 @@ A FreshRSS extension which set the feed aside in the main stream following the w
To use it, upload this directory in your `./extensions` directory and enable it on the extension panel in FreshRSS.
# Deprecated
## Deprecated
This function is already implemented into FreshRSS.

View file

@ -3,7 +3,7 @@
declare(strict_types=1);
class StickyFeedsExtension extends Minz_Extension {
public function init() {
public function init(): void {
Minz_View::appendStyle($this->getFileUrl('style.css', 'css'));
Minz_View::appendScript($this->getFileUrl('script.js', 'js'));
}

View file

@ -10,7 +10,7 @@
overflow-y: auto;
}
@media(max-width: 840px) {
@media (max-width: 840px) {
/* No effect on mobile. And yep, under 840px it is a mobile... a big one. */
#aside_feed.sticky .tree {
position: static;

View file

@ -0,0 +1,18 @@
#aside_feed.sticky {
position: relative;
}
#aside_feed.sticky .tree {
position: absolute;
right: 0;
width: 100%;
margin-top: 0;
overflow-y: auto;
}
@media (max-width: 840px) {
/* No effect on mobile. And yep, under 840px it is a mobile... a big one. */
#aside_feed.sticky .tree {
position: static;
}
}

View file

@ -6,6 +6,6 @@ Note the API is NOT FULLY SUPPORTED YET!
Please be sure to enable API in the configuration and set an API password on your profile.
URL to use is something like http://rss.example.com/api/ttrss.php or http://example.com/freshrss/p/api/ttrss.php.
URL to use is something like <http://rss.example.com/api/ttrss.php> or <http://example.com/freshrss/p/api/ttrss.php>.
To use it, upload this directory in your `./extensions` directory and enable it on the extension panel in FreshRSS.

View file

@ -1,11 +1,12 @@
<?php
declare(strict_types=1);
class TTRSS_APIExtension extends Minz_Extension {
public function init() {
$this->registerHook('post_update',
array($this, 'postUpdateHook'));
$this->registerHook(
'post_update',
array($this, 'postUpdateHook')
);
}
public function install() {

View file

@ -339,7 +339,7 @@ class FreshAPI_TTRSS {
if ($show_excerpt) {
// @todo add a facultative max char in content method to get
// an exerpt.
// an excerpt.
$line['excerpt'] = $item->content();
}

View file

@ -9,4 +9,4 @@ The css code used to wrap long titles was originally [proposed](https://github.c
## Changelog
- 0.1 initial version
- 0.1 initial version

View file

@ -3,7 +3,7 @@
declare(strict_types=1);
class TitleWrapExtension extends Minz_Extension {
public function init() {
Minz_View::appendStyle($this->getFileUrl('title_wrap.css', 'css'));
public function init(): void {
Minz_View::appendStyle($this->getFileUrl('title_wrap.css', 'css'));
}
}

View file

@ -1,21 +1,26 @@
.horizontal-list {
display: flex;
display: flex;
}
.horizontal-list.bottom {
display: table;
display: table;
}
.flux .item {
flex-shrink: 0;
line-height: normal;
padding: .3em 0;
flex-shrink: 0;
line-height: normal;
padding: .3em 0;
}
.flux .item > a {
white-space: normal;
white-space: normal;
}
.flux:not(.current):hover .item.title {
position: relative;
max-width: inherit;
position: relative;
max-width: inherit;
}
.flux_header .title {
flex: auto;
}
flex: auto;
}

View file

@ -0,0 +1,26 @@
.horizontal-list {
display: flex;
}
.horizontal-list.bottom {
display: table;
}
.flux .item {
flex-shrink: 0;
line-height: normal;
padding: .3em 0;
}
.flux .item > a {
white-space: normal;
}
.flux:not(.current):hover .item.title {
position: relative;
max-width: inherit;
}
.flux_header .title {
flex: auto;
}

View file

@ -1,6 +1,6 @@
Extension for FressRSS (https://github.com/FreshRSS)
Extension for FressRSS (<https://github.com/FreshRSS>)
It helps to find the feed IDs of each feed.
It helps to find the feed IDs of each feed.
Feed IDs are used f.e. for the search or user queries.

View file

@ -3,7 +3,7 @@
declare(strict_types=1);
class ShowFeedIdExtension extends Minz_Extension {
public function init() {
public function init(): void {
Minz_View::appendScript($this->getFileUrl('showfeedid.js', 'js'));
}
}

View file

@ -1,6 +1,6 @@
'use strict';
const url = new URL(window.location);
const url = new URL(window.location);
if (url.searchParams.get('c') === 'subscription') {
const div = document.querySelector('h1 ~ div');
const button = document.createElement('Button');