diff --git a/.all-contributorsrc b/.all-contributorsrc index 710b62c7..f0018ca9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3,17 +3,15 @@ "projectOwner": "adaures", "repoType": "gitlab", "repoHost": "https://code.castopod.org", - "files": ["README.md", "docs/src/content/docs/en/index.mdx"], + "files": ["README.md"], "imageSize": 100, "commit": false, - "contributorsPerLine": 7, - "wrapperTemplate": "\n\n <%= bodyContent %> \n<%= tableFooterContent %>
\n\n", "contributors": [ { "login": "yassinedoghri", "name": "Yassine Doghri", - "avatar_url": "https://code.castopod.org/uploads/-/system/user/avatar/3/avatar.png", - "profile": "https://github.com/yassinedoghri", + "avatar_url": "https://avatars.githubusercontent.com/u/11021441?v=4", + "profile": "https://yassinedoghri.com", "contributions": [ "code", "bug", @@ -472,18 +470,6 @@ } ] }, - { - "login": "ghose", - "name": "ghose (XoseM)", - "avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/12617257/large/a201650da44fed28890b0e0d8477a663.jpg", - "profile": "https://crowdin.com/profile/xosem", - "contributions": [ - { - "type": "translation", - "url": "https://translate.castopod.org" - } - ] - }, { "login": "basen1982", "name": "Andreas Olsson", @@ -545,7 +531,7 @@ ] }, { - "login": "ahmed.sabouni11", + "login": "ahmedsabouni", "name": "Ahmed Sabouni", "avatar_url": "https://avatars.githubusercontent.com/u/74497842?v=4", "profile": "https://github.com/ahmedsabouni", @@ -564,11 +550,25 @@ "contributions": ["code"] }, { - "login": "NeoluxConsulting", + "login": "Dwev", "name": "Guy Martin", - "avatar_url": "https://secure.gravatar.com/avatar/6e745565356330c1e29a85d52bffdaa1?s=80&d=identicon", - "profile": "https://code.castopod.org/NeoluxConsulting", + "avatar_url": "https://avatars.githubusercontent.com/u/46626050?v=4", + "profile": "https://github.com/Dwev", "contributions": ["bug", "code"] + }, + { + "login": "prcutler", + "name": "Paul Cutler", + "avatar_url": "https://avatars.githubusercontent.com/u/67276?v=4", + "profile": "https://github.com/prcutler", + "contributions": ["doc", "question", "ideas"] + }, + { + "login": "nateritter", + "name": "Nate Ritter", + "avatar_url": "https://avatars.githubusercontent.com/u/198798?v=4", + "profile": "https://github.com/nateritter", + "contributions": ["code"] } ], "commitConvention": "none" diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4da0ceaa..cca42fb0 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -4,7 +4,7 @@ # ⚠️ NOT optimized for production # should be used only for development purposes #--------------------------------------------------- -FROM php:8.2-fpm +FROM php:8.5-fpm LABEL maintainer="Yassine Doghri " @@ -47,11 +47,4 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ && docker-php-ext-enable redis \ # mysqli for database access && docker-php-ext-install mysqli \ - && docker-php-ext-enable mysqli \ - # configure php - && echo "file_uploads = On\n" \ - "memory_limit = 512M\n" \ - "upload_max_filesize = 500M\n" \ - "post_max_size = 512M\n" \ - "max_execution_time = 300\n" \ - > /usr/local/etc/php/conf.d/uploads.ini + && docker-php-ext-enable mysqli diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3673a17b..f1c12034 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "postCreateCommand": "composer install && pnpm install && pnpm run build:static && php spark migrate --all && php spark db:seed DevSeeder", - "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && crontab .devcontainer/crontab && cron && php spark serve --host 0.0.0.0", + "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && crontab .devcontainer/crontab && cron && php spark serve --host 0.0.0.0 --port ${APP_PORT:-8080}", "postAttachCommand": "crontab .devcontainer/crontab && service cron reload", "shutdownAction": "stopCompose", "features": { @@ -30,7 +30,17 @@ "spark": "php", "env": "dotenv", ".rsync-filter": "diff" - } + }, + "json.schemas": [ + { + "fileMatch": [ + "plugins/**/manifest.json", + "tests/modules/Plugins/mocks/manifests/*.json", + "tests/modules/Plugins/mocks/plugins/**/manifest.json" + ], + "url": "/workspaces/castopod/modules/Plugins/Manifest/manifest.schema.json" + } + ] }, "extensions": [ "astro-build.astro-vscode", @@ -52,7 +62,8 @@ "stylelint.vscode-stylelint", "unifiedjs.vscode-mdx", "wayou.vscode-todo-highlight", - "yzhang.markdown-all-in-one" + "yzhang.markdown-all-in-one", + "42Crunch.vscode-openapi" ] } } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 5aa15231..665b78e9 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,20 +1,19 @@ -version: "3" - services: app: build: context: . dockerfile: Dockerfile - ports: - - 8080:8080 volumes: - ../..:/workspaces:cached + - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini environment: + APP_PORT: ${APP_PORT:-8080} # used in devcontainer.json file + VITE_PORT: ${VITE_PORT:-5173} # used in ../vite.config.js file CI_ENVIRONMENT: development vite_environment: development app_forceGlobalSecureRequests: 0 #false - app_baseURL: http://localhost:8080/ - media_baseURL: http://localhost:8080/ + app_baseURL: http://localhost:${APP_PORT:-8080}/ + media_baseURL: http://localhost:${APP_PORT:-8080}/ admin_gateway: cp-admin auth_gateway: cp-auth analytics_salt: dev_analytics_salt @@ -29,16 +28,10 @@ services: email_SMTPHost: mailpit email_SMTPUser: castopod email_SMTPPass: castopod - email_SMTPPort: 1025 + email_SMTPPort: ${MAILPIT_SMTP_PORT:-1025} depends_on: - - redis - mariadb - redis: - image: redis:alpine - volumes: - - redis:/data - mariadb: image: mariadb:10.2 volumes: @@ -69,8 +62,8 @@ services: volumes: - mailpit:/data ports: - - 8025:8025 - - 1025:1025 + - ${MAILPIT_WEBUI_PORT:-8025}:8025 + - ${MAILPIT_SMTP_PORT:-1025}:1025 environment: MP_MAX_MESSAGES: 5000 MP_DATA_FILE: /data/mailpit.db @@ -78,7 +71,6 @@ services: MP_SMTP_AUTH_ALLOW_INSECURE: 1 volumes: - redis: mariadb: phpmyadmin: mailpit: diff --git a/.devcontainer/uploads.ini b/.devcontainer/uploads.ini new file mode 100644 index 00000000..23b3c1cd --- /dev/null +++ b/.devcontainer/uploads.ini @@ -0,0 +1,5 @@ +file_uploads = On +memory_limit = 512M +upload_max_filesize = 500M +post_max_size = 512M +max_execution_time = 300 diff --git a/.gitignore b/.gitignore index 759dd2f9..b7ea048c 100644 --- a/.gitignore +++ b/.gitignore @@ -179,3 +179,9 @@ modules/Admin/Language/*/PersonsTaxonomy.php castopod/ castopod-*.zip castopod-*.tar.gz + +# Plugins +plugins/* +!plugins/.gitkeep +writable/plugins.json +writable/plugins-lock.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b10a5b9b..32076043 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: code.castopod.org:5050/adaures/castopod:ci-php8.1 +image: code.castopod.org:5050/adaures/castopod:ci-php8.5 stages: - prepare @@ -94,14 +94,14 @@ lint-js: tests: stage: quality services: - - mariadb:10.2 + - mariadb:10.11 variables: MYSQL_ROOT_PASSWORD: "R00Tp4ssW0RD" MYSQL_DATABASE: "test" MYSQL_USER: "castopod" MYSQL_PASSWORD: "castopod" script: - - echo "SHOW DATABASES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mariadb "$MYSQL_DATABASE" --skip-ssl + - echo "SHOW DATABASES;" | mariadb --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mariadb "$MYSQL_DATABASE" --skip_ssl # run phpunit without code coverage # TODO: add code coverage @@ -135,7 +135,7 @@ bundle: rules: - if: $CI_PROJECT_NAMESPACE != "adaures" when: never - - if: $CI_COMMIT_BRANCH =~ /^(main|beta|alpha|next)$/ || $CI_COMMIT_TAG + - if: $CI_COMMIT_BRANCH =~ /^(main|alpha|beta|next)$/ || $CI_COMMIT_TAG when: never - when: on_success diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md index 7fec749a..c6041a61 100644 --- a/.gitlab/issue_templates/bug.md +++ b/.gitlab/issue_templates/bug.md @@ -6,7 +6,7 @@ 1. [First step] 2. [Second step] -3. [and so on...] +3. [and so on…] ### Expected behavior @@ -27,7 +27,7 @@ logs, and code as it's very hard to read otherwise. - OS: [e.g. Ubuntu server] - Browser: [e.g. chrome, safari] - Web server: [eg. Apache] -- [any other relevant context...] +- [any other relevant context…] ### Possible fixes diff --git a/.gitlab/issue_templates/feature-request.md b/.gitlab/issue_templates/feature-request.md index 644a8fd4..0886d392 100644 --- a/.gitlab/issue_templates/feature-request.md +++ b/.gitlab/issue_templates/feature-request.md @@ -1,7 +1,7 @@ ### Is your feature request related to a problem? Please describe A clear and concise description of what the problem is. Ex. I'm always -frustrated when [...] +frustrated when […] ### Describe the solution you'd like diff --git a/.prettierrc.json b/.prettierrc.json index d567a64c..cae76d94 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,7 +2,7 @@ "trailingComma": "es5", "overrides": [ { - "files": "*.md", + "files": ["*.md", "*.mdx"], "options": { "proseWrap": "always" } diff --git a/.releaserc.json b/.releaserc.json index 049e4b2d..7a8b6584 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -8,6 +8,10 @@ { "name": "beta", "prerelease": true + }, + { + "name": "next", + "prerelease": true } ], "plugins": [ diff --git a/.rsync-filter b/.rsync-filter index 6acbba79..0fcb79ec 100644 --- a/.rsync-filter +++ b/.rsync-filter @@ -4,6 +4,7 @@ + resources/ + app/*** + modules/*** ++ plugins/*** + public/*** + themes/*** + vendor/*** diff --git a/.stylelintrc.json b/.stylelintrc.json index b9a5630c..8ea025c1 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -10,10 +10,17 @@ "responsive", "variants", "screen", - "layer" + "layer", + "config" ] } ], + "at-rule-no-deprecated": [ + true, + { + "ignoreAtRules": ["apply"] + } + ], "function-no-unknown": [ true, { diff --git a/CHANGELOG.md b/CHANGELOG.md index f99eef1e..29f4a170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,234 +1,107 @@ -## [1.15.5](https://code.castopod.org/adaures/castopod/compare/v1.15.4...v1.15.5) (2026-02-24) - -### Bug Fixes - -- **docker:** add arch-specific supercronic and s6-overlay services - ([243ce3a](https://code.castopod.org/adaures/castopod/commit/243ce3a45c740352841bf67fe1ff63151f276eb9)), - closes [#580](https://code.castopod.org/adaures/castopod/issues/580) - -## [1.15.4](https://code.castopod.org/adaures/castopod/compare/v1.15.3...v1.15.4) (2026-02-19) - -### Bug Fixes - -- **emails:** display verification link in clear text for email clients only - displaying text - ([37c89df](https://code.castopod.org/adaures/castopod/commit/37c89df3424e4f67fb05c09094560df7edbd76d4)), - closes [#328](https://code.castopod.org/adaures/castopod/issues/328) - -## [1.15.3](https://code.castopod.org/adaures/castopod/compare/v1.15.2...v1.15.3) (2026-02-19) - -### Bug Fixes - -- **bundle:** include resources folder for icons during rsync - ([70ca0c7](https://code.castopod.org/adaures/castopod/commit/70ca0c7928cabb99a07de01cb4e12f5d81689bbe)), - closes [#588](https://code.castopod.org/adaures/castopod/issues/588) - -## [1.15.2](https://code.castopod.org/adaures/castopod/compare/v1.15.1...v1.15.2) (2026-02-18) - -### Bug Fixes - -- **player:** load icons locally instead of relying on vimejs picking them from - third party scripts - ([0961987](https://code.castopod.org/adaures/castopod/commit/096198727627b3dba9c5bd28f20e90cff680316b)), - closes [#551](https://code.castopod.org/adaures/castopod/issues/551) - -## [1.15.1](https://code.castopod.org/adaures/castopod/compare/v1.15.0...v1.15.1) (2026-02-17) - -### Bug Fixes - -- **docker:** create optimized builder with docker-container driver for arm64 - builds - ([89ae2b8](https://code.castopod.org/adaures/castopod/commit/89ae2b89fd20fa31851a31fe44f514294fbcd688)), - closes [#580](https://code.castopod.org/adaures/castopod/issues/580) - -## [1.15.0](https://code.castopod.org/adaures/castopod/compare/v1.14.1...v1.15.0) (2026-02-16) - -### Features - -- **docker:** replace all-in-one image with FrankenPHP and Caddy based image + - discard other images - ([14089f0](https://code.castopod.org/adaures/castopod/commit/14089f0542ccdf187bd64bea8ad2787b9e8c7d59)) - -## 1.14.1 (2026-01-31) - -- fix(i18n): set english as first item in supported locales in case locale - negotiation does not work - ([44fb904](https://code.castopod.org/adaures/castopod/commit/44fb904)) - -## 1.14.0 (2026-01-23) - -- feat: add Lithuanian and Czech languages - ([9582f2a](https://code.castopod.org/adaures/castopod/commit/9582f2a)) - -## 1.13.8 (2025-12-20) - -- chore: update CI4 to v4.6.4 + php and js packages to latest - ([133e308](https://code.castopod.org/adaures/castopod/commit/133e308)) -- fix(fediverse): match episode posts replies fields with comments in union - query ([d438190](https://code.castopod.org/adaures/castopod/commit/d438190)), - closes [#577](https://code.castopod.org/adaures/castopod/issues/577) - -## 1.13.7 (2025-11-03) - -- fix(rss): set person's avatar url to "federation" for width and height of - 400px ([a50b0f3](https://code.castopod.org/adaures/castopod/commit/a50b0f3)) - -## 1.13.6 (2025-11-03) - -- fix(fediverse): access to URI in 'object' instead of going down with '->id' in - delete case - ([41211a1](https://code.castopod.org/adaures/castopod/commit/41211a1)) -- build: update docker images' versions + docs to latest - ([07e3a9c](https://code.castopod.org/adaures/castopod/commit/07e3a9c)) - -## 1.13.5 (2025-08-25) - -- chore: add discourse social network - ([08a3779](https://code.castopod.org/adaures/castopod/commit/08a3779)) -- chore: fix rector issues with filters' methods return types - ([3d7969d](https://code.castopod.org/adaures/castopod/commit/3d7969d)) -- chore: update .releaserc to include more detailed release notes - ([5b4403e](https://code.castopod.org/adaures/castopod/commit/5b4403e)) -- chore: update CI to v4.6.3 + all php and js dependencies - ([842c4e4](https://code.castopod.org/adaures/castopod/commit/842c4e4)) -- fix(episodes): set dropdown menu for seasons / years to a maximum height with - auto scroll - ([f88abd2](https://code.castopod.org/adaures/castopod/commit/f88abd2)) -- fix(fediverse): add is_private field to posts to flag private posts and hide - them from public views - ([d5ef2ab](https://code.castopod.org/adaures/castopod/commit/d5ef2ab)) - -## [1.13.4](https://code.castopod.org/adaures/castopod/compare/v1.13.3...v1.13.4) (2025-02-24) - -### Bug Fixes - -- **platforms:** add podcast id when deleting platforms on save - ([019fbaf](https://code.castopod.org/adaures/castopod/commit/019fbaf74ddd7427c3b7dfaef0d2e4409aab0e7c)) -- return method instead of throwing a 404 when submitting a post - ([44ad651](https://code.castopod.org/adaures/castopod/commit/44ad65117635b6292b4653bca7e22acecb025146)), - closes [#550](https://code.castopod.org/adaures/castopod/issues/550) - -## [1.13.3](https://code.castopod.org/adaures/castopod/compare/v1.13.2...v1.13.3) (2025-01-08) - -### Bug Fixes - -- remove exit function from podcast:import command to allow for - episodes:compute-downloads to run - ([3359abf](https://code.castopod.org/adaures/castopod/commit/3359abf3fc7d6ddf9db2cacc3e25f7c6d99e33a6)) - -## [1.13.2](https://code.castopod.org/adaures/castopod/compare/v1.13.1...v1.13.2) (2024-12-28) - -### Bug Fixes - -- add downloads_count to episodes table, computed every hour - ([5182d5d](https://code.castopod.org/adaures/castopod/commit/5182d5d67aa3c6f7906d4603efcec4b48f048991)) - -## [1.13.1](https://code.castopod.org/adaures/castopod/compare/v1.13.0...v1.13.1) (2024-12-05) - -### Bug Fixes - -- **api:** cast integers when creating episode - ([7ca501d](https://code.castopod.org/adaures/castopod/commit/7ca501dd6f426a1d50ffb1ea759f1e2cc91c1d13)) - -# [1.13.0](https://code.castopod.org/adaures/castopod/compare/v1.12.11...v1.13.0) (2024-10-25) +## [2.0.0-next.3](https://code.castopod.org/adaures/castopod/compare/v2.0.0-next.2...v2.0.0-next.3) (2024-12-30) ### Features - **api:** add Episode create and publish endpoints - ([75cf78e](https://code.castopod.org/adaures/castopod/commit/75cf78e972c52528dc38be050dcb1eb1f8e626fa)) + ([a90cdfd](https://code.castopod.org/adaures/castopod/commit/a90cdfdcdbde7a8fb520c6815d7b757947aea055)) +- **image:** add image size's width and height + ([f50098e](https://code.castopod.org/adaures/castopod/commit/f50098ec8926c8ae40718f5f128b6de7fe721b46)) +- **plugins:** add defaultValue for all field types + ([d3a98db](https://code.castopod.org/adaures/castopod/commit/d3a98db6d0112b5f59daddd2708c09dd2e595332)) +- **plugins:** add group field type + multiple option to render field arrays + ([11ccd0e](https://code.castopod.org/adaures/castopod/commit/11ccd0ebe71d476d8c0dbfe28edcf01f7f362b83)) +- **plugins:** add html field type + CodeEditor component + rework html head + generation + ([8cf9c6d](https://code.castopod.org/adaures/castopod/commit/8cf9c6dc833aedcccbc4cdb309b111f84d97d629)) - **rss:** add option for 301 redirect to new feed url - ([3a7d26f](https://code.castopod.org/adaures/castopod/commit/3a7d26fdf9bfeffb9247f8efe06d9040ae2fe5ff)) - -## [1.12.11](https://code.castopod.org/adaures/castopod/compare/v1.12.10...v1.12.11) (2024-10-16) + ([8402cc2](https://code.castopod.org/adaures/castopod/commit/8402cc29d2d0c61b014a7e03e5ccce7d3c11782a)) ### Bug Fixes +- add downloads_count to episodes table, computed every hour + ([f981937](https://code.castopod.org/adaures/castopod/commit/f9819376455c371eb5bd3c84ad938698335a3d67)) - allow passing json to app.proxyIPs config to set it - ([7d1460b](https://code.castopod.org/adaures/castopod/commit/7d1460b8e08beb447389c604995efd931c84fd72)) - -## [1.12.10](https://code.castopod.org/adaures/castopod/compare/v1.12.9...v1.12.10) (2024-10-03) - -### Bug Fixes - -- set user as www-data when running cron jobs in docker's supervisord config - ([be3b6db](https://code.castopod.org/adaures/castopod/commit/be3b6db207204e14c9ad5d4d84384b15e0dbfa84)) - -## [1.12.9](https://code.castopod.org/adaures/castopod/compare/v1.12.8...v1.12.9) (2024-08-16) - -### Bug Fixes - -- **fediverse:** add "processing" and "failed" statuses to better manage - broadcast load - ([cf9e072](https://code.castopod.org/adaures/castopod/commit/cf9e0724fcdb8d0194676880cc3b088b221f5a38)), - closes [#511](https://code.castopod.org/adaures/castopod/issues/511) - -## [1.12.8](https://code.castopod.org/adaures/castopod/compare/v1.12.7...v1.12.8) (2024-08-16) - -### Bug Fixes - -- **podcast-model:** always query podcast from database when clearing cache - ([995ca5b](https://code.castopod.org/adaures/castopod/commit/995ca5b197f8f917102a108dd07d1f81e99cc8e6)) - -## [1.12.7](https://code.castopod.org/adaures/castopod/compare/v1.12.6...v1.12.7) (2024-08-14) - -### Bug Fixes - -- **episode:** do not change slug when editing episode title - ([89bf73b](https://code.castopod.org/adaures/castopod/commit/89bf73b869c28c2fcffa3dcbc3660fac3b6bf988)), - closes [#513](https://code.castopod.org/adaures/castopod/issues/513) -- **preview:** delete episode preview cache after editing episode - ([6a2cdd0](https://code.castopod.org/adaures/castopod/commit/6a2cdd066ee13efc6489901bbdcbcc5fea35cd71)), - closes [#514](https://code.castopod.org/adaures/castopod/issues/514) - -## [1.12.6](https://code.castopod.org/adaures/castopod/compare/v1.12.5...v1.12.6) (2024-08-09) - -### Bug Fixes - + ([cbf739e](https://code.castopod.org/adaures/castopod/commit/cbf739e95cc0ad6e83a21353b8f4678e68d74f63)) +- **api:** cast integers when creating episode + ([775b302](https://code.castopod.org/adaures/castopod/commit/775b302f7c886e30e133c8a8c68764301b6c663b)) - **docker-image:** clear cache to account for new assets and data structure changes - ([e41245d](https://code.castopod.org/adaures/castopod/commit/e41245d2e758bce2a404749398bef89998638561)), + ([63c763f](https://code.castopod.org/adaures/castopod/commit/63c763f941195b3758c4b91acd8c350a5e7bb9c2)), closes [#510](https://code.castopod.org/adaures/castopod/issues/510) - -## [1.12.5](https://code.castopod.org/adaures/castopod/compare/v1.12.4...v1.12.5) (2024-07-30) - -### Bug Fixes - +- edit remap functions to get episode in episode admin controllers + ([9f74cca](https://code.castopod.org/adaures/castopod/commit/9f74cca342fedd896977efd2e89d0143959f3c4f)) +- **episode:** do not change slug when editing episode title + ([a83afb0](https://code.castopod.org/adaures/castopod/commit/a83afb0004511db80337806577fbc36f8d777116)), + closes [#513](https://code.castopod.org/adaures/castopod/issues/513) +- **fediverse:** add "processing" and "failed" statuses to better manage + broadcast load + ([1d7583d](https://code.castopod.org/adaures/castopod/commit/1d7583d738219574ae3d45d294dc94e7e406472b)), + closes [#511](https://code.castopod.org/adaures/castopod/issues/511) +- **icons:** set correct names for lock and lock-unlock icons in premium banner + ([37ee6d3](https://code.castopod.org/adaures/castopod/commit/37ee6d35b4bb66ce23dc271fb846200d1be0e7f6)) +- **plugins:** clear cache after activating or deactivating plugin + ([08c7df2](https://code.castopod.org/adaures/castopod/commit/08c7df2a5d5be340490c78deeef823167eb1b2fc)) +- **plugins:** delete relevant cache when submitting settings + ([00bd4c0](https://code.castopod.org/adaures/castopod/commit/00bd4c02ee23b181d74e7731626bfec3b1ff4916)) +- **podcast-model:** always query podcast from database when clearing cache + ([d30c49c](https://code.castopod.org/adaures/castopod/commit/d30c49cdff380c15db4f1851631a255a5baffcbe)) +- **premium-podcasts:** update query to validate subscription + ([2b1bbf3](https://code.castopod.org/adaures/castopod/commit/2b1bbf34303ead927f433b5c7d5d888ca3799954)) +- **preview:** delete episode preview cache after editing episode + ([732d429](https://code.castopod.org/adaures/castopod/commit/732d42923d0d7a66ff1ebd5841458e4205060560)), + closes [#514](https://code.castopod.org/adaures/castopod/issues/514) +- **release:** add conventional-changelog-conventionalcommits for CHANGELOG + generation + ([6934c8a](https://code.castopod.org/adaures/castopod/commit/6934c8aa8f0b7f9eea7c3f6f4089c56b2391d9a6)) - **rss:** add subscription id to cache name to prevent premium feeds from overlapping - ([5310d86](https://code.castopod.org/adaures/castopod/commit/5310d8648af6d43b9090f8d9f8066f7b3a8f0aa7)) + ([74f9325](https://code.castopod.org/adaures/castopod/commit/74f9325946d03a0d4efce57045e41cc9454ff97c)) +- set user as www-data when running cron jobs in docker's supervisord config + ([65d74f1](https://code.castopod.org/adaures/castopod/commit/65d74f14e612be3757c9304518eee112705f5ff9)) +- typo in EpisodeController remap function to get episode + ([f288a75](https://code.castopod.org/adaures/castopod/commit/f288a750f580ab19b04a170cc76bf8769084e19d)) +- update select and multi-select options to value/label arrays + ([63f93f5](https://code.castopod.org/adaures/castopod/commit/63f93f585bec4a11022cc8c75deb34968cba2348)) -## [1.12.4](https://code.castopod.org/adaures/castopod/compare/v1.12.3...v1.12.4) (2024-07-30) +### Internal -### Bug Fixes +- **plugins:** create Field objects per field type in settings forms + handle + rendering in class + ([34be5bc](https://code.castopod.org/adaures/castopod/commit/34be5bccabb7531afdcc6ebaf1dd39e4dfbe0677)) +- remove fields from podcast and episode entities to be replaced with plugins + ([b869acb](https://code.castopod.org/adaures/castopod/commit/b869acb3a988a3616d883a41c25d9c8409bd5518)) +- rename controller methods for views and actions to be more consistent + ([85704bf](https://code.castopod.org/adaures/castopod/commit/85704bfbe03fe5e38ff5e76a0e1cf0e5f1275f57)) +- update CodeIgniter to v4.5.6 + ([f295e9a](https://code.castopod.org/adaures/castopod/commit/f295e9aa4ca3129df24a22779f7c19bba7fac370)) +- update codigniter-icons to v1.0.1 + ([fa6967e](https://code.castopod.org/adaures/castopod/commit/fa6967e65cef1705b19cbb205132c4c751507d53)) +- update js dependencies to latest + ([70c9797](https://code.castopod.org/adaures/castopod/commit/70c97971fcf5bbeee826578057ae0e3afbbbd8a8)) -- **icons:** set correct names for lock and lock-unlock icons in premium banner - ([94deaab](https://code.castopod.org/adaures/castopod/commit/94deaab3cd0912ff1a585bee174a096a84c68384)) -- **premium-podcasts:** update query to validate subscription - ([0e6d294](https://code.castopod.org/adaures/castopod/commit/0e6d2945f215453abbe7d9f90afd012d2507846b)) - -## [1.12.3](https://code.castopod.org/adaures/castopod/compare/v1.12.2...v1.12.3) (2024-07-04) +# [2.0.0-next.2](https://code.castopod.org/adaures/castopod/compare/v2.0.0-next.1...v2.0.0-next.2) (2024-07-08) ### Bug Fixes - **audio-player:** set player icons to default instead of missing Castopod's - ([c89d298](https://code.castopod.org/adaures/castopod/commit/c89d29867e122fe7d4d5563f0ab1e9993e2ece16)) - -## [1.12.2](https://code.castopod.org/adaures/castopod/compare/v1.12.1...v1.12.2) (2024-07-03) - -### Bug Fixes - + ([0ba0a25](https://code.castopod.org/adaures/castopod/commit/0ba0a25b11bd67aeeb47a8179b72152dfd4a36da)) - broken icon call in frontend default pages template - ([d8d2eb9](https://code.castopod.org/adaures/castopod/commit/d8d2eb92b741ecfc956b416db481f8c2dee84864)) - -## [1.12.1](https://code.castopod.org/adaures/castopod/compare/v1.12.0...v1.12.1) (2024-07-01) - -### Bug Fixes - + ([3228362](https://code.castopod.org/adaures/castopod/commit/322836254e86be7878e21438177ee8f73f03a2fa)) +- **manifest:** set repository url as required in docstring typings + ([a8c81b3](https://code.castopod.org/adaures/castopod/commit/a8c81b3fa19a28dbd608027c231dcac31eafb38f)) - set correct icons parameters in map and funding links views - ([b129813](https://code.castopod.org/adaures/castopod/commit/b129813ea5d38436563639b51ec9ed2882644228)), + ([5d35524](https://code.castopod.org/adaures/castopod/commit/5d355248753be24e3cf324144ff076f2fc23be88)), closes [#500](https://code.castopod.org/adaures/castopod/issues/500) -# [1.12.0](https://code.castopod.org/adaures/castopod/compare/v1.11.0...v1.12.0) (6/28/2024) +### Features + +- **plugins:** add `minCastopodVersion` to denote incompatibility with previous + Castopod versions + ([fc9ea75](https://code.castopod.org/adaures/castopod/commit/fc9ea7597e454e5c7c7af043d29af7bbe119e342)) +- **plugins:** load and display LICENSE.md file if found in plugin's directory + ([fee7905](https://code.castopod.org/adaures/castopod/commit/fee7905935a9adf963b4485b437fe4d972c14b5f)) + +# [2.0.0-next.1](https://code.castopod.org/adaures/castopod/compare/v1.11.0...v2.0.0-next.1) (6/19/2024) ### Bug Fixes @@ -248,14 +121,65 @@ ([fc4f982](https://code.castopod.org/adaures/castopod/commit/fc4f9825568cd4384c5b3cfe972accd146548807)), closes [#473](https://code.castopod.org/adaures/castopod/issues/473) +### Build System + +- release next major version as prerelease + ([8275226](https://code.castopod.org/adaures/castopod/commit/827522643e9f8a5ea9be05b4847dc637f0f43a13)) + ### Features +- add Plugins module with base files for plugins architecture + ([7253e13](https://code.castopod.org/adaures/castopod/commit/7253e13ac2118f6f165f54ea0cbcd63d51ab9205)) +- **plugins:** abstract settings form for general, podcast and episode types + ([b62b483](https://code.castopod.org/adaures/castopod/commit/b62b483ad9ff114a22a9ee52e1a1a2c9fa444d42)) +- **plugins:** activate / deactivate plugin using settings table + ([27d2a1b](https://code.castopod.org/adaures/castopod/commit/27d2a1b0ffba9454dd54cbb4251a2d179b09762a)) +- **plugins:** add aside with plugin metadata next to plugin's readme + ([dfb7888](https://code.castopod.org/adaures/castopod/commit/dfb7888aeb689b4066abc37084e08cd7f1d0f15d)) +- **plugins:** add before channel/item hooks to allow podcast/episode data edit + when generating rss + ([80d2c48](https://code.castopod.org/adaures/castopod/commit/80d2c48ee265cb32ed0d710c488292fcbc120044)) +- **plugins:** add json schema definition for plugin manifest + ([b5eddf3](https://code.castopod.org/adaures/castopod/commit/b5eddf351f6f6fa1c299fbac31cbd056ef232330)) +- **plugins:** add methods to easily retrieve general, podcast and episode + settings in hooks methods + ([3a900bb](https://code.castopod.org/adaures/castopod/commit/3a900bbab68b819cedf8943540d2ee0aeb6e8539)) +- **plugins:** add new field types + validate & cast user data before storing + settings + ([6f833fc](https://code.castopod.org/adaures/castopod/commit/6f833fc76a3aa6c6b87c27ad18a2fb90e537e21e)) +- **plugins:** add options to manifest for building forms and storing plugin + settings + ([3d8aedf](https://code.castopod.org/adaures/castopod/commit/3d8aedf9c34e6927b6d3b11445d5f0e669b347d7)) +- **plugins:** add settings page for podcast and episode if defined in the + plugin's manifest + ([89ac92f](https://code.castopod.org/adaures/castopod/commit/89ac92fb412a04231ce52fd6480c9ab893b19ef5)) +- **plugins:** add siteHead hook to add custom meta tags to public pages + ([e80a33b](https://code.castopod.org/adaures/castopod/commit/e80a33bf2ad4fe1b47037add7470a6c2770f4036)) +- **plugins:** display errors when plugin is invalid instead of crashing + ([8ec7909](https://code.castopod.org/adaures/castopod/commit/8ec79097bbdbcbce622518ef61c068f20e0ef74e)) +- **plugins:** handle empty states and long strings in UI + ([45ac2a4](https://code.castopod.org/adaures/castopod/commit/45ac2a4be96532b9456e6af1d26ba4ada3649303)) +- **plugins:** load and validate plugin manifest.json + ([1510e36](https://code.castopod.org/adaures/castopod/commit/1510e36c0acd2b254622ec230acd1d2461ee9bf3)) +- **plugins:** load plugins using file locator service + ([587938d](https://code.castopod.org/adaures/castopod/commit/587938d2bf307b823af143586b9ec9e9b44e8dc1)) +- **plugins:** load README.md file to view plugin's instructions in UI + ([e6bfdfc](https://code.castopod.org/adaures/castopod/commit/e6bfdfc3902705285701c13c8067fe0f538425c6)) +- **plugins:** register plugins using Plugin.php file instead of namespace + + simplify i18n structure + ([2035c39](https://code.castopod.org/adaures/castopod/commit/2035c39fd138a1fd408516bd1972ab6a02544c10)) +- **plugins:** uninstall plugins via CLI and admin UI + ([9a80de4](https://code.castopod.org/adaures/castopod/commit/9a80de40686bbf4288da21cc2a6dde8036580e47)) - set owner email to hidden by default in podcast create form ([7a6d9df](https://code.castopod.org/adaures/castopod/commit/7a6d9df6db8a6184b8250ced0475f3e741dde7f4)) - support podcast:txt tag with verify use case ([57e459e](https://code.castopod.org/adaures/castopod/commit/57e459e187ed048430f4137172e22396cd02bf81)), closes [#468](https://code.castopod.org/adaures/castopod/issues/468) +### BREAKING CHANGES + +- next major release including plugins architecture + # [1.11.0](https://code.castopod.org/adaures/castopod/compare/v1.10.5...v1.11.0) (4/17/2024) ### Bug Fixes diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 57da0daf..b1291ae9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,128 +1,162 @@ -# Contributor Covenant Code of Conduct +# Contributor Covenant 3.0 Code of Conduct ## Our Pledge -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity and -orientation. +We pledge to make our community welcoming, safe, and equitable for all. -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. +We are committed to fostering an environment that respects and promotes the +dignity, rights, and contributions of all individuals, regardless of +characteristics including race, ethnicity, caste, color, age, physical +characteristics, neurodiversity, disability, sex or gender, gender identity or +expression, sexual orientation, language, philosophy or religion, national or +social origin, socio-economic position, level of education, or other status. The +same privileges of participation are extended to everyone who participates in +good faith and in accordance with this Covenant. -## Our Standards +## Encouraged Behaviors -Examples of behavior that contributes to a positive environment for our -community include: +While acknowledging differences in social norms, we all strive to meet our +community's expectations for positive behavior. We also understand that our +words and actions may be interpreted differently than we intend based on +culture, background, or native language. -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall - community +With these considerations in mind, we agree to behave mindfully toward each +other and act in ways that center our shared values, including: -Examples of unacceptable behavior include: +1. Respecting the **purpose of our community**, our activities, and our ways of + gathering. +2. Engaging **kindly and honestly** with others. +3. Respecting **different viewpoints** and experiences. +4. **Taking responsibility** for our actions and contributions. +5. Gracefully giving and accepting **constructive feedback**. +6. Committing to **repairing harm** when it occurs. +7. Behaving in other ways that promote and sustain the **well-being of our + community**. -- The use of sexualized language or imagery, and sexual attention or advances of - any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, - without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +## Restricted Behaviors -## Enforcement Responsibilities +We agree to restrict the following behaviors in our community. Instances, +threats, and promotion of these behaviors are violations of this Code of +Conduct. -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. +1. **Harassment.** Violating explicitly expressed boundaries or engaging in + unnecessary personal attention after any clear request to stop. +2. **Character attacks.** Making insulting, demeaning, or pejorative comments + directed at a community member or group of people. +3. **Stereotyping or discrimination.** Characterizing anyone’s personality or + behavior on the basis of immutable identities or traits. +4. **Sexualization.** Behaving in a way that would generally be considered + inappropriately intimate in the context or purpose of the community. +5. **Violating confidentiality**. Sharing or acting on someone's personal or + private information without their permission. +6. **Endangerment.** Causing, encouraging, or threatening violence or other harm + toward any person or group. +7. Behaving in other ways that **threaten the well-being** of our community. -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. +### Other Restrictions + +1. **Misleading identity.** Impersonating someone else for any reason, or + pretending to be someone else to evade enforcement actions. +2. **Failing to credit sources.** Not properly crediting the sources of content + you contribute. +3. **Promotional materials**. Sharing marketing or other commercial content in a + way that is outside the norms of the community. +4. **Irresponsible communication.** Failing to responsibly present content which + includes, links or describes any other restricted behaviors. + +## Reporting an Issue + +Tensions can occur between community members even when they are trying their +best to collaborate. Not every conflict represents a code of conduct violation, +and this Code of Conduct reinforces encouraged behaviors and norms that can help +avoid conflicts and minimize harm. + +When an incident does occur, it is important to report it promptly. To report a +possible violation, email us at [abuse@castopod.org](mailto:abuse@castopod.org). + +Community Moderators take reports of violations seriously and will make every +effort to respond in a timely manner. They will investigate all reports of code +of conduct violations, reviewing messages, logs, and recordings, or interviewing +witnesses and other participants. Community Moderators will keep investigation +and enforcement actions as transparent as possible while prioritizing safety and +confidentiality. In order to honor these values, enforcement actions are carried +out in private with the involved parties, but communicating to the whole +community may be part of a mutually agreed upon resolution. + +## Addressing and Repairing Harm + +If an investigation by the Community Moderators finds that this Code of Conduct +has been violated, the following enforcement ladder may be used to determine how +best to repair harm, based on the incident's impact on the individuals involved +and the community as a whole. Depending on the severity of a violation, lower +rungs on the ladder may be skipped. + +1. Warning + 1. Event: A violation involving a single incident or series of incidents. + 2. Consequence: A private, written warning from the Community Moderators. + 3. Repair: Examples of repair include a private written apology, + acknowledgement of responsibility, and seeking clarification on + expectations. +2. Temporarily Limited Activities + 1. Event: A repeated incidence of a violation that previously resulted in a + warning, or the first incidence of a more serious violation. + 2. Consequence: A private, written warning with a time-limited cooldown + period designed to underscore the seriousness of the situation and give + the community members involved time to process the incident. The cooldown + period may be limited to particular communication channels or interactions + with particular community members. + 3. Repair: Examples of repair may include making an apology, using the + cooldown period to reflect on actions and impact, and being thoughtful + about re-entering community spaces after the period is over. +3. Temporary Suspension + 1. Event: A pattern of repeated violation which the Community Moderators have + tried to address with warnings, or a single serious violation. + 2. Consequence: A private written warning with conditions for return from + suspension. In general, temporary suspensions give the person being + suspended time to reflect upon their behavior and possible corrective + actions. + 3. Repair: Examples of repair include respecting the spirit of the + suspension, meeting the specified conditions for return, and being + thoughtful about how to reintegrate with the community when the suspension + is lifted. +4. Permanent Ban + 1. Event: A pattern of repeated code of conduct violations that other steps + on the ladder have failed to resolve, or a violation so serious that the + Community Moderators determine there is no way to keep the community safe + with this person as a member. + 2. Consequence: Access to all community spaces, tools, and communication + channels is removed. In general, permanent bans should be rarely used, + should have strong reasoning behind them, and should only be resorted to + if working through other remedies has failed to change the behavior. + 3. Repair: There is no possible repair in cases of this severity. + +This enforcement ladder is intended as a guideline. It does not limit the +ability of Community Managers to use their discretion and judgment, in keeping +with the best interests of our community. ## Scope This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed +an individual is officially representing the community in public or other +spaces. Examples of representing our community include using an official email +address, posting via an official social media account, or acting as an appointed representative at an online or offline event. -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[abuse@castopod.org](mailto:abuse@castopod.org). All complaints will be reviewed -and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +This Code of Conduct is adapted from the Contributor Covenant, version 3.0, +permanently available at +[https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/). -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). +Contributor Covenant is stewarded by the Organization for Ethical Source and +licensed under CC BY-SA 4.0. To view a copy of this license, visit +[https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/) -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +For answers to common questions about Contributor Covenant, see the FAQ at +[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). +Translations are provided at +[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). +Additional enforcement and community guideline resources can be found at +[https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). +The enforcement ladder was inspired by the work of +[Mozilla’s code of conduct team](https://github.com/mozilla/inclusion). diff --git a/CONTRIBUTING-DEV.md b/CONTRIBUTING-DEV.md index 66bfae37..907d3ce1 100644 --- a/CONTRIBUTING-DEV.md +++ b/CONTRIBUTING-DEV.md @@ -5,7 +5,7 @@ Castopod is a web app based on the `php` framework [CodeIgniter 4](https://codeigniter.com). -We use [Docker](https://www.docker.com/) quickly setup a dev environment. A +We use [Docker](https://www.docker.com/) to quickly setup a dev environment. A `docker-compose.yml` and `Dockerfile` are included in the project's root folder to help you kickstart your contribution. @@ -16,9 +16,9 @@ to help you kickstart your contribution. ### 1. Pre-requisites -0. Install [docker](https://docs.docker.com/get-docker). +0. Install [Docker](https://docs.docker.com/get-docker). -1. Clone Castopod project by running: +1. Clone the Castopod repository by running: ```bash git clone https://code.castopod.org/adaures/castopod.git @@ -79,7 +79,7 @@ to help you kickstart your contribution. > [CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) > for more info. -3. (for docker desktop) Add the repository you've cloned to docker desktop's +3. (for Docker desktop) Add the repository you've cloned to Docker desktop's `Settings` > `Resources` > `File Sharing` ### 2. (recommended) Develop inside the app container with VSCode @@ -96,7 +96,7 @@ required services will be loaded automagically! 🪄 > The VSCode window will reload inside the dev container. Expect several > minutes during first load as it is building all necessary services. - **Note**: The dev container will start by running Castopod's php server. + **Note**: The dev container will start by running Castopod's PHP server. During development, you will have to start [Vite](https://vitejs.dev)'s dev server for compiling the typescript code and styles: @@ -105,7 +105,7 @@ required services will be loaded automagically! 🪄 pnpm run dev ``` - If there is any issue with the php server not running, you can restart them + If there is any issue with the PHP server not running, you can restart them using the following commands: ```bash @@ -157,9 +157,9 @@ To see your changes, go to: You do not wish to use the VSCode devcontainer? No problem! -1. Start docker containers manually: +1. Start the Docker containers manually: - Go to project's root folder and run: + Go to the project's root folder and run: ```bash # starts all services declared in docker-compose.yml file @@ -223,14 +223,14 @@ You do not wish to use the VSCode devcontainer? No problem! > For more info, check out the > [Composer documentation](https://getcomposer.org/doc/). -2. Install javascript dependencies with [pnpm](https://pnpm.io/) +2. Install JavaScript dependencies with [pnpm](https://pnpm.io/) ```bash pnpm install ``` > [!NOTE] - > The javascript dependencies aren't included in the repository. Pnpm will + > The JavaScript dependencies aren't included in the repository. Pnpm will > check the `package.json` and `pnpm-lock.yaml` files to download the > packages with the right versions. The dependencies will live under the > `node_module` folder. For more info, check out the @@ -249,7 +249,7 @@ You do not wish to use the VSCode devcontainer? No problem! > [!NOTE] > The static assets generated live under the `public/assets` folder, it - > includes javascript, styles, images, fonts, icons and svg files. + > includes JavaScript, styles, images, fonts, icons and svg files. ### Initialize and populate database @@ -291,7 +291,7 @@ You do not wish to use the VSCode devcontainer? No problem! php spark db:seed DevSuperadminSeeder ``` -3. (optionnal) Populate the database with test data: +3. (optional) Populate the database with test data: - Populate with fake podcast analytics: ```bash @@ -312,13 +312,13 @@ You do not wish to use the VSCode devcontainer? No problem! docker-compose logs --tail 50 --follow --timestamps app ``` -- Interact with redis server using included redis-cli command: +- Interact with the Redis server using included redis-cli command: ```bash docker exec -it castopod_redis redis-cli ``` -- Monitor the redis container: +- Monitor the Redis container: ```bash docker-compose logs --tail 50 --follow --timestamps redis @@ -354,10 +354,37 @@ docker-compose down docker-compose build app ``` -Check [docker](https://docs.docker.com/engine/reference/commandline/docker/) and +Check [Docker](https://docs.docker.com/engine/reference/commandline/docker/) and [docker-compose](https://docs.docker.com/compose/reference/) documentations for more insights. +### Updating Documentation + +Castopod's documentation is written in Markdown and uses the Astro Starlight +framework. To update Castopod's documentation, including the Getting Started +guide and User Guide: + +1. Change directories to the `docs` directory and install the dependencies: + + ```bash + cd docs/ + pnpm i + ``` + +2. Start the documentation development server: + + ```bash + pnpm run dev --host + ``` + +3. The documentation development server runs on port 4321. In your browser visit + `http://localhost:4321/docs`. If the page displays a 404 Not Found error, + click on the Castopod logo in the upper left hand corner of the page and the + documentation should load. + +4. Edit the Markdown files with your documentation updates. The Astro Starlight + development server will automatically update each time you save a change. + ## Known issues ### Allocation failed - JavaScript heap out of memory @@ -396,7 +423,7 @@ You may use Linux user namespaces to fix this on your machine: username:100000:65536 ``` -3. Restart docker: +3. Restart Docker: ```bash sudo systemctl restart docker diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24af0bdb..a3468537 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -98,8 +98,8 @@ accurate comments, etc.) and any other requirements (such as test coverage). Adhering to the following process is the best way to get your work included in the project: -1. [Fork](https://docs.gitlab.com/ee/gitlab-basics/fork-project.html) the - project, clone your fork, and configure the remotes: +1. [Fork](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html) + the project, clone your fork, and configure the remotes: ```bash # Clone your fork of the repo into the current directory diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index b9c083c2..97175ffd 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -18,9 +18,9 @@ Javascript dependencies can be found in the [package.json](./package.json) file. ([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL)) - [RemixIcon](https://remixicon.com/) ([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License)) -- [OPAWG/User agent list](https://github.com/opawg/user-agents) +- [OPAWG/User agent list](https://github.com/opawg/user-agents-v2) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) - ([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE)) + ([MIT license](https://github.com/opawg/user-agents-v2/blob/master/LICENSE)) - [OPAWG/podcast-rss-useragents](https://github.com/opawg/podcast-rss-useragents) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) ([MIT license](https://github.com/opawg/podcast-rss-useragents/blob/master/LICENSE)) diff --git a/README.md b/README.md index d6c165c9..6460720c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

- Castopod + Castopod

@@ -46,18 +46,17 @@ Thanks goes to these wonderful people - - +
- + -
+ @@ -66,7 +65,7 @@ Thanks goes to these wonderful people -
+ @@ -75,7 +74,7 @@ Thanks goes to these wonderful people -
+ @@ -84,7 +83,7 @@ Thanks goes to these wonderful people -
+ @@ -93,7 +92,7 @@ Thanks goes to these wonderful people -
+ @@ -102,25 +101,24 @@ Thanks goes to these wonderful people -
+ - -
- - + + + + +
Yassine Doghri
Yassine Doghri

💻 🐛 📖 👀 🚧 🖋 🎨 ️️️️♿️ 🌍 💬 🧑‍🏫 🚇 🤔 📆 📝
Yassine Doghri
Yassine Doghri

💻 🐛 📖 👀 🚧 🖋 🎨 ️️️️♿️ 🌍 💬 🧑‍🏫 🚇 🤔 📆 📝
Benjamin Bellamy
Benjamin Bellamy

💻 🐛 👀 🖋 🌍 💬 🚇 🤔 📝 📆 📢
Ola Hneini
Ola Hneini

💻 👀 📖 🚧 💬 🤔
Romain de Laage
Romain de Laage

💻 🚇 📖 🌍 🤔
Lyonel Bernard
Lyonel Bernard

🐛 💬 🔊 🤔
Christopher Lagonick-Weitzel
Christopher Lagonick-Weitzel

🐛 💬 🔊 🤔
Ernesto Acosta
Ernesto Acosta

🐛 🔊 🌍 💬 🤔
Ewen
Ewen

🌍 🤔 💻
Bastien Luneteau
Bastien Luneteau

💻 🐛
Marcin Lewandowski
Marcin Lewandowski

🐛 🤔
Sebastian Janik
Sebastian Janik

💻
Patryk Karczmarczyk
Patryk Karczmarczyk

💻
denis d
denis d

🐛 🤔
Douglas Kastle
Douglas Kastle

🐛 🤔
Jonas S
Jonas S

💻
LEFEBVRE Yann
LEFEBVRE Yann

🐛
Sebastian Späth
Sebastian Späth

🐛 🤔
rocky III
rocky III

🐛
Hermann Josef Eckl
Hermann Josef Eckl

🐛
Angelos Chouvardas
Angelos Chouvardas

🌍
Eivind
Eivind

🌍
forght
forght

🌍
glottis0q
glottis0q

🌍
ButterflyOfFire
ButterflyOfFire

🌍
CTHTC
CTHTC

🌍
Russian Retro
Russian Retro

🌍
Marek L'ach
Marek L'ach

🌍
GunChleoc
GunChleoc

🌍
GabiSnow
GabiSnow

🌍
Dimitri Regnier
Dimitri Regnier

🤔
irithys
irithys

🌍
Sergi
Sergi

🌍
ghose (XoseM)
ghose (XoseM)

🌍
Andreas Olsson
Andreas Olsson

🌍
leonfrom
leonfrom

🌍
agentcobra
agentcobra

🌍
Alessandro
Alessandro

🌍
liimee
liimee

🌍
Ahmed Sabouni
Ahmed Sabouni

🌍
KrzysztofDomanczyk
KrzysztofDomanczyk

💻
Guy Martin
Guy Martin

🐛 💻
Guy Martin
Guy Martin

🐛 💻
Paul Cutler
Paul Cutler

📖 💬 🤔
Nate Ritter
Nate Ritter

💻
- - @@ -157,10 +155,10 @@ backers. If you'd like to help, please consider - Netlify + Ad Aures - NLnet Logo + NLnet Logo diff --git a/app/Config/App.php b/app/Config/App.php index 9f95c964..404ff5a0 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Config; use CodeIgniter\Config\BaseConfig; +use Override; class App extends BaseConfig { @@ -123,19 +124,17 @@ class App extends BaseConfig * @var list */ public array $supportedLocales = [ - 'en', // keep english language first in case locale negotiation does not work + 'en', 'fr', - 'br', - 'ca', - 'cs', - 'de', - 'es', - 'lt', - 'nn-no', 'pl', + 'de', 'pt-br', - 'sr-latn', + 'nn-no', + 'es', 'zh-hans', + 'ca', + 'br', + 'sr-latn', ]; /** @@ -274,6 +273,7 @@ class App extends BaseConfig * * @param mixed $property */ + #[Override] protected function initEnvValue(&$property, string $name, string $prefix, string $shortPrefix): void { // if attempting to set property from ENV, first set to empty string diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index 6d5bb530..3fd8e175 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -48,6 +48,7 @@ class Autoload extends AutoloadConfig 'Modules\Media' => ROOTPATH . 'modules/Media/', 'Modules\MediaClipper' => ROOTPATH . 'modules/MediaClipper/', 'Modules\Platforms' => ROOTPATH . 'modules/Platforms/', + 'Modules\Plugins' => ROOTPATH . 'modules/Plugins/', 'Modules\PodcastImport' => ROOTPATH . 'modules/PodcastImport/', 'Modules\PremiumPodcasts' => ROOTPATH . 'modules/PremiumPodcasts/', 'Modules\Update' => ROOTPATH . 'modules/Update/', @@ -55,7 +56,6 @@ class Autoload extends AutoloadConfig 'Themes' => ROOTPATH . 'themes', 'ViewComponents' => APPPATH . 'Libraries/ViewComponents/', 'ViewThemes' => APPPATH . 'Libraries/ViewThemes/', - 'Vite' => APPPATH . 'Libraries/Vite/', ]; /** @@ -93,7 +93,7 @@ class Autoload extends AutoloadConfig * * @var list */ - public $files = [APPPATH . 'Libraries/ViewComponents/Helpers/view_components_helper.php']; + public $files = []; /** * ------------------------------------------------------------------- @@ -106,5 +106,5 @@ class Autoload extends AutoloadConfig * * @var list */ - public $helpers = ['auth', 'setting']; + public $helpers = ['auth', 'setting', 'plugins']; } diff --git a/app/Config/CURLRequest.php b/app/Config/CURLRequest.php index 040800df..4dbb7afa 100644 --- a/app/Config/CURLRequest.php +++ b/app/Config/CURLRequest.php @@ -8,6 +8,19 @@ use CodeIgniter\Config\BaseConfig; class CURLRequest extends BaseConfig { + /** + * -------------------------------------------------------------------------- + * CURLRequest Share Connection Options + * -------------------------------------------------------------------------- + * + * Share connection options between requests. + * + * @var list + * + * @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect + */ + public array $shareConnectionOptions = [CURL_LOCK_DATA_CONNECT, CURL_LOCK_DATA_DNS]; + /** * -------------------------------------------------------------------------- * CURLRequest Share Options diff --git a/app/Config/Cache.php b/app/Config/Cache.php index c37f7c00..bbf812f9 100644 --- a/app/Config/Cache.php +++ b/app/Config/Cache.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Config; use CodeIgniter\Cache\CacheInterface; +use CodeIgniter\Cache\Handlers\ApcuHandler; use CodeIgniter\Cache\Handlers\DummyHandler; use CodeIgniter\Cache\Handlers\FileHandler; use CodeIgniter\Cache\Handlers\MemcachedHandler; @@ -76,6 +77,7 @@ class Cache extends BaseConfig * -------------------------------------------------------------------------- * File settings * -------------------------------------------------------------------------- + * * Your file storage preferences can be specified below, if you are using * the File driver. * @@ -90,6 +92,7 @@ class Cache extends BaseConfig * ------------------------------------------------------------------------- * Memcached settings * ------------------------------------------------------------------------- + * * Your Memcached servers can be specified below, if you are using * the Memcached drivers. * @@ -111,14 +114,24 @@ class Cache extends BaseConfig * Your Redis server can be specified below, if you are using * the Redis or Predis drivers. * - * @var array{host?: string, password?: string|null, port?: int, timeout?: int, database?: int} + * @var array{ + * host?: string, + * password?: string|null, + * port?: int, + * timeout?: int, + * async?: bool, + * persistent?: bool, + * database?: int + * } */ public array $redis = [ - 'host' => '127.0.0.1', - 'password' => null, - 'port' => 6379, - 'timeout' => 0, - 'database' => 0, + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + 'async' => false, // specific to Predis and ignored by the native Redis extension + 'persistent' => false, + 'database' => 0, ]; /** @@ -132,6 +145,7 @@ class Cache extends BaseConfig * @var array> */ public array $validHandlers = [ + 'apcu' => ApcuHandler::class, 'dummy' => DummyHandler::class, 'file' => FileHandler::class, 'memcached' => MemcachedHandler::class, @@ -158,4 +172,28 @@ class Cache extends BaseConfig * @var bool|list */ public $cacheQueryString = false; + + /** + * -------------------------------------------------------------------------- + * Web Page Caching: Cache Status Codes + * -------------------------------------------------------------------------- + * + * HTTP status codes that are allowed to be cached. Only responses with + * these status codes will be cached by the PageCache filter. + * + * Default: [] - Cache all status codes (backward compatible) + * + * Recommended: [200] - Only cache successful responses + * + * You can also use status codes like: + * [200, 404, 410] - Cache successful responses and specific error codes + * [200, 201, 202, 203, 204] - All 2xx successful responses + * + * WARNING: Using [] may cache temporary error pages (404, 500, etc). + * Consider restricting to [200] for production applications to avoid + * caching errors that should be temporary. + * + * @var list + */ + public array $cacheStatusCodes = []; } diff --git a/app/Config/Constants.php b/app/Config/Constants.php index ea517aea..e23c0e0d 100644 --- a/app/Config/Constants.php +++ b/app/Config/Constants.php @@ -11,7 +11,7 @@ declare(strict_types=1); | | NOTE: this constant is updated upon release with Continuous Integration. */ -defined('CP_VERSION') || define('CP_VERSION', '1.15.5'); +defined('CP_VERSION') || define('CP_VERSION', '2.0.0-next.3'); /* | -------------------------------------------------------------------- @@ -24,10 +24,23 @@ defined('CP_VERSION') || define('CP_VERSION', '1.15.5'); | classes should use. | | NOTE: changing this will require manually modifying the - | existing namespaces of App\* namespaced-classes. + | existing namespaces of App* namespaced-classes. */ defined('APP_NAMESPACE') || define('APP_NAMESPACE', 'App'); +/* + | -------------------------------------------------------------------- + | Plugins Path + | -------------------------------------------------------------------- + | + | This defines the folder in which plugins will live. + */ +defined('PLUGINS_PATH') || + define('PLUGINS_PATH', ROOTPATH . 'plugins' . DIRECTORY_SEPARATOR); + +defined('PLUGINS_KEY_PATTERN') || + define('PLUGINS_KEY_PATTERN', '[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*'); + /* | -------------------------------------------------------------------------- | Composer Path diff --git a/app/Config/ContentSecurityPolicy.php b/app/Config/ContentSecurityPolicy.php index 6c08b13c..99fa0b0a 100644 --- a/app/Config/ContentSecurityPolicy.php +++ b/app/Config/ContentSecurityPolicy.php @@ -26,14 +26,24 @@ class ContentSecurityPolicy extends BaseConfig */ public ?string $reportURI = null; + /** + * Specifies a reporting endpoint to which violation reports ought to be sent. + */ + public ?string $reportTo = null; + /** * Instructs user agents to rewrite URL schemes, changing HTTP to HTTPS. This directive is for websites with large * numbers of old URLs that need to be rewritten. */ public bool $upgradeInsecureRequests = false; + // ------------------------------------------------------------------------- + // CSP DIRECTIVES SETTINGS + // NOTE: once you set a policy to 'none', it cannot be further restricted + // ------------------------------------------------------------------------- + /** - * Will default to self if not overridden + * Will default to `'self'` if not overridden * * @var list|string|null */ @@ -46,6 +56,21 @@ class ContentSecurityPolicy extends BaseConfig */ public string | array $scriptSrc = 'self'; + /** + * Specifies valid sources for JavaScript + HTML); + + if ($this->title) { + $this->tag('title', esc($this->title)); + } + + if (url_is(route_to('admin') . '*') || url_is(base_url(config('Auth')->gateway) . '*')) { + // restricted admin and auth areas, do not index + $this->meta('robots', 'noindex'); + } else { + // public website, set siteHead hook only there + service('plugins') + ->siteHead($this); + } + + $head = ''; + foreach ($this->tags as $tag) { + if ($tag['value'] === null) { + $head .= <<stringify_attributes($tag['attributes'])}/> + HTML; + } else { + $head .= <<stringify_attributes($tag['attributes'])}>{$tag['value']} + HTML; + } + } + + $head .= $this->rawContent . ''; + + // reset head for next render + $this->title = null; + $this->tags = []; + $this->rawContent = ''; + + return $head; + } + + public function title(string $title): self + { + $this->title = $title; + return $this->meta('title', $title) + ->og('title', $title) + ->twitter('title', $title); + } + + public function description(string $desc): self + { + return $this->meta('description', $desc) + ->og('description', $desc) + ->twitter('description', $desc); + } + + public function image(string $url, string $card = 'summary_large_image'): self + { + return $this->og('image', $url) + ->twitter('card', $card) + ->twitter('image', $url); + } + + public function canonical(string $url): self + { + return $this->tag('link', null, [ + 'rel' => 'canonical', + 'href' => $url, + ]); + } + + public function twitter(string $name, string $value): self + { + $this->meta("twitter:{$name}", $value); + return $this; + } + + /** + * @param array $attributes + */ + public function tag(string $name, ?string $value = null, array $attributes = []): self + { + $this->tags[] = [ + 'name' => $name, + 'value' => $value, + 'attributes' => $attributes, + ]; + + return $this; + } + + public function meta(string $name, string $content): self + { + $this->tag('meta', null, [ + 'name' => $name, + 'content' => $content, + ]); + + return $this; + } + + public function og(string $name, string $content): self + { + $this->meta('og:' . $name, $content); + + return $this; + } + + public function appendRawContent(string $content): self + { + $this->rawContent .= $content; + + return $this; + } + + /** + * @param array $attributes + */ + private function stringify_attributes(array $attributes): string + { + return stringify_attributes($attributes); + } +} diff --git a/app/Libraries/Router.php b/app/Libraries/Router.php index 7063f630..5ce2c227 100644 --- a/app/Libraries/Router.php +++ b/app/Libraries/Router.php @@ -15,9 +15,11 @@ declare(strict_types=1); namespace App\Libraries; use CodeIgniter\Exceptions\PageNotFoundException; -use CodeIgniter\Router\Exceptions\RedirectException; +use CodeIgniter\HTTP\Exceptions\RedirectException; use CodeIgniter\Router\Exceptions\RouterException; use CodeIgniter\Router\Router as CodeIgniterRouter; +use Config\Routing; +use Override; class Router extends CodeIgniterRouter { @@ -29,6 +31,7 @@ class Router extends CodeIgniterRouter * * @return boolean Whether the route was matched or not. */ + #[Override] protected function checkRoutes(string $uri): bool { $routes = $this->collection->getRoutes($this->collection->getHTTPVerb()); @@ -66,7 +69,7 @@ class Router extends CodeIgniterRouter throw new RedirectException( preg_replace('#^' . $routeKey . '$#u', (string) $redirectTo, $uri), - $this->collection->getRedirectCode($routeKey) + $this->collection->getRedirectCode($routeKey), ); } @@ -76,7 +79,7 @@ class Router extends CodeIgniterRouter preg_match( '#^' . str_replace('{locale}', '(?[^/]+)', $matchedKey) . '$#u', $uri, - $matched + $matched, ); if ($this->collection->shouldUseSupportedLocalesOnly() @@ -179,24 +182,50 @@ class Router extends CodeIgniterRouter return true; } - [$controller] = explode('::', (string) $handler); + if (str_contains((string) $handler, '::')) { + [$controller, $methodAndParams] = explode('::', (string) $handler); + } else { + $controller = $handler; + $methodAndParams = ''; + } // Checks `/` in controller name - if (str_contains($controller, '/')) { + if (str_contains((string) $controller, '/')) { throw RouterException::forInvalidControllerName($handler); } if (str_contains((string) $handler, '$') && str_contains($routeKey, '(')) { // Checks dynamic controller - if (str_contains($controller, '$')) { + if (str_contains((string) $controller, '$')) { throw RouterException::forDynamicController($handler); } - // Using back-references - $handler = preg_replace('#^' . $routeKey . '$#u', (string) $handler, $uri); + if (config(Routing::class)->multipleSegmentsOneParam === false) { + // Using back-references + $segments = explode( + '/', + (string) preg_replace('#\A' . $routeKey . '\z#u', (string) $handler, $uri), + ); + } else { + if (str_contains($methodAndParams, '/')) { + [$method, $handlerParams] = explode('/', $methodAndParams, 2); + $params = explode('/', $handlerParams); + $handlerSegments = array_merge([$controller . '::' . $method], $params); + } else { + $handlerSegments = [$handler]; + } + + $segments = []; + + foreach ($handlerSegments as $segment) { + $segments[] = $this->replaceBackReferences($segment, $matches); + } + } + } else { + $segments = explode('/', (string) $handler); } - $this->setRequest(explode('/', (string) $handler)); + $this->setRequest($segments); $this->setMatchedRoute($matchedKey, $handler); diff --git a/app/Libraries/SimpleRSSElement.php b/app/Libraries/RssFeed.php similarity index 55% rename from app/Libraries/SimpleRSSElement.php rename to app/Libraries/RssFeed.php index 827e58dd..631c80f1 100644 --- a/app/Libraries/SimpleRSSElement.php +++ b/app/Libraries/RssFeed.php @@ -11,10 +11,34 @@ declare(strict_types=1); namespace App\Libraries; use DOMDocument; +use Override; use SimpleXMLElement; -class SimpleRSSElement extends SimpleXMLElement +class RssFeed extends SimpleXMLElement { + public const ATOM_NS = 'atom'; + + public const ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom'; + + public const ITUNES_NS = 'itunes'; + + public const ITUNES_NAMESPACE = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + + public const PODCAST_NS = 'podcast'; + + public const PODCAST_NAMESPACE = 'https://podcastindex.org/namespace/1.0'; + + public function __construct(string $contents = '') + { + parent::__construct(sprintf( + "%s", + $this::ATOM_NAMESPACE, + $this::ITUNES_NAMESPACE, + $this::PODCAST_NAMESPACE, + $contents, + )); + } + /** * Adds a child with $value inside CDATA * @@ -47,6 +71,7 @@ class SimpleRSSElement extends SimpleXMLElement * * @return static The addChild method returns a SimpleXMLElement object representing the child added to the XML node. */ + #[Override] public function addChild($name, $value = null, $namespace = null, $escape = true): static { $newChild = parent::addChild($name, null, $namespace); @@ -61,4 +86,37 @@ class SimpleRSSElement extends SimpleXMLElement return $newChild; } + + /** + * Add RssFeed code into a RssFeed + * + * adapted from: https://stackoverflow.com/a/23527002 + * + * @param self|array $nodes + */ + public function appendNodes(self|array $nodes): void + { + if (! is_array($nodes)) { + $nodes = [$nodes]; + } + + foreach ($nodes as $element) { + $namespaces = $element->getNamespaces(); + $namespace = array_first($namespaces) ?? null; + + if (trim((string) $element) === '') { + $simpleRSS = $this->addChild($element->getName(), null, $namespace); + } else { + $simpleRSS = $this->addChild($element->getName(), (string) $element, $namespace); + } + + foreach ($element->children() as $child) { + $simpleRSS->appendNodes($child); + } + + foreach ($element->attributes() as $name => $value) { + $simpleRSS->addAttribute($name, (string) $value); + } + } + } } diff --git a/app/Libraries/ViewComponents/Component.php b/app/Libraries/ViewComponents/Component.php index b86e4a85..eaf9744a 100644 --- a/app/Libraries/ViewComponents/Component.php +++ b/app/Libraries/ViewComponents/Component.php @@ -4,26 +4,32 @@ declare(strict_types=1); namespace ViewComponents; -class Component implements ComponentInterface -{ - protected string $slot = ''; +use Override; - protected string $class = ''; +abstract class Component implements ComponentInterface +{ + /** + * @var list + */ + protected array $props = []; + + /** + * @var array + */ + protected array $casts = []; + + protected ?string $slot = null; /** * @var array */ - protected array $attributes = [ - 'class' => '', - ]; + protected array $attributes = []; /** * @param array $attributes */ public function __construct(array $attributes) { - helper('viewcomponents'); - // overwrite default attributes if set $this->attributes = [...$this->attributes, ...$attributes]; @@ -42,11 +48,42 @@ class Component implements ComponentInterface if (is_callable([$this, $method])) { $this->{$method}($value); } else { + if (array_key_exists($name, $this->casts)) { + $value = match ($this->casts[$name]) { + 'boolean' => $value === 'true', + 'number' => (int) $value, + 'array' => json_decode(htmlspecialchars_decode($value), true), + default => $value, + }; + } + $this->{$name} = $value; } + + // remove from attributes + if (in_array($name, $this->props, true)) { + unset($this->attributes[$name]); + } + } + + unset($this->attributes['slot']); + } + + public function mergeClass(string $class): void + { + if (! array_key_exists('class', $this->attributes)) { + $this->attributes['class'] = $class; + } else { + $this->attributes['class'] .= ' ' . $class; } } + public function getStringifiedAttributes(): string + { + return stringify_attributes($this->attributes); + } + + #[Override] public function render(): string { return static::class . ': RENDER METHOD NOT IMPLEMENTED'; diff --git a/app/Libraries/ViewComponents/ComponentRenderer.php b/app/Libraries/ViewComponents/ComponentRenderer.php index f9e916c0..e9a68172 100644 --- a/app/Libraries/ViewComponents/ComponentRenderer.php +++ b/app/Libraries/ViewComponents/ComponentRenderer.php @@ -43,38 +43,38 @@ class ComponentRenderer private function renderSelfClosingTags(string $output): string { // Pattern borrowed and adapted from Laravel's ComponentTagCompiler - // Should match any Component tags + // Should match any Component tags $pattern = "/ < - \s* - (?[A-Z][A-Za-z0-9\.]*?) - \s* + \\s* + x[-\\:](?[\\w\\-\\:\\.]*) + \\s* (? (?: - \s+ + \\s+ (?: (?: - \{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\} + \\{\\{\\s*\\\$attributes(?:[^}]+?)?\\s*\\}\\} ) | (?: - [\w\-:.@]+ + [\\w\\-:.@]+ ( = (?: \\\"[^\\\"]*\\\" | - \'[^\']*\' + \\'[^\\']*\\' | - [^\'\\\"=<>]+ + [^\\'\\\"=<>]+ ) )? ) ) )* - \s* + \\s* ) - \/> + \\/> /x"; /* @@ -96,8 +96,9 @@ class ComponentRenderer private function renderPairedTags(string $output): string { - $pattern = '/<\s*(?[A-Z][A-Za-z0-9\.]*?)(?(\s*[\w\-]+\s*=\s*(\'[^\']*\'|\"[^\"]*\"))+\s*)>(?.*)<\/\s*\1\s*>/uUsm'; - ini_set('pcre.backtrack_limit', '-1'); + // ini_set('pcre.backtrack_limit', '-1'); + $pattern = '/<\s*x[-\:](?[\w\-\:\.]*?)(?(\s*[\w\-]+\s*=\s*(\'[^\']*\'|\"[^\"]*\"))+\s*)>(?.*)<\/\s*x-\1\s*>/uiUsm'; + /* $matches[0] = full tags matched and all of its content $matches[name] = pascal cased tag name @@ -167,8 +168,6 @@ class ComponentRenderer ( \"[^\"]+\" | - \'[^\']+\' - | \\\'[^\\\']+\\\' | [^\s>]+ diff --git a/app/Libraries/ViewComponents/Decorator.php b/app/Libraries/ViewComponents/Decorator.php index 4701052f..d8e7bfb6 100644 --- a/app/Libraries/ViewComponents/Decorator.php +++ b/app/Libraries/ViewComponents/Decorator.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ViewComponents; use CodeIgniter\View\ViewDecoratorInterface; +use Override; /** * Enables rendering of View Components into the views. @@ -15,6 +16,7 @@ class Decorator implements ViewDecoratorInterface { private static ?ComponentRenderer $components = null; + #[Override] public static function decorate(string $html): string { $components = self::factory(); diff --git a/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php b/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php deleted file mode 100644 index 9c7caf7d..00000000 --- a/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php +++ /dev/null @@ -1,33 +0,0 @@ - $val) { - $atts .= ($js) ? $key . '=' . esc($val, 'js') . ',' : ' ' . $key . '="' . $val . '"'; - } - - return rtrim($atts, ','); - } -} diff --git a/app/Libraries/ViewThemes/Theme.php b/app/Libraries/ViewThemes/Theme.php index 51d69ffe..b1b9b008 100644 --- a/app/Libraries/ViewThemes/Theme.php +++ b/app/Libraries/ViewThemes/Theme.php @@ -46,7 +46,7 @@ class Theme /** * Returns the path to the specified theme folder. If no theme is provided, will use the current theme. */ - public static function path(string $theme = null): string + public static function path(?string $theme = null): string { if ($theme === null) { $theme = static::current(); diff --git a/app/Models/ActorModel.php b/app/Models/ActorModel.php index b34993d9..59369100 100644 --- a/app/Models/ActorModel.php +++ b/app/Models/ActorModel.php @@ -12,14 +12,16 @@ namespace App\Models; use App\Entities\Actor; use Modules\Fediverse\Models\ActorModel as FediverseActorModel; +use Override; class ActorModel extends FediverseActorModel { /** - * @var string + * @var class-string */ protected $returnType = Actor::class; + #[Override] public function getActorById(int $id): ?Actor { return $this->find($id); diff --git a/app/Models/CategoryModel.php b/app/Models/CategoryModel.php index 8847d2c1..2c67efeb 100644 --- a/app/Models/CategoryModel.php +++ b/app/Models/CategoryModel.php @@ -31,7 +31,7 @@ class CategoryModel extends Model protected $allowedFields = ['parent_id', 'code', 'apple_category', 'google_category']; /** - * @var string + * @var class-string */ protected $returnType = Category::class; @@ -65,12 +65,17 @@ class CategoryModel extends Model $options = array_reduce( $categories, static function (array $result, Category $category): array { - $result[$category->id] = ''; + $label = ''; if ($category->parent instanceof Category) { - $result[$category->id] = lang('Podcast.category_options.' . $category->parent->code) . ' › '; + $label = lang('Podcast.category_options.' . $category->parent->code) . ' › '; } - $result[$category->id] .= lang('Podcast.category_options.' . $category->code); + $label .= lang('Podcast.category_options.' . $category->code); + + $result[] = [ + 'value' => $category->id, + 'label' => $label, + ]; return $result; }, [], diff --git a/app/Models/ClipModel.php b/app/Models/ClipModel.php index 64583296..cc5209c9 100644 --- a/app/Models/ClipModel.php +++ b/app/Models/ClipModel.php @@ -67,8 +67,8 @@ class ClipModel extends Model public function __construct( protected string $type = 'audio', - ConnectionInterface &$db = null, - ValidationInterface $validation = null + ?ConnectionInterface &$db = null, + ?ValidationInterface $validation = null, ) { switch ($type) { case 'audio': diff --git a/app/Models/CreditModel.php b/app/Models/CreditModel.php index 021b81d6..23342d4c 100644 --- a/app/Models/CreditModel.php +++ b/app/Models/CreditModel.php @@ -21,7 +21,7 @@ class CreditModel extends Model protected $table = 'credits'; /** - * @var string + * @var class-string */ protected $returnType = Credit::class; } diff --git a/app/Models/EpisodeCommentModel.php b/app/Models/EpisodeCommentModel.php index 88202c43..60582586 100644 --- a/app/Models/EpisodeCommentModel.php +++ b/app/Models/EpisodeCommentModel.php @@ -25,7 +25,7 @@ use Modules\Fediverse\Objects\TombstoneObject; class EpisodeCommentModel extends UuidModel { /** - * @var string + * @var class-string */ protected $returnType = EpisodeComment::class; @@ -86,11 +86,13 @@ class EpisodeCommentModel extends UuidModel } if ($comment->in_reply_to_id === null) { - (new EpisodeModel())->builder() + new EpisodeModel() + ->builder() ->where('id', $comment->episode_id) ->increment('comments_count'); } else { - (new self())->builder() + new self() + ->builder() ->where('id', service('uuid')->fromString($comment->in_reply_to_id)->getBytes()) ->increment('replies_count'); } @@ -102,7 +104,7 @@ class EpisodeCommentModel extends UuidModel 'episode-comment', esc($comment->actor->username), $comment->episode->slug, - $comment->id + $comment->id, ); $createActivity = new CreateActivity(); @@ -180,7 +182,8 @@ class EpisodeCommentModel extends UuidModel ->where('id', $comment->episode_id) ->decrement('comments_count'); } else { - (new self())->builder() + new self() + ->builder() ->where('id', service('uuid')->fromString($comment->in_reply_to_id)->getBytes()) ->decrement('replies_count'); } @@ -211,7 +214,7 @@ class EpisodeCommentModel extends UuidModel $postModel = new PostModel(); $episodePostsRepliesBuilder = $postModel->builder(); $episodePostsReplies = $episodePostsRepliesBuilder->select( - 'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, replies_count, published_at as created_at, created_by, is_private, 1 as is_from_post' + 'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, replies_count, published_at as created_at, created_by, is_private, 1 as is_from_post', ) ->whereIn('in_reply_to_id', static function (BaseBuilder $builder) use (&$episodeId): BaseBuilder { return $builder->select('id') @@ -232,12 +235,12 @@ class EpisodeCommentModel extends UuidModel /** @var BaseResult $allEpisodeComments */ $allEpisodeComments = $this->db->query( - $episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC' + $episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC', ); return $this->convertUuidFieldsToStrings( $allEpisodeComments->getCustomResultObject($this->tempReturnType), - $this->tempReturnType + $this->tempReturnType, ); } diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index 238a6d15..c1ec3a02 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -87,7 +87,6 @@ class EpisodeModel extends UuidModel 'location_name', 'location_geo', 'location_osm', - 'custom_rss', 'is_published_on_hubs', 'downloads_count', 'posts_count', @@ -99,7 +98,7 @@ class EpisodeModel extends UuidModel ]; /** - * @var string + * @var class-string */ protected $returnType = Episode::class; @@ -236,8 +235,8 @@ class EpisodeModel extends UuidModel public function getPodcastEpisodes( int $podcastId, string $podcastType, - string $year = null, - string $season = null + ?string $year = null, + ?string $season = null, ): array { $cacheName = implode( '_', @@ -348,7 +347,7 @@ class EpisodeModel extends UuidModel { $result = $this->builder() ->select( - 'COUNT(DISTINCT season_number) as number_of_seasons, COUNT(*) as number_of_episodes, MIN(published_at) as first_published_at' + 'COUNT(DISTINCT season_number) as number_of_seasons, COUNT(*) as number_of_episodes, MIN(published_at) as first_published_at', ) ->where('podcast_id', $podcastId) ->where('`published_at` <= UTC_TIMESTAMP()', null, false) @@ -369,13 +368,15 @@ class EpisodeModel extends UuidModel public function resetCommentsCount(): int | false { - $episodeCommentsCount = (new EpisodeCommentModel())->builder() + $episodeCommentsCount = new EpisodeCommentModel() + ->builder() ->select('episode_id, COUNT(*) as `comments_count`') ->where('in_reply_to_id') ->groupBy('episode_id') ->getCompiledSelect(); - $episodePostsRepliesCount = (new PostModel())->builder() + $episodePostsRepliesCount = new PostModel() + ->builder() ->select('fediverse_posts.episode_id as episode_id, COUNT(*) as `comments_count`') ->join('fediverse_posts as fp', 'fediverse_posts.id = fp.in_reply_to_id') ->where('fediverse_posts.in_reply_to_id') @@ -385,13 +386,14 @@ class EpisodeModel extends UuidModel /** @var BaseResult $query */ $query = $this->db->query( - 'SELECT `episode_id` as `id`, SUM(`comments_count`) as `comments_count` FROM (' . $episodeCommentsCount . ' UNION ALL ' . $episodePostsRepliesCount . ') x GROUP BY `episode_id`' + 'SELECT `episode_id` as `id`, SUM(`comments_count`) as `comments_count` FROM (' . $episodeCommentsCount . ' UNION ALL ' . $episodePostsRepliesCount . ') x GROUP BY `episode_id`', ); $countsPerEpisodeId = $query->getResultArray(); if ($countsPerEpisodeId !== []) { - return (new self())->updateBatch($countsPerEpisodeId, 'id'); + return new self() + ->updateBatch($countsPerEpisodeId, 'id'); } return 0; @@ -430,7 +432,8 @@ class EpisodeModel extends UuidModel } /** @var ?Episode $episode */ - $episode = (new self())->find($episodeId); + $episode = new self() + ->find($episodeId); if (! $episode instanceof Episode) { return $data; @@ -481,7 +484,7 @@ class EpisodeModel extends UuidModel ') ->select("{$podcastTable}.created_at AS podcast_created_at") ->select( - "{$podcastTable}.title as podcast_title, {$podcastTable}.handle as podcast_handle, {$podcastTable}.description_markdown as podcast_description_markdown" + "{$podcastTable}.title as podcast_title, {$podcastTable}.handle as podcast_handle, {$podcastTable}.description_markdown as podcast_description_markdown", ) ->join($podcastTable, "{$podcastTable} on {$podcastTable}.id = {$episodeTable}.podcast_id") ->where(' @@ -490,7 +493,7 @@ class EpisodeModel extends UuidModel . 'OR' . $podcastModel->getFullTextMatchClauseForPodcasts($podcastTable, $query) . ') - '); + ', ); return $this->builder; } @@ -524,7 +527,8 @@ class EpisodeModel extends UuidModel } /** @var ?Episode $episode */ - $episode = (new self())->find($episodeId); + $episode = new self() + ->find($episodeId); if (! $episode instanceof Episode) { return $data; diff --git a/app/Models/LanguageModel.php b/app/Models/LanguageModel.php index bec367f1..b68a9197 100644 --- a/app/Models/LanguageModel.php +++ b/app/Models/LanguageModel.php @@ -31,7 +31,7 @@ class LanguageModel extends Model protected $allowedFields = ['code', 'native_name']; /** - * @var string + * @var class-string */ protected $returnType = Language::class; @@ -56,7 +56,10 @@ class LanguageModel extends Model $options = array_reduce( $languages, static function (array $result, Language $language): array { - $result[$language->code] = $language->native_name; + $result[] = [ + 'value' => $language->code, + 'label' => $language->native_name, + ]; return $result; }, [], diff --git a/app/Models/LikeModel.php b/app/Models/LikeModel.php index 05a69881..f3f382de 100644 --- a/app/Models/LikeModel.php +++ b/app/Models/LikeModel.php @@ -36,7 +36,7 @@ class LikeModel extends UuidModel protected $allowedFields = ['actor_id', 'comment_id']; /** - * @var string + * @var class-string */ protected $returnType = Like::class; @@ -56,7 +56,8 @@ class LikeModel extends UuidModel 'comment_id' => $comment->id, ]); - (new EpisodeCommentModel())->builder() + new EpisodeCommentModel() + ->builder() ->where('id', service('uuid')->fromString($comment->id)->getBytes()) ->increment('likes_count'); @@ -91,7 +92,8 @@ class LikeModel extends UuidModel { $this->db->transStart(); - (new EpisodeCommentModel())->builder() + new EpisodeCommentModel() + ->builder() ->where('id', service('uuid') ->fromString($comment->id) ->getBytes()) ->decrement('likes_count'); diff --git a/app/Models/PageModel.php b/app/Models/PageModel.php index ce2e4fcc..f9a9eb5a 100644 --- a/app/Models/PageModel.php +++ b/app/Models/PageModel.php @@ -31,7 +31,7 @@ class PageModel extends Model protected $allowedFields = ['id', 'title', 'slug', 'content_markdown', 'content_html']; /** - * @var string + * @var class-string */ protected $returnType = Page::class; diff --git a/app/Models/PersonModel.php b/app/Models/PersonModel.php index 14fc249e..705ad50a 100644 --- a/app/Models/PersonModel.php +++ b/app/Models/PersonModel.php @@ -39,7 +39,7 @@ class PersonModel extends Model ]; /** - * @var string + * @var class-string */ protected $returnType = Person::class; @@ -146,7 +146,10 @@ class PersonModel extends Model ->orderBy('`full_name`', 'ASC') ->findAll(), static function (array $result, Person $person): array { - $result[$person->id] = $person->full_name; + $result[] = [ + 'value' => $person->id, + 'label' => $person->full_name, + ]; return $result; }, [], @@ -174,9 +177,10 @@ class PersonModel extends Model if (! ($options = cache($cacheName))) { foreach ($personsTaxonomy as $group_key => $group) { foreach ($group['roles'] as $role_key => $role) { - $options[ - "{$group_key},{$role_key}" - ] = "{$group['label']} › {$role['label']}"; + $options[] = [ + 'value' => sprintf('%s,%s', $group_key, $role_key), + 'label' => sprintf('%s › %s', $group['label'], $role['label']), + ]; } } @@ -211,7 +215,7 @@ class PersonModel extends Model if (! ($found = cache($cacheName))) { $this->builder() ->select( - 'persons.*, episodes_persons.podcast_id as podcast_id, episodes_persons.episode_id as episode_id' + 'persons.*, episodes_persons.podcast_id as podcast_id, episodes_persons.episode_id as episode_id', ) ->distinct() ->join('episodes_persons', 'persons.id = episodes_persons.person_id') @@ -253,7 +257,7 @@ class PersonModel extends Model int $episodeId, int $personId, string $groupSlug, - string $roleSlug + string $roleSlug, ): bool { return $this->db->table('episodes_persons') ->insert([ @@ -293,9 +297,10 @@ class PersonModel extends Model cache() ->delete("podcast#{$podcastId}_persons"); - (new PodcastModel())->clearCache([ - 'id' => $podcastId, - ]); + new PodcastModel() + ->clearCache([ + 'id' => $podcastId, + ]); $data = []; foreach ($personIds as $personId) { @@ -335,9 +340,10 @@ class PersonModel extends Model cache()->deleteMatching("podcast#{$podcastId}_person#{$personId}*"); cache() ->delete("podcast#{$podcastId}_persons"); - (new PodcastModel())->clearCache([ - 'id' => $podcastId, - ]); + new PodcastModel() + ->clearCache([ + 'id' => $podcastId, + ]); return $this->db->table('podcasts_persons') ->delete([ @@ -359,9 +365,10 @@ class PersonModel extends Model if ($personIds !== []) { cache() ->delete("podcast#{$podcastId}_episode#{$episodeId}_persons"); - (new EpisodeModel())->clearCache([ - 'id' => $episodeId, - ]); + new EpisodeModel() + ->clearCache([ + 'id' => $episodeId, + ]); $data = []; foreach ($personIds as $personId) { @@ -400,9 +407,10 @@ class PersonModel extends Model cache()->deleteMatching("podcast#{$podcastId}_episode#{$episodeId}_person#{$personId}*"); cache() ->delete("podcast#{$podcastId}_episode#{$episodeId}_persons"); - (new EpisodeModel())->clearCache([ - 'id' => $episodeId, - ]); + new EpisodeModel() + ->clearCache([ + 'id' => $episodeId, + ]); return $this->db->table('episodes_persons') ->delete([ diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index f53fd567..c64516f1 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -38,8 +38,6 @@ class PodcastModel extends Model 'handle', 'description_markdown', 'description_html', - 'episode_description_footer_markdown', - 'episode_description_footer_html', 'cover_id', 'banner_id', 'language_code', @@ -47,10 +45,8 @@ class PodcastModel extends Model 'parental_advisory', 'owner_name', 'owner_email', - 'is_owner_email_removed_from_feed', 'publisher', 'type', - 'medium', 'copyright', 'imported_feed_url', 'new_feed_url', @@ -60,13 +56,7 @@ class PodcastModel extends Model 'location_name', 'location_geo', 'location_osm', - 'verify_txt', - 'payment_pointer', - 'custom_rss', 'is_published_on_hubs', - 'partner_id', - 'partner_link_url', - 'partner_image_url', 'is_premium_by_default', 'published_at', 'created_by', @@ -74,7 +64,7 @@ class PodcastModel extends Model ]; /** - * @var string + * @var class-string */ protected $returnType = Podcast::class; @@ -173,7 +163,7 @@ class PodcastModel extends Model /** * @return Podcast[] */ - public function getAllPodcasts(string $orderBy = null): array + public function getAllPodcasts(?string $orderBy = null): array { $prefix = $this->db->getPrefix(); @@ -185,7 +175,7 @@ class PodcastModel extends Model ->where( '`' . $prefix . 'fediverse_posts`.`published_at` <= UTC_TIMESTAMP()', null, - false + false, )->orWhere('fediverse_posts.published_at') ->groupEnd() ->groupBy('podcasts.actor_id') @@ -319,7 +309,8 @@ class PodcastModel extends Model ]; } - $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode($podcastId); + $secondsToNextUnpublishedEpisode = new EpisodeModel() + ->getSecondsToNextUnpublishedEpisode($podcastId); cache() ->save($cacheName, $defaultQuery, $secondsToNextUnpublishedEpisode ?: DECADE); @@ -335,7 +326,8 @@ class PodcastModel extends Model */ public function clearCache(array $data): array { - $podcast = (new self())->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); + $podcast = new self() + ->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); // delete cache for users' podcasts cache() @@ -399,21 +391,22 @@ class PodcastModel extends Model $domain = $url->getHost() . ($url->getPort() ? ':' . $url->getPort() : ''); - $actorId = (new ActorModel())->insert( - [ - 'uri' => url_to('podcast-activity', $username), - 'username' => $username, - 'domain' => $domain, - 'private_key' => $privatekey, - 'public_key' => $publickey, - 'display_name' => $data['data']['title'], - 'summary' => $data['data']['description_html'], - 'inbox_url' => url_to('inbox', $username), - 'outbox_url' => url_to('outbox', $username), - 'followers_url' => url_to('followers', $username), - ], - true, - ); + $actorId = new ActorModel() + ->insert( + [ + 'uri' => url_to('podcast-activity', $username), + 'username' => $username, + 'domain' => $domain, + 'private_key' => $privatekey, + 'public_key' => $publickey, + 'display_name' => $data['data']['title'], + 'summary' => $data['data']['description_html'], + 'inbox_url' => url_to('inbox', $username), + 'outbox_url' => url_to('outbox', $username), + 'followers_url' => url_to('followers', $username), + ], + true, + ); $data['data']['actor_id'] = $actorId; @@ -427,10 +420,12 @@ class PodcastModel extends Model */ protected function setActorAvatar(array $data): array { - $podcast = (new self())->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); + $podcast = new self() + ->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); if ($podcast instanceof Podcast) { - $podcastActor = (new ActorModel())->find($podcast->actor_id); + $podcastActor = new ActorModel() + ->find($podcast->actor_id); if (! $podcastActor instanceof Actor) { return $data; @@ -439,7 +434,8 @@ class PodcastModel extends Model $podcastActor->avatar_image_url = $podcast->cover->federation_url; $podcastActor->avatar_image_mimetype = $podcast->cover->federation_mimetype; - (new ActorModel())->update($podcast->actor_id, $podcastActor); + new ActorModel() + ->update($podcast->actor_id, $podcastActor); } return $data; @@ -452,7 +448,8 @@ class PodcastModel extends Model */ protected function updatePodcastActor(array $data): array { - $podcast = (new self())->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); + $podcast = new self() + ->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); if ($podcast instanceof Podcast) { $actorModel = new ActorModel(); @@ -488,7 +485,7 @@ class PodcastModel extends Model { if (! array_key_exists( 'guid', - $data['data'] + $data['data'], ) || $data['data']['guid'] === null || $data['data']['guid'] === '') { $uuid = service('uuid'); $feedUrl = url_to('podcast-rss-feed', $data['data']['handle']); diff --git a/app/Models/PostModel.php b/app/Models/PostModel.php index 88f41003..06b0b00a 100644 --- a/app/Models/PostModel.php +++ b/app/Models/PostModel.php @@ -16,7 +16,7 @@ use Modules\Fediverse\Models\PostModel as FediversePostModel; class PostModel extends FediversePostModel { /** - * @var string + * @var class-string */ protected $returnType = Post::class; diff --git a/app/Validation/FileRules.php b/app/Validation/FileRules.php index 579ec3ca..2a149d46 100644 --- a/app/Validation/FileRules.php +++ b/app/Validation/FileRules.php @@ -11,13 +11,15 @@ declare(strict_types=1); namespace App\Validation; use CodeIgniter\Validation\FileRules as ValidationFileRules; +use Override; class FileRules extends ValidationFileRules { /** * Checks an uploaded file to verify that the dimensions are within a specified allowable dimension. */ - public function min_dims(string $blank = null, string $params = ''): bool + #[Override] + public function min_dims(?string $blank = null, string $params = ''): bool { // Grab the file name off the top of the $params // after we split it. @@ -59,7 +61,7 @@ class FileRules extends ValidationFileRules /** * Checks an uploaded image to verify that the ratio corresponds to the params */ - public function is_image_ratio(string $blank = null, string $params = ''): bool + public function is_image_ratio(?string $blank = null, string $params = ''): bool { // Grab the file name off the top of the $params // after we split it. @@ -99,7 +101,7 @@ class FileRules extends ValidationFileRules /** * Checks that an uploaded json file's content is valid */ - public function is_json(string $blank = null, string $params = ''): bool + public function is_json(?string $blank = null, string $params = ''): bool { // Grab the file name off the top of the $params // after we split it. diff --git a/app/Validation/OtherRules.php b/app/Validation/OtherRules.php new file mode 100644 index 00000000..74782809 --- /dev/null +++ b/app/Validation/OtherRules.php @@ -0,0 +1,29 @@ + 'alert', + ]; /** * @var 'default'|'success'|'danger'|'warning' */ protected string $variant = 'default'; + #[Override] public function render(): string { - $variants = [ + $variantData = match ($this->variant) { 'success' => [ 'class' => 'text-pine-900 bg-pine-100 border-pine-300', 'glyph' => 'check-fill', // @icon("check-fill") @@ -32,30 +40,21 @@ class Alert extends Component 'class' => 'text-yellow-900 bg-yellow-100 border-yellow-300', 'glyph' => 'alert-fill', // @icon("alert-fill") ], - 'default' => [ + default => [ 'class' => 'text-blue-900 bg-blue-100 border-blue-300', 'glyph' => 'error-warning-fill', // @icon("error-warning-fill") ], - ]; + }; - if (! array_key_exists($this->variant, $variants)) { - $this->variant = 'default'; - } - - $glyph = icon(($this->glyph ?? $variants[$this->variant]['glyph']), [ + $glyph = icon(($this->glyph === '' ? $variantData['glyph'] : $this->glyph), [ 'class' => 'flex-shrink-0 mr-2 text-lg', ]); - $title = $this->title === null ? '' : '
' . $this->title . '
'; - $class = 'inline-flex w-full p-2 text-sm border rounded ' . $variants[$this->variant]['class'] . ' ' . $this->class; - - unset($this->attributes['slot']); - unset($this->attributes['variant']); - unset($this->attributes['class']); - unset($this->attributes['glyph']); - $attributes = stringify_attributes($this->attributes); + $title = $this->title === '' ? '' : '
' . $this->title . '
'; + $this->mergeClass('inline-flex w-full p-2 text-sm border rounded '); + $this->mergeClass($variantData['class']); return <<{$glyph}
{$title}

{$this->slot}

+
getStringifiedAttributes()}>{$glyph}
{$title}

{$this->slot}

HTML; } } diff --git a/app/Views/Components/Button.php b/app/Views/Components/Button.php index 4d024339..90d2ca63 100644 --- a/app/Views/Components/Button.php +++ b/app/Views/Components/Button.php @@ -4,14 +4,25 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class Button extends Component { + protected array $props = ['uri', 'variant', 'size', 'iconLeft', 'iconRight', 'isSquared', 'isExternal']; + + protected array $casts = [ + 'isSquared' => 'boolean', + 'isExternal' => 'boolean', + ]; + protected string $uri = ''; protected string $variant = 'default'; + /** + * @var 'small'|'base'|'large' + */ protected string $size = 'base'; protected string $iconLeft = ''; @@ -20,65 +31,54 @@ class Button extends Component protected bool $isSquared = false; - public function setIsSquared(string $value): void - { - $this->isSquared = $value === 'true'; - } + protected bool $isExternal = false; + #[Override] public function render(): string { - $baseClass = - 'gap-x-2 flex-shrink-0 inline-flex items-center justify-center font-semibold rounded-full focus:ring-accent'; + $this->mergeClass('shadow gap-x-2 flex-shrink-0 inline-flex items-center justify-center font-semibold rounded-full'); - $variantClass = [ - 'default' => 'shadow-sm text-black bg-gray-300 hover:bg-gray-400', - 'primary' => 'shadow-sm text-accent-contrast bg-accent-base hover:bg-accent-hover', - 'secondary' => 'shadow-sm border-2 border-accent-base text-accent-base bg-white hover:border-accent-hover hover:text-accent-hover', - 'success' => 'shadow-sm text-white bg-pine-500 hover:bg-pine-800', - 'danger' => 'shadow-sm text-white bg-red-600 hover:bg-red-700', - 'warning' => 'shadow-sm text-black bg-yellow-500 hover:bg-yellow-600', - 'info' => 'shadow-sm text-white bg-blue-500 hover:bg-blue-600', - 'disabled' => 'shadow-sm text-black bg-gray-300 cursor-not-allowed', - ]; + $variantClass = match ($this->variant) { + 'primary' => 'text-accent-contrast bg-accent-base hover:bg-accent-hover', + 'secondary' => 'ring-2 ring-accent-base ring-inset text-accent-base bg-white hover:border-accent-hover hover:text-accent-hover hover:ring-accent-hover', + 'danger' => 'bg-red-50 ring-2 ring-red-700 ring-inset text-red-700 hover:ring-red-800 hover:text-red-800', + 'warning' => 'bg-yellow-50 ring-2 ring-yellow-700 ring-inset text-yellow-700 hover:ring-yellow-800 hover:text-yellow-800', + 'info' => 'bg-blue-50 ring-2 ring-blue-700 ring-inset text-blue-700 hover:ring-blue-800 hover:text-blue-800', + 'disabled' => 'text-black bg-gray-300 cursor-not-allowed', + default => 'text-black bg-gray-50 hover:bg-gray-200', + }; - $sizeClass = [ + $sizeClass = match ($this->size) { 'small' => 'text-xs leading-6', - 'base' => 'text-sm leading-5', 'large' => 'text-base leading-6', - ]; + default => 'text-sm leading-5', + }; - $iconSize = [ + $iconSizeClass = match ($this->size) { 'small' => 'text-sm', - 'base' => 'text-lg', 'large' => 'text-2xl', - ]; + default => 'text-lg', + }; - $basePaddings = [ + $basePaddings = match ($this->size) { 'small' => 'px-3 py-1', - 'base' => 'px-3 py-2', 'large' => 'px-4 py-2', - ]; + default => 'px-3 py-2', + }; - $squaredPaddings = [ + $squaredPaddings = match ($this->size) { 'small' => 'p-1', - 'base' => 'p-2', 'large' => 'p-3', - ]; + default => 'p-2', + }; - $buttonClass = - $baseClass . - ' ' . - ($this->isSquared - ? $squaredPaddings[$this->size] - : $basePaddings[$this->size]) . - ' ' . - $sizeClass[$this->size] . - ' ' . - $variantClass[$this->variant]; + $this->mergeClass($variantClass); + $this->mergeClass($sizeClass); - if (array_key_exists('class', $this->attributes)) { - $buttonClass .= ' ' . $this->attributes['class']; - unset($this->attributes['class']); + if ($this->isSquared) { + $this->mergeClass($squaredPaddings); + } else { + $this->mergeClass($basePaddings); } if ($this->iconLeft !== '' || $this->iconRight !== '') { @@ -87,41 +87,30 @@ class Button extends Component if ($this->iconLeft !== '') { $this->slot = icon($this->iconLeft, [ - 'class' => 'opacity-75 ' . $iconSize[$this->size], + 'class' => 'opacity-75 ' . $iconSizeClass, ]) . $this->slot; } if ($this->iconRight !== '') { $this->slot .= icon($this->iconRight, [ - 'class' => 'opacity-75 ' . $iconSize[$this->size], + 'class' => 'opacity-75 ' . $iconSizeClass, ]); } - unset($this->attributes['slot']); - unset($this->attributes['variant']); - unset($this->attributes['size']); - unset($this->attributes['iconLeft']); - unset($this->attributes['iconRight']); - unset($this->attributes['isSquared']); - unset($this->attributes['uri']); - unset($this->attributes['label']); - if ($this->uri !== '') { $tagName = 'a'; - $defaultButtonAttributes = [ - 'href' => $this->uri, - ]; + $this->attributes['href'] = $this->uri; + if ($this->isExternal) { + $this->attributes['target'] = '_blank'; + $this->attributes['rel'] = 'noopener noreferrer'; + } } else { $tagName = 'button'; - $defaultButtonAttributes = [ - 'type' => 'button', - ]; + $this->attributes['type'] ??= 'button'; } - $attributes = stringify_attributes(array_merge($defaultButtonAttributes, $this->attributes)); - return <<{$this->slot} + <{$tagName} {$this->getStringifiedAttributes()}>{$this->slot} HTML; } } diff --git a/app/Views/Components/Charts/ChartsComponent.php b/app/Views/Components/Charts/ChartsComponent.php index a4c4a575..7d680f5a 100644 --- a/app/Views/Components/Charts/ChartsComponent.php +++ b/app/Views/Components/Charts/ChartsComponent.php @@ -4,18 +4,22 @@ declare(strict_types=1); namespace App\Views\Components\Charts; +use Override; use ViewComponents\Component; class ChartsComponent extends Component { - protected string $title = ''; + protected array $props = ['title', 'subtitle', 'dataUrl', 'type']; + + protected string $title; protected string $subtitle = ''; - protected string $dataUrl = ''; + protected string $dataUrl; - protected string $type = ''; + protected string $type; + #[Override] public function render(): string { $subtitleBlock = ''; @@ -23,8 +27,10 @@ class ChartsComponent extends Component $subtitleBlock = '

' . $this->subtitle . '

'; } + $this->mergeClass('bg-elevated border-3 rounded-xl border-subtle'); + return << +
getStringifiedAttributes()}>

{$this->title}

{$subtitleBlock}
diff --git a/app/Views/Components/DashboardCard.php b/app/Views/Components/DashboardCard.php index 69ab183f..74c7d519 100644 --- a/app/Views/Components/DashboardCard.php +++ b/app/Views/Components/DashboardCard.php @@ -4,11 +4,14 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class DashboardCard extends Component { - protected ?string $href = null; + protected array $props = ['href', 'glyph', 'title', 'subtitle']; + + protected string $href = ''; protected string $glyph; @@ -21,17 +24,18 @@ class DashboardCard extends Component $this->subtitle = html_entity_decode($value); } + #[Override] public function render(): string { $glyph = (string) icon($this->glyph, [ 'class' => 'flex-shrink-0 bg-base rounded-full w-8 h-8 p-2 text-accent-base', ]); - if ($this->href !== null && $this->href !== '') { - $chevronRight = (string) icon('arrow-right-s-fill'); + if ($this->href !== '') { + $chevronRight = icon('arrow-right-s-fill'); $viewLang = lang('Common.view'); return << +
{$glyph}
{$this->title}
{$viewLang}{$chevronRight}

{$this->subtitle}

{$this->slot}
diff --git a/app/Views/Components/DropdownMenu.php b/app/Views/Components/DropdownMenu.php index 8c062311..992ef22e 100644 --- a/app/Views/Components/DropdownMenu.php +++ b/app/Views/Components/DropdownMenu.php @@ -5,27 +5,37 @@ declare(strict_types=1); namespace App\Views\Components; use Exception; +use Override; use ViewComponents\Component; class DropdownMenu extends Component { - public string $id = ''; + protected array $props = ['id', 'labelledby', 'placement', 'offsetX', 'offsetY', 'items']; - public string $labelledby; + protected array $casts = [ + 'offsetX' => 'number', + 'offsetY' => 'number', + 'items' => 'array', + ]; - public string $placement = 'bottom-end'; + protected string $id; - public string $offsetX = '0'; + protected string $labelledby; - public string $offsetY = '0'; + protected string $placement = 'bottom-end'; - public array $items = []; + protected int $offsetX = 0; + + protected int $offsetY = 0; + + protected array $items = []; public function setItems(string $value): void { $this->items = json_decode(htmlspecialchars_decode($value), true); } + #[Override] public function render(): string { if ($this->items === []) { @@ -37,7 +47,7 @@ class DropdownMenu extends Component switch ($item['type']) { case 'link': $menuItems .= anchor($item['uri'], $item['title'], [ - 'class' => 'px-4 py-1 hover:bg-highlight focus:ring-accent focus:ring-inset' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''), + 'class' => 'inline-flex gap-x-1 items-center px-4 py-1 hover:bg-highlight' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''), ]); break; case 'html': @@ -51,14 +61,16 @@ class DropdownMenu extends Component } } + $this->mergeClass('absolute flex flex-col py-2 rounded-lg z-60 whitespace-nowrap text-skin-base border-contrast bg-elevated border-3'); + $this->attributes['id'] = $this->id; + $this->attributes['aria-labelledby'] = $this->labelledby; + $this->attributes['data-dropdown'] = 'menu'; + $this->attributes['data-dropdown-placement'] = $this->placement; + $this->attributes['data-dropdown-offset-x'] = $this->offsetX; + $this->attributes['data-dropdown-offset-y'] = $this->offsetY; + return <<{$menuItems} + HTML; } } diff --git a/app/Views/Components/Forms/Checkbox.php b/app/Views/Components/Forms/Checkbox.php index 1d81acb8..08c94bc5 100644 --- a/app/Views/Components/Forms/Checkbox.php +++ b/app/Views/Components/Forms/Checkbox.php @@ -4,35 +4,59 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use App\Views\Components\Hint; +use Override; + class Checkbox extends FormComponent { - protected ?string $hint = null; + protected array $props = ['hint', 'helper']; - protected bool $isChecked = false; + protected array $casts = [ + 'isChecked' => 'boolean', + ]; - public function setIsChecked(string $value): void - { - $this->isChecked = $value === 'true'; - } + protected string $hint = ''; + protected string $helper = ''; + + #[Override] public function render(): string { - $attributes = [ - 'id' => $this->value, - 'name' => $this->name, - 'class' => 'form-checkbox bg-elevated text-accent-base border-contrast border-3 focus:ring-accent w-6 h-6', - ]; - $checkboxInput = form_checkbox( - $attributes, + [ + 'id' => $this->id, + 'name' => $this->name, + 'class' => 'form-checkbox bg-elevated text-accent-base border-contrast border-3 focus:ring-accent w-6 h-6 transition', + ], 'yes', - old($this->name) ? old($this->name) === $this->value : $this->isChecked, + in_array($this->getValue(), ['yes', 'true', 'on', '1'], true), ); - $hint = $this->hint === null ? '' : hint_tooltip($this->hint, 'ml-1'); + $hint = $this->hint === '' ? '' : new Hint([ + 'class' => 'ml-1', + 'slot' => $this->hint, + ])->render(); + + $this->mergeClass('inline-flex items-start gap-x-2'); + + $helperText = ''; + if ($this->helper !== '') { + $helperId = $this->name . 'Help'; + $helperText = new Helper([ + 'id' => $helperId, + 'slot' => $this->helper, + 'class' => '-mt-1', + ])->render(); + $this->attributes['aria-describedby'] = $helperId; + } return <<{$checkboxInput}{$this->slot}{$hint} + HTML; } } diff --git a/app/Views/Components/Forms/CodeEditor.php b/app/Views/Components/Forms/CodeEditor.php new file mode 100644 index 00000000..ab20988f --- /dev/null +++ b/app/Views/Components/Forms/CodeEditor.php @@ -0,0 +1,35 @@ + '6', + 'class' => 'bg-elevated w-full rounded-lg border-3 border-contrast focus:border-contrast focus-within:ring-accent transition', + ]; + + protected string $lang = ''; + + public function setValue(string $value): void + { + $this->value = htmlspecialchars_decode($value); + } + + #[Override] + public function render(): string + { + $this->attributes['slot'] = 'textarea'; + $textarea = form_textarea($this->attributes, $this->getValue()); + + return <<{$textarea} + HTML; + } +} diff --git a/app/Views/Components/Forms/ColorRadioButton.php b/app/Views/Components/Forms/ColorRadioButton.php index ab2fb16f..f1ff3fde 100644 --- a/app/Views/Components/Forms/ColorRadioButton.php +++ b/app/Views/Components/Forms/ColorRadioButton.php @@ -4,17 +4,19 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class ColorRadioButton extends FormComponent { - protected bool $isChecked = false; + protected array $props = ['isSelected']; - protected string $style = ''; + protected array $casts = [ + 'isSelected' => 'boolean', + ]; - public function setIsChecked(string $value): void - { - $this->isChecked = $value === 'true'; - } + protected bool $isSelected = false; + #[Override] public function render(): string { $data = [ @@ -23,18 +25,18 @@ class ColorRadioButton extends FormComponent 'class' => 'color-radio-btn', ]; - if ($this->required) { + if ($this->isRequired) { $data['required'] = 'required'; } $radioInput = form_radio( $data, $this->value, - old($this->name) ? old($this->name) === $this->value : $this->isChecked, + old($this->name) ? old($this->name) === $this->value : $this->isSelected, ); return << +
getStringifiedAttributes()}> {$radioInput}
diff --git a/app/Views/Components/Forms/DatetimePicker.php b/app/Views/Components/Forms/DatetimePicker.php index f2d33332..1267ffef 100644 --- a/app/Views/Components/Forms/DatetimePicker.php +++ b/app/Views/Components/Forms/DatetimePicker.php @@ -4,23 +4,34 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class DatetimePicker extends FormComponent { + protected array $attributes = [ + 'data-picker' => 'datetime', + ]; + + #[Override] public function render(): string { - $this->attributes['class'] = 'rounded-l-lg border-0 border-rounded-r-none flex-1 focus:ring-0'; - $this->attributes['data-input'] = ''; - $dateInput = form_input($this->attributes, old($this->name, $this->value)); + $dateInput = form_input([ + 'name' => $this->name, + 'class' => 'rounded-l-lg border-0 border-rounded-r-none flex-1 focus:ring-0', + 'data-input' => '', + ], $this->getValue()); $clearLabel = lang( 'Episode.publish_form.scheduled_publication_date_clear', ); $closeIcon = icon('close-fill'); + $this->mergeClass('flex border-3 rounded-lg border-contrast focus-within:ring-accent transition'); + return << +
getStringifiedAttributes()}> {$dateInput} -
diff --git a/app/Views/Components/Forms/Field.php b/app/Views/Components/Forms/Field.php index 40d219cb..0efc0d09 100644 --- a/app/Views/Components/Forms/Field.php +++ b/app/Views/Components/Forms/Field.php @@ -4,51 +4,80 @@ declare(strict_types=1); namespace App\Views\Components\Forms; -class Field extends FormComponent +use Override; +use ViewComponents\Component; + +class Field extends Component { + protected array $props = [ + 'name', + 'label', + 'isRequired', + 'isReadonly', + 'as', + 'hint', + 'helper', + ]; + + protected array $casts = [ + 'isRequired' => 'boolean', + 'isReadonly' => 'boolean', + ]; + + protected string $name; + + protected string $label; + + protected bool $isRequired = false; + + protected bool $isReadonly = false; + protected string $as = 'Input'; - protected string $label = ''; + protected string $hint = ''; - protected ?string $helper = null; - - protected ?string $hint = null; + protected string $helper = ''; + #[Override] public function render(): string { $helperText = ''; - if ($this->helper !== null) { - $helperId = $this->id . 'Help'; - $helperText = '' . $this->helper . ''; + if ($this->helper !== '') { + $helperId = $this->name . 'Help'; + $helperText = new Helper([ + 'id' => $helperId, + 'slot' => $this->helper, + ])->render(); $this->attributes['aria-describedby'] = $helperId; } $labelAttributes = [ - 'for' => $this->id, - 'isOptional' => $this->required ? 'false' : 'true', + 'for' => $this->name, + 'isOptional' => $this->isRequired ? 'false' : 'true', 'class' => '-mb-1', + 'slot' => $this->label, ]; - if ($this->hint) { + if ($this->hint !== '') { $labelAttributes['hint'] = $this->hint; } - $labelAttributes = stringify_attributes($labelAttributes); + $label = new Label($labelAttributes); - // remove field specific attributes to inject the rest to Form Component - $fieldComponentAttributes = $this->attributes; - unset($fieldComponentAttributes['as']); - unset($fieldComponentAttributes['label']); - unset($fieldComponentAttributes['class']); - unset($fieldComponentAttributes['helper']); - unset($fieldComponentAttributes['hint']); + $this->mergeClass('flex flex-col'); + $fieldClass = $this->attributes['class']; + unset($this->attributes['class']); + + $this->attributes['name'] = $this->name; + $this->attributes['isRequired'] = var_export($this->isRequired, true); + $this->attributes['isReadonly'] = var_export($this->isReadonly, true); $element = __NAMESPACE__ . '\\' . $this->as; - $fieldElement = new $element($fieldComponentAttributes); + $fieldElement = new $element($this->attributes); return << - {$this->label} +
+ {$label->render()} {$helperText} -
+
{$fieldElement->render()}
diff --git a/app/Views/Components/Forms/FormComponent.php b/app/Views/Components/Forms/FormComponent.php index f854fb64..bcc46b26 100644 --- a/app/Views/Components/Forms/FormComponent.php +++ b/app/Views/Components/Forms/FormComponent.php @@ -6,52 +6,77 @@ namespace App\Views\Components\Forms; use ViewComponents\Component; -class FormComponent extends Component +abstract class FormComponent extends Component { - protected ?string $id = null; + protected array $props = [ + 'id', + 'name', + 'value', + 'defaultValue', + 'isRequired', + 'isReadonly', + ]; - protected string $name = ''; + protected array $casts = [ + 'isRequired' => 'boolean', + 'isReadonly' => 'boolean', + ]; - protected string $value = ''; + protected string $id; - protected bool $required = false; + protected string $name; - protected bool $readonly = false; + /** + * @var string|string[]|null + */ + protected string|array|null $value = null; + + /** + * @var string|string[]|null + */ + protected string|array|null $defaultValue = null; + + protected bool $isRequired = false; + + protected bool $isReadonly = false; /** * @param array $attributes */ public function __construct(array $attributes) { + $parentVars = get_class_vars(self::class); + $this->casts = [...$parentVars['casts'], ...$this->casts]; + $this->props = [...$parentVars['props'], $this->props]; + parent::__construct($attributes); - if ($this->id === null) { + if (! isset($this->id)) { $this->id = $this->name; - $this->attributes['id'] = $this->id; } - } - public function setValue(string $value): void - { - $this->value = htmlspecialchars_decode($value, ENT_QUOTES); - } + $this->attributes['id'] = $this->id; + $this->attributes['name'] = $this->name; - public function setRequired(string $value): void - { - $this->required = $value === 'true'; - unset($this->attributes['required']); - if ($this->required) { + if ($this->isRequired) { $this->attributes['required'] = 'required'; } - } - public function setReadonly(string $value): void - { - $this->readonly = $value === 'true'; - if ($this->readonly) { + if ($this->isReadonly) { $this->attributes['readonly'] = 'readonly'; - } else { - unset($this->attributes['readonly']); } } + + protected function getValue(): string|array + { + $valueCast = $this->casts['value'] ?? ''; + if ($valueCast === 'array') { + return old($this->name, in_array($this->value, [[], null], true) ? $this->defaultValue : $this->value) ?? []; + } + + return old( + $this->name, + in_array($this->value, ['', null], true) ? $this->defaultValue : $this->value, + ) ?? ''; + } } diff --git a/app/Views/Components/Forms/Helper.php b/app/Views/Components/Forms/Helper.php index f1702573..fbc895d1 100644 --- a/app/Views/Components/Forms/Helper.php +++ b/app/Views/Components/Forms/Helper.php @@ -4,19 +4,20 @@ declare(strict_types=1); namespace App\Views\Components\Forms; -class Helper extends FormComponent -{ - /** - * @var 'default'|'error' - */ - protected string $type = 'default'; +use Override; +use ViewComponents\Component; +class Helper extends Component +{ + // TODO: add type with error and show errors inline + + #[Override] public function render(): string { - $class = 'text-skin-muted'; + $this->mergeClass('form-helper'); return <<{$this->slot} + getStringifiedAttributes()}>{$this->slot} HTML; } } diff --git a/app/Views/Components/Forms/Input.php b/app/Views/Components/Forms/Input.php index 6ba0ffdb..a45d3224 100644 --- a/app/Views/Components/Forms/Input.php +++ b/app/Views/Components/Forms/Input.php @@ -4,28 +4,33 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class Input extends FormComponent { + protected array $props = ['type']; + protected string $type = 'text'; + #[Override] public function render(): string { - $baseClass = 'w-full border-contrast rounded-lg focus:border-contrast border-3 focus:ring-accent focus-within:ring-accent ' . $this->class; - - $this->attributes['class'] = $baseClass; + $this->mergeClass('w-full border-contrast rounded-lg focus:border-contrast border-3 focus-within:ring-accent transition'); if ($this->type === 'file') { - $this->attributes['class'] .= ' file:px-3 file:py-2 file:h-[40px] file:font-semibold file:text-skin-muted file:text-sm file:rounded-none file:border-none file:bg-highlight file:cursor-pointer'; + $this->mergeClass('file:px-3 file:py-2 file:h-[40px] file:font-semibold file:text-accent-hover file:text-sm file:rounded-none file:border-none file:bg-base file:cursor-pointer'); } else { - $this->attributes['class'] .= ' px-3 py-2'; + $this->mergeClass('px-3 py-2'); } - if ($this->readonly) { - $this->attributes['class'] .= ' bg-base'; + if ($this->isReadonly) { + $this->mergeClass('bg-base'); } else { - $this->attributes['class'] .= ' bg-elevated'; + $this->mergeClass('bg-elevated'); } - return form_input($this->attributes, old($this->name, $this->value)); + $this->attributes['type'] = $this->type; + + return form_input($this->attributes, $this->getValue()); } } diff --git a/app/Views/Components/Forms/Label.php b/app/Views/Components/Forms/Label.php index 1c7ef093..3f8af6f6 100644 --- a/app/Views/Components/Forms/Label.php +++ b/app/Views/Components/Forms/Label.php @@ -4,39 +4,42 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use App\Views\Components\Hint; +use Override; use ViewComponents\Component; class Label extends Component { - protected ?string $for = null; + protected array $props = ['for', 'hint', 'isOptional']; - protected ?string $hint = null; + protected array $casts = [ + 'isOptional' => 'boolean', + ]; + + protected string $for; + + protected string $hint = ''; protected bool $isOptional = false; - public function setIsOptional(string $value): void - { - $this->isOptional = $value === 'true'; - } - + #[Override] public function render(): string { - $labelClass = 'text-sm font-semibold ' . $this->attributes['class']; - unset($this->attributes['class']); + $this->mergeClass('text-sm font-semibold'); $optionalText = $this->isOptional ? '(' . lang('Common.optional') . ')' : ''; - $hint = $this->hint === null ? '' : hint_tooltip($this->hint, 'ml-1'); - unset($this->attributes['isOptional']); - unset($this->attributes['hint']); - unset($this->attributes['slot']); + $hint = $this->hint === '' ? '' : new Hint([ + 'class' => 'ml-1', + 'slot' => $this->hint, + ])->render(); - $attributes = stringify_attributes($this->attributes); + $this->attributes['for'] = $this->for; return <<{$this->slot}{$optionalText}{$hint} + HTML; } } diff --git a/app/Views/Components/Forms/MarkdownEditor.php b/app/Views/Components/Forms/MarkdownEditor.php index c004ef2d..0549fd05 100644 --- a/app/Views/Components/Forms/MarkdownEditor.php +++ b/app/Views/Components/Forms/MarkdownEditor.php @@ -4,8 +4,12 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class MarkdownEditor extends FormComponent { + protected array $props = ['disallowList']; + /** * @var string[] */ @@ -16,20 +20,23 @@ class MarkdownEditor extends FormComponent $this->disallowList = explode(',', $value); } + #[Override] public function render(): string { - $editorClass = 'w-full flex flex-col bg-elevated border-3 border-contrast rounded-lg overflow-hidden focus-within:ring-accent ' . $this->class; + $this->mergeClass('w-full flex flex-col bg-elevated border-3 border-contrast rounded-lg overflow-hidden focus-within:ring-accent transition'); + $wrapperClass = $this->attributes['class']; $this->attributes['class'] = 'bg-elevated border-none focus:border-none focus:outline-none focus:ring-0 w-full h-full'; $this->attributes['rows'] = 6; - $textarea = form_textarea($this->attributes, old($this->name, $this->value)); - $markdownIcon = (string) icon( - 'markdown-fill', - [ - 'class' => 'mr-1 text-lg opacity-40', - ] + $textarea = form_textarea( + $this->attributes, + $this->getValue(), ); + $markdownIcon = (string) icon('markdown-fill', [ + 'class' => 'mr-1 text-lg opacity-40', + ]); + $translations = [ 'write' => lang('Common.forms.editor.write'), 'preview' => lang('Common.forms.editor.preview'), @@ -85,19 +92,19 @@ class MarkdownEditor extends FormComponent $toolbarContent .= '
'; foreach ($buttonsGroup as $button) { if (! in_array($button['name'], $this->disallowList, true)) { - $toolbarContent .= '<' . $button['tag'] . ' class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">' . $button['icon'] . ''; + $toolbarContent .= '<' . $button['tag'] . ' class="opacity-50 hover:opacity-100 focus:opacity-100">' . $button['icon'] . ''; } } $toolbarContent .= '
'; } return << +
- - + + {$toolbarContent}
diff --git a/app/Views/Components/Forms/MultiSelect.php b/app/Views/Components/Forms/MultiSelect.php deleted file mode 100644 index 81450a32..00000000 --- a/app/Views/Components/Forms/MultiSelect.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ - protected array $options = []; - - /** - * @var string[] - */ - protected array $selected = []; - - public function setOptions(string $value): void - { - $this->options = json_decode(htmlspecialchars_decode($value), true); - } - - public function setSelected(string $selected): void - { - $this->selected = json_decode(htmlspecialchars_decode($selected), true); - } - - public function render(): string - { - $defaultAttributes = [ - 'data-class' => $this->attributes['class'], - 'multiple' => 'multiple', - 'data-select-text' => lang('Common.forms.multiSelect.selectText'), - 'data-loading-text' => lang('Common.forms.multiSelect.loadingText'), - 'data-no-results-text' => lang('Common.forms.multiSelect.noResultsText'), - 'data-no-choices-text' => lang('Common.forms.multiSelect.noChoicesText'), - 'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'), - ]; - $this->attributes['class'] .= ' w-full bg-elevated border-3 border-contrast rounded-lg'; - $extra = array_merge($defaultAttributes, $this->attributes); - - return form_dropdown($this->name, $this->options, $this->selected, $extra); - } -} diff --git a/app/Views/Components/Forms/PermalinkEditor.php b/app/Views/Components/Forms/PermalinkEditor.php new file mode 100644 index 00000000..26cf31c9 --- /dev/null +++ b/app/Views/Components/Forms/PermalinkEditor.php @@ -0,0 +1,41 @@ +mergeClass('flex-1 text-xs border-contrast rounded-lg focus:border-contrast border-3 focus-within:ring-accent transition'); + + $this->attributes['slot'] = 'slug-input'; + $input = form_input($this->attributes, $this->getValue()); + + $editLabel = lang('Common.edit'); + $copyLabel = lang('Common.copy'); + $copiedLabel = lang('Common.copied'); + + return << + {$this->label} + + {$this->prefix} + {$input} + +
+ HTML; + } +} diff --git a/app/Views/Components/Forms/Radio.php b/app/Views/Components/Forms/Radio.php index 72bd6273..beac8525 100644 --- a/app/Views/Components/Forms/Radio.php +++ b/app/Views/Components/Forms/Radio.php @@ -4,29 +4,35 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class Radio extends FormComponent { + protected array $props = ['isChecked']; + + protected array $casts = [ + 'isChecked' => 'boolean', + ]; + protected bool $isChecked = false; - public function setIsChecked(string $value): void - { - $this->isChecked = $value === 'true'; - } - + #[Override] public function render(): string { $radioInput = form_radio( [ 'id' => $this->value, 'name' => $this->name, - 'class' => 'text-accent-base bg-elevated border-contrast border-3 focus:ring-accent w-6 h-6', + 'class' => 'text-accent-base bg-elevated border-contrast border-3 focus:ring-accent w-6 h-6 transition', ], - $this->value, + $this->getValue(), old($this->name) ? old($this->name) === $this->value : $this->isChecked, ); + $this->mergeClass('inline-flex items-center'); + return <<{$radioInput}{$this->slot} + HTML; } } diff --git a/app/Views/Components/Forms/RadioButton.php b/app/Views/Components/Forms/RadioButton.php index 550cb823..9f470b33 100644 --- a/app/Views/Components/Forms/RadioButton.php +++ b/app/Views/Components/Forms/RadioButton.php @@ -4,17 +4,21 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class RadioButton extends FormComponent { - protected bool $isChecked = false; + protected array $props = ['isSelected', 'description']; - protected ?string $hint = null; + protected array $casts = [ + 'isSelected' => 'boolean', + ]; - public function setIsChecked(string $value): void - { - $this->isChecked = $value === 'true'; - } + protected bool $isSelected = false; + protected string $description = ''; + + #[Override] public function render(): string { $data = [ @@ -23,22 +27,34 @@ class RadioButton extends FormComponent 'class' => 'form-radio-btn bg-elevated', ]; - if ($this->required) { + if ($this->isRequired) { $data['required'] = 'required'; } + $this->mergeClass('relative w-full'); + + $descriptionText = ''; + if ($this->description !== '') { + $describerId = $this->name . 'Help'; + $descriptionText = <<{$this->description} + HTML; + $data['aria-describedby'] = $describerId; + } + $radioInput = form_radio( $data, - $this->value, - old($this->name) ? old($this->name) === $this->value : $this->isChecked, + $this->getValue(), + old($this->name) ? old($this->name) === $this->value : $this->isSelected, ); - $hint = $this->hint ? hint_tooltip($this->hint, 'ml-1 text-base') : ''; - return << +
getStringifiedAttributes()}"> {$radioInput} - +
HTML; } diff --git a/app/Views/Components/Forms/RadioGroup.php b/app/Views/Components/Forms/RadioGroup.php new file mode 100644 index 00000000..776ff8f2 --- /dev/null +++ b/app/Views/Components/Forms/RadioGroup.php @@ -0,0 +1,70 @@ + 'array', + ]; + + protected string $label; + + /** + * @var array{value:string,label:string,hint?:string} + */ + protected array $options = []; + + protected string $hint = ''; + + protected string $helper = ''; + + #[Override] + public function render(): string + { + $this->mergeClass('flex flex-col'); + $options = ''; + foreach ($this->options as $option) { + $radioButtonData = [ + 'value' => $option['value'], + 'name' => $this->name, + 'slot' => $option['label'], + 'description' => $option['description'] ?? '', + 'isSelected' => var_export($this->getValue() === '' ? $option['value'] === $this->options[0]['value'] : $option['value'] === $this->getValue(), true), + 'isRequired' => var_export($this->isRequired, true), + ]; + + $options .= new RadioButton($radioButtonData)->render(); + } + + $helperText = ''; + if ($this->helper !== '') { + $helperId = $this->name . 'Help'; + $helperText = new Helper([ + 'id' => $helperId, + 'slot' => $this->helper, + ])->render(); + $this->attributes['aria-describedby'] = $helperId; + } + + $hint = $this->hint === '' ? '' : new Hint([ + 'class' => 'ml-1', + 'slot' => $this->hint, + ])->render(); + + return <<getStringifiedAttributes()}> + {$this->label}{$hint} + {$helperText} +
{$options}
+ + HTML; + } +} diff --git a/app/Views/Components/Forms/Section.php b/app/Views/Components/Forms/Section.php index f28237d1..d50baaed 100644 --- a/app/Views/Components/Forms/Section.php +++ b/app/Views/Components/Forms/Section.php @@ -4,21 +4,27 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; use ViewComponents\Component; class Section extends Component { - protected string $title = ''; + protected array $props = ['title', 'subtitle']; - protected ?string $subtitle = null; + protected string $title; + protected string $subtitle = ''; + + #[Override] public function render(): string { - $subtitle = $this->subtitle === null ? '' : '

' . $this->subtitle . '

'; + $subtitle = $this->subtitle === '' ? '' : '

' . $this->subtitle . '

'; + + $this->mergeClass('w-full p-4 sm:p-6 md:p-8 bg-elevated border-3 flex flex-col items-start border-subtle rounded-xl'); return << - {$this->title} +
getStringifiedAttributes()}> + {$this->title} {$subtitle}
{$this->slot}
diff --git a/app/Views/Components/Forms/Select.php b/app/Views/Components/Forms/Select.php index 8f112c90..b62c6942 100644 --- a/app/Views/Components/Forms/Select.php +++ b/app/Views/Components/Forms/Select.php @@ -4,36 +4,44 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class Select extends FormComponent { + protected array $props = ['options']; + + protected array $casts = [ + 'options' => 'array', + ]; + /** - * @var array + * @var array> */ protected array $options = []; - protected string $selected = ''; - - public function setOptions(string $value): void - { - $this->options = json_decode(htmlspecialchars_decode($value), true); - } - + #[Override] public function render(): string { + $this->mergeClass('w-full focus:border-contrast border-3 rounded-lg bg-elevated border-contrast'); $defaultAttributes = [ - 'class' => 'w-full focus:border-contrast focus:ring-accent border-3 rounded-lg bg-elevated border-contrast ' . $this->class, - 'data-class' => $this->class, 'data-select-text' => lang('Common.forms.multiSelect.selectText'), 'data-loading-text' => lang('Common.forms.multiSelect.loadingText'), 'data-no-results-text' => lang('Common.forms.multiSelect.noResultsText'), 'data-no-choices-text' => lang('Common.forms.multiSelect.noChoicesText'), 'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'), ]; - unset($this->attributes['name']); - unset($this->attributes['options']); - unset($this->attributes['selected']); - $extra = [...$this->attributes, ...$defaultAttributes]; + $this->attributes = [...$defaultAttributes, ...$this->attributes]; - return form_dropdown($this->name, $this->options, old($this->name, $this->selected !== '' ? [$this->selected] : []), $extra); + $options = ''; + $selected = $this->getValue(); + foreach ($this->options as $option) { + $options .= ''; + } + + $this->attributes['name'] = $this->name; + + return <<getStringifiedAttributes()}>{$options} + HTML; } } diff --git a/app/Views/Components/Forms/SelectMulti.php b/app/Views/Components/Forms/SelectMulti.php new file mode 100644 index 00000000..6c09619b --- /dev/null +++ b/app/Views/Components/Forms/SelectMulti.php @@ -0,0 +1,52 @@ + 'array', + 'defaultValue' => 'array', + 'options' => 'array', + ]; + + /** + * @var array> + */ + protected array $options = []; + + #[Override] + public function render(): string + { + $this->mergeClass('w-full bg-elevated border-3 border-contrast rounded-lg relative'); + + $defaultAttributes = [ + 'multiple' => 'multiple', + 'data-select-text' => lang('Common.forms.multiSelect.selectText'), + 'data-loading-text' => lang('Common.forms.multiSelect.loadingText'), + 'data-no-results-text' => lang('Common.forms.multiSelect.noResultsText'), + 'data-no-choices-text' => lang('Common.forms.multiSelect.noChoicesText'), + 'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'), + ]; + + $this->attributes = [...$defaultAttributes, ...$this->attributes]; + + $options = ''; + $selected = $this->getValue(); + foreach ($this->options as $option) { + $options .= ''; + } + + $this->attributes['name'] = $this->name . '[]'; + + return <<getStringifiedAttributes()}>{$options} + HTML; + } +} diff --git a/app/Views/Components/Forms/Textarea.php b/app/Views/Components/Forms/Textarea.php index 705ec0f3..f58e3b98 100644 --- a/app/Views/Components/Forms/Textarea.php +++ b/app/Views/Components/Forms/Textarea.php @@ -4,25 +4,27 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class Textarea extends FormComponent { - public function setValue(?string $value): void + protected array $attributes = [ + 'rows' => '6', + ]; + + public function setValue(string $value): void { - if ($value) { - $this->value = htmlspecialchars_decode($value); - } + $this->value = htmlspecialchars_decode($value); } + #[Override] public function render(): string { - unset($this->attributes['value']); + $this->mergeClass('bg-elevated w-full rounded-lg border-3 border-contrast focus:border-contrast focus-within:ring-accent transition'); - $this->attributes['class'] = 'bg-elevated w-full focus:border-contrast focus:ring-accent rounded-lg border-3 border-contrast ' . $this->class; + $this->attributes['id'] = $this->id; - $textarea = form_textarea( - $this->attributes, - old($this->name, $this->value ?? '', false) - ); + $textarea = form_textarea($this->attributes, $this->getValue()); return << 'boolean', + ]; protected string $hint = ''; - protected bool $checked = false; + protected string $helper = ''; - public function setChecked(string $value): void - { - $this->checked = $value === 'true'; - } + protected bool $isChecked = false; + #[Override] public function render(): string { - unset($this->attributes['checked']); + $this->mergeClass('relative justify-between inline-flex items-start gap-x-2'); - $wrapperClass = $this->class; - unset($this->attributes['class']); + $checkbox = form_checkbox( + [ + 'id' => $this->id, + 'name' => $this->name, + 'class' => 'form-switch', + ], + 'yes', + in_array($this->getValue(), ['yes', 'true', 'on', '1'], true), + ); - $sizeClass = [ - 'base' => 'form-switch-slider', - 'small' => 'form-switch-slider form-switch-slider--small', - ]; + $hint = $this->hint === '' ? '' : new Hint([ + 'class' => 'ml-1', + 'slot' => $this->hint, + ])->render(); - $this->attributes['class'] = 'form-switch'; + $helperText = ''; + if ($this->helper !== '') { + $helperId = $this->name . 'Help'; + $helperText = new Helper([ + 'id' => $helperId, + 'slot' => $this->helper, + 'class' => '-mt-1', + ])->render(); + $this->attributes['aria-describedby'] = $helperId; + } - $checkbox = form_checkbox($this->attributes, $this->value, old($this->name) === 'yes' ? true : $this->checked); - $hint = $this->hint === '' ? '' : hint_tooltip($this->hint, 'ml-1'); return << + HTML; } diff --git a/app/Views/Components/Forms/XMLEditor.php b/app/Views/Components/Forms/XMLEditor.php deleted file mode 100644 index 1c23eddb..00000000 --- a/app/Views/Components/Forms/XMLEditor.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ - protected array $attributes = [ - 'rows' => '5', - 'class' => 'textarea', - ]; - - protected string $content = ''; - - public function setContent(string $value): void - { - $this->content = htmlspecialchars_decode($value); - } - - public function render(): string - { - $this->attributes['slot'] = 'textarea'; - $textarea = form_textarea($this->attributes, $this->content); - - return <<{$textarea} - HTML; - } -} diff --git a/app/Views/Components/Heading.php b/app/Views/Components/Heading.php index 001679cf..87d2de6e 100644 --- a/app/Views/Components/Heading.php +++ b/app/Views/Components/Heading.php @@ -4,10 +4,13 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class Heading extends Component { + protected array $props = ['tagName', 'size']; + protected string $tagName = 'div'; /** @@ -15,18 +18,20 @@ class Heading extends Component */ protected string $size = 'base'; + #[Override] public function render(): string { - $sizeClasses = [ + $sizeClass = match ($this->size) { 'small' => 'tracking-wide text-base', - 'base' => 'text-xl', 'large' => 'text-3xl', - ]; + default => 'text-xl', + }; - $class = $this->class . ' relative z-10 font-bold text-heading-foreground font-display before:w-full before:absolute before:h-1/2 before:left-0 before:bottom-0 before:rounded-full before:bg-heading-background before:z-[-10] ' . $sizeClasses[$this->size]; + $this->mergeClass('relative z-10 font-bold text-heading-foreground font-display before:w-full before:absolute before:h-1/2 before:left-0 before:bottom-0 before:rounded-full before:bg-heading-background before:z-[-10]'); + $this->mergeClass($sizeClass); return <<tagName} class="{$class}">{$this->slot}tagName}> + <{$this->tagName} {$this->getStringifiedAttributes()}>{$this->slot}tagName}> HTML; } } diff --git a/app/Views/Components/Hint.php b/app/Views/Components/Hint.php new file mode 100644 index 00000000..c329296f --- /dev/null +++ b/app/Views/Components/Hint.php @@ -0,0 +1,30 @@ + 'bottom', + 'tabindex' => '0', + ]; + + #[Override] + public function render(): string + { + $this->attributes['title'] = $this->slot; + + $this->mergeClass('inline-block align-middle opacity-75'); + + $icon = icon('question-fill'); + + return <<getStringifiedAttributes()}>{$icon} + HTML; + } +} diff --git a/app/Views/Components/IconButton.php b/app/Views/Components/IconButton.php index ebc2d5f8..7c6f44ad 100644 --- a/app/Views/Components/IconButton.php +++ b/app/Views/Components/IconButton.php @@ -6,7 +6,9 @@ namespace App\Views\Components; class IconButton extends Button { - public string $glyph = ''; + protected array $props = ['glyph']; + + protected string $glyph; public function __construct(array $attributes) { @@ -16,18 +18,18 @@ class IconButton extends Button 'data-tooltip' => 'bottom', ]; - $glyphSize = [ - 'small' => 'text-sm', - 'base' => 'text-lg', - 'large' => 'text-2xl', - ]; - $allAttributes = [...$attributes, ...$iconButtonAttributes]; parent::__construct($allAttributes); + $glyphSizeClass = match ($this->size) { + 'small' => 'text-sm', + 'large' => 'text-2xl', + default => 'text-lg', + }; + $this->slot = (string) icon($this->glyph, [ - 'class' => $glyphSize[$this->size], + 'class' => $glyphSizeClass, ]); } } diff --git a/app/Views/Components/Pill.php b/app/Views/Components/Pill.php index b3ec02a3..9d76cc5e 100644 --- a/app/Views/Components/Pill.php +++ b/app/Views/Components/Pill.php @@ -4,40 +4,57 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class Pill extends Component { + protected array $props = ['size', 'variant', 'icon', 'iconClass', 'hint']; + /** * @var 'small'|'base' */ - public string $size = 'base'; + protected string $size = 'base'; - public string $variant = 'default'; + protected string $variant = 'default'; - public ?string $icon = null; + protected string $icon = ''; - public ?string $iconClass = ''; + protected string $iconClass = ''; - protected ?string $hint = null; + protected string $hint = ''; + #[Override] public function render(): string { - $variantClasses = [ - 'default' => 'text-gray-800 bg-gray-100 border-gray-300', + $variantClass = match ($this->variant) { 'primary' => 'text-accent-contrast bg-accent-base border-accent-base', 'success' => 'text-pine-900 bg-pine-100 border-pine-300', 'danger' => 'text-red-900 bg-red-100 border-red-300', 'warning' => 'text-yellow-900 bg-yellow-100 border-yellow-300', - ]; + default => 'text-gray-800 bg-gray-100 border-gray-300', + }; - $icon = $this->icon ? icon($this->icon, [ + $sizeClass = match ($this->size) { + 'small' => 'text-xs tracking-wide', + default => 'text-sm', + }; + + $icon = $this->icon !== '' ? icon($this->icon, [ 'class' => $this->iconClass, ]) : ''; - $hint = $this->hint ? 'data-tooltip="bottom" title="' . $this->hint . '"' : ''; + + if ($this->hint !== '') { + $this->attributes['data-tooltip'] = 'bottom'; + $this->attributes['title'] = $this->hint; + } + + $this->mergeClass('inline-flex lowercase items-center gap-x-1 px-1 font-semibold border rounded'); + $this->mergeClass($variantClass); + $this->mergeClass($sizeClass); return <<{$icon}{$this->slot} + getStringifiedAttributes()}>{$icon}{$this->slot} HTML; } } diff --git a/app/Views/Components/ReadMore.php b/app/Views/Components/ReadMore.php index 016a60b3..d55e48d9 100644 --- a/app/Views/Components/ReadMore.php +++ b/app/Views/Components/ReadMore.php @@ -4,21 +4,29 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class ReadMore extends Component { - public string $id; + protected array $props = ['id']; + protected string $id; + + #[Override] public function render(): string { $readMoreLabel = lang('Common.read_more'); $readLessLabel = lang('Common.read_less'); + + $this->mergeClass('read-more'); + $this->attributes['style'] = '--line-clamp: 3'; + return << +
getStringifiedAttributes()}> -
{$this->slot}
- +
{$this->slot}
+
HTML; } diff --git a/app/Views/Components/SeeMore.php b/app/Views/Components/SeeMore.php index 19f247f2..e8e42deb 100644 --- a/app/Views/Components/SeeMore.php +++ b/app/Views/Components/SeeMore.php @@ -4,19 +4,25 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class SeeMore extends Component { + #[Override] public function render(): string { $seeMoreLabel = lang('Common.see_more'); $seeLessLabel = lang('Common.see_less'); + + $this->mergeClass('see-more'); + $this->attributes['styles'] = '--content-height: 10rem'; + return << +
getStringifiedAttributes()}> -
{$this->slot}
- +
{$this->slot}
+
HTML; } diff --git a/app/Views/_message_block.php b/app/Views/_message_block.php index 1504aa37..4f40c4e5 100644 --- a/app/Views/_message_block.php +++ b/app/Views/_message_block.php @@ -1,18 +1,18 @@ has('message')): ?> - + has('error')): ?> - + has('errors')): ?>
    -
  • +
diff --git a/app/Views/errors/html/debug.css b/app/Views/errors/html/debug.css index 79f365db..3b6de5b6 100644 --- a/app/Views/errors/html/debug.css +++ b/app/Views/errors/html/debug.css @@ -49,6 +49,7 @@ p.lead { .header { background: var(--light-bg-color); color: var(--dark-text-color); + margin-top: 2.17rem; } .header .container { diff --git a/app/Views/errors/html/error_400.php b/app/Views/errors/html/error_400.php index ae25b5c0..555da042 100644 --- a/app/Views/errors/html/error_400.php +++ b/app/Views/errors/html/error_400.php @@ -1,29 +1,84 @@ - - - + <?= lang('Errors.badRequest') ?> - <?= lang('Errors.pageNotFound') ?> - ' /> - asset('styles/index.css') ?> + + +
+

400

- - -

400

- -

+

- +
- diff --git a/app/Views/errors/html/error_403.php b/app/Views/errors/html/error_403.php index ac9b6deb..a5d4d040 100644 --- a/app/Views/errors/html/error_403.php +++ b/app/Views/errors/html/error_403.php @@ -1,6 +1,7 @@ - + @@ -8,7 +9,7 @@ 403 Forbidden ' /> - asset('styles/index.css') ?> + asset('styles/index.css', 'css') ?> " class="inline-flex items-center justify-center px-3 py-1 text-sm font-semibold rounded-full shadow-xs text-accent-contrast focus:ring-accent md:px-4 md:py-2 md:text-base bg-accent-base hover:bg-accent-hover"> + diff --git a/app/Views/errors/html/error_404.php b/app/Views/errors/html/error_404.php index 09390651..4d74a150 100644 --- a/app/Views/errors/html/error_404.php +++ b/app/Views/errors/html/error_404.php @@ -1,6 +1,7 @@ - + @@ -8,7 +9,7 @@ <?= lang('Errors.pageNotFound') ?> ' /> - asset('styles/index.css') ?> + asset('styles/index.css', 'css') ?> " class="inline-flex items-center justify-center px-3 py-1 text-sm font-semibold rounded-full shadow-xs text-accent-contrast focus:ring-accent md:px-4 md:py-2 md:text-base bg-accent-base hover:bg-accent-hover"> + diff --git a/app/Views/errors/html/production.php b/app/Views/errors/html/production.php index df50ec0f..77445e16 100644 --- a/app/Views/errors/html/production.php +++ b/app/Views/errors/html/production.php @@ -9,9 +9,9 @@ <?= lang('Errors.whoops') ?> ' /> - asset('styles/index.css') ?> + asset('styles/index.css', 'css') ?> loggedIn()): ?> - asset('js/error.ts') ?> + asset('js/error.ts', 'js') ?> @@ -28,7 +28,7 @@

getCode() ? ' #' . $exception->getCode() : '') ?>

getMessage())) ?>
at getFile())) ?>:getLine()) ?>

- + 'mr-2', ]) ?>Copy stack trace @@ -41,11 +41,11 @@

Found a bug?

-

You can help get it fixed by creating an issue on the Castopod issue tracker. Please check that the issue does not already exist beforehand.

+

You can help get it fixed by creating an issue on the Castopod issue tracker. Please check that the issue does not already exist beforehand.

Not sure what's happening?

-

You can ask for help in the Castopod community chat!

+

You can ask for help in the Castopod community chat!

diff --git a/composer.json b/composer.json index c4eebd5d..f3ce91ba 100644 --- a/composer.json +++ b/composer.json @@ -1,20 +1,21 @@ { "name": "adaures/castopod", - "version": "1.15.5", + "version": "2.0.0-dev", "type": "project", "description": "Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.", "homepage": "https://castopod.org", "license": "AGPL-3.0-or-later", "require": { - "php": "^8.1", + "php": "^8.5", + "adaures/castopod-plugins-manager": "dev-main", "adaures/ipcat-php": "^v1.0.0", "adaures/podcast-persons-taxonomy": "^v1.0.1", - "aws/aws-sdk-php": "^3.369.36", + "aws/aws-sdk-php": "^3.369.37", "chrisjean/php-ico": "^1.0.4", - "cocur/slugify": "^v4.7.1", - "codeigniter4/framework": "4.6.5", + "cocur/slugify": "4.7.1", + "codeigniter4/framework": "4.7.0", "codeigniter4/settings": "v2.2.0", - "codeigniter4/shield": "^1.2.0", + "codeigniter4/shield": "1.2.0", "codeigniter4/tasks": "dev-develop", "geoip2/geoip2": "3.3.0", "james-heinrich/getid3": "^2.0.0-beta6", @@ -25,19 +26,19 @@ "mpratt/embera": "^2.0.42", "opawg/user-agents-v2-php": "dev-main", "phpseclib/phpseclib": "~2.0.51", - "vlucas/phpdotenv": "^5.6.3", + "vlucas/phpdotenv": "5.6.3", "whichbrowser/parser": "^v2.1.8", - "yassinedoghri/codeigniter-vite": "^2.1", - "yassinedoghri/php-icons": "^1.3.0", + "yassinedoghri/codeigniter-vite": "^2.1.0", + "yassinedoghri/php-icons": "1.3.0", "yassinedoghri/podcast-feed": "dev-main" }, "require-dev": { "captainhook/captainhook": "^5.28.3", - "codeigniter/phpstan-codeigniter": "^1.5.4", - "mikey179/vfsstream": "v1.6.12", + "codeigniter/phpstan-codeigniter": "1.5.4", + "mikey179/vfsstream": "^v1.6.12", "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan": "^2.1.39", - "phpunit/phpunit": "^10.5.63", + "phpunit/phpunit": "^13.0.5", "rector/rector": "^2.3.6", "symplify/coding-standard": "^13.0.0", "symplify/easy-coding-standard": "^13.0.4" diff --git a/composer.lock b/composer.lock index 6755eaf1..3e35a5dd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,63 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ae175922494cbac88f4477946a143ae8", + "content-hash": "de1c665976cbb37fec3de326336d83f5", "packages": [ + { + "name": "adaures/castopod-plugins-manager", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/ad-aures/castopod-plugins-manager.git", + "reference": "53430f9a57cd38eee3e3dfe5953764cc42c2a0c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ad-aures/castopod-plugins-manager/zipball/53430f9a57cd38eee3e3dfe5953764cc42c2a0c9", + "reference": "53430f9a57cd38eee3e3dfe5953764cc42c2a0c9", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "z4kn4fein/php-semver": "^3.0" + }, + "require-dev": { + "pestphp/pest": "^4.0.4", + "pestphp/pest-plugin-type-coverage": "^4.0.2", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "rector/rector": "^2.1.4", + "symplify/coding-standard": "^12.4.3", + "symplify/easy-coding-standard": "^12.5.24" + }, + "default-branch": true, + "type": "library", + "autoload": { + "files": [ + "src/Constants.php", + "src/helpers.php" + ], + "psr-4": { + "Castopod\\PluginsManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0-only" + ], + "authors": [ + { + "name": "Yassine Doghri", + "homepage": "https://yassinedoghri.com/" + } + ], + "description": "A PHP library to install, update, and remove plugins on a Castopod instance.", + "support": { + "issues": "https://github.com/ad-aures/castopod-plugins-manager/issues", + "source": "https://github.com/ad-aures/castopod-plugins-manager/tree/main" + }, + "time": "2025-10-06T15:58:43+00:00" + }, { "name": "adaures/ipcat-php", "version": "v1.0.0", @@ -206,16 +261,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.369.36", + "version": "3.369.37", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0" + "reference": "bc599ce989b101ee630d5e1a1aeda387632df47b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0", - "reference": "2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bc599ce989b101ee630d5e1a1aeda387632df47b", + "reference": "bc599ce989b101ee630d5e1a1aeda387632df47b", "shasum": "" }, "require": { @@ -297,9 +352,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.369.36" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.37" }, - "time": "2026-02-17T19:45:01+00:00" + "time": "2026-02-18T19:16:34+00:00" }, { "name": "brick/math", @@ -484,23 +539,23 @@ }, { "name": "codeigniter4/framework", - "version": "v4.6.5", + "version": "v4.7.0", "source": { "type": "git", "url": "https://github.com/codeigniter4/framework.git", - "reference": "116e0919590a412c09d2b9e4f6b8addda18224d8" + "reference": "e7753bc03f8b74af428f46b5e2bb74925487c930" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codeigniter4/framework/zipball/116e0919590a412c09d2b9e4f6b8addda18224d8", - "reference": "116e0919590a412c09d2b9e4f6b8addda18224d8", + "url": "https://api.github.com/repos/codeigniter4/framework/zipball/e7753bc03f8b74af428f46b5e2bb74925487c930", + "reference": "e7753bc03f8b74af428f46b5e2bb74925487c930", "shasum": "" }, "require": { "ext-intl": "*", "ext-mbstring": "*", - "laminas/laminas-escaper": "^2.17", - "php": "^8.1", + "laminas/laminas-escaper": "^2.18", + "php": "^8.2", "psr/log": "^3.0" }, "require-dev": { @@ -514,6 +569,7 @@ "predis/predis": "^3.0" }, "suggest": { + "ext-apcu": "If you use Cache class ApcuHandler", "ext-curl": "If you use CURLRequest class", "ext-dom": "If you use TestResponse", "ext-exif": "If you run Image class tests", @@ -525,7 +581,9 @@ "ext-memcached": "If you use Cache class MemcachedHandler with Memcached", "ext-mysqli": "If you use MySQL", "ext-oci8": "If you use Oracle Database", + "ext-pcntl": "If you use Signals", "ext-pgsql": "If you use PostgreSQL", + "ext-posix": "If you use Signals", "ext-readline": "Improves CLI::input() usability", "ext-redis": "If you use Cache class RedisHandler", "ext-simplexml": "If you format XML", @@ -554,7 +612,7 @@ "slack": "https://codeigniterchat.slack.com", "source": "https://github.com/codeigniter4/CodeIgniter4" }, - "time": "2026-02-01T17:59:34+00:00" + "time": "2026-02-01T20:39:35+00:00" }, { "name": "codeigniter4/queue", @@ -3201,25 +3259,25 @@ }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0|^8.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3247,7 +3305,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" }, "funding": [ { @@ -3267,7 +3325,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3836,6 +3894,60 @@ "source": "https://github.com/yassinedoghri/podcast-feed/tree/main" }, "time": "2024-04-28T16:17:41+00:00" + }, + { + "name": "z4kn4fein/php-semver", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/z4kn4fein/php-semver.git", + "reference": "049a1d81e92235c8b3c9ab30a96fcbaa929a266d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/z4kn4fein/php-semver/zipball/049a1d81e92235c8b3c9ab30a96fcbaa929a266d", + "reference": "049a1d81e92235c8b3c9ab30a96fcbaa929a266d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^1.0", + "phpunit/phpunit": "^10" + }, + "type": "library", + "autoload": { + "psr-4": { + "z4kn4fein\\SemVer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Peter Csajtai", + "email": "peter.csajtai@outlook.com" + } + ], + "description": "Semantic Versioning library for PHP. It implements the full semantic version 2.0.0 specification and provides ability to parse, compare, and increment semantic versions along with validation against constraints.", + "homepage": "https://github.com/z4kn4fein/php-semver", + "keywords": [ + "comparison", + "semantic", + "semver", + "validation", + "version", + "versioning" + ], + "support": { + "issues": "https://github.com/z4kn4fein/php-semver/issues", + "source": "https://github.com/z4kn4fein/php-semver/tree/v3.0.0" + }, + "time": "2024-04-01T16:17:27+00:00" } ], "packages-dev": [ @@ -4940,35 +5052,34 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.16", + "version": "13.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a8b58fde2f4fbc69a064e1f80ff917607cf7737c", + "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" + "nikic/php-parser": "^5.7.0", + "php": ">=8.4", + "phpunit/php-file-iterator": "^7.0", + "phpunit/php-text-template": "^6.0", + "sebastian/complexity": "^6.0", + "sebastian/environment": "^9.0", + "sebastian/lines-of-code": "^5.0", + "sebastian/version": "^7.0", + "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^10.1" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -4977,7 +5088,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1.x-dev" + "dev-main": "13.0.x-dev" } }, "autoload": { @@ -5006,40 +5117,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/13.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-08-22T04:31:57+00:00" + "time": "2026-02-06T06:05:15+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -5067,36 +5190,48 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2026-02-06T04:33:26+00:00" }, { "name": "phpunit/php-invoker", - "version": "4.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-pcntl": "*" @@ -5104,7 +5239,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -5130,40 +5265,53 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", + "type": "tidelift" } ], - "time": "2023-02-03T06:56:09+00:00" + "time": "2026-02-06T04:34:47+00:00" }, { "name": "phpunit/php-text-template", - "version": "3.0.1", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -5190,40 +5338,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", + "type": "tidelift" } ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2026-02-06T04:36:37+00:00" }, { "name": "phpunit/php-timer", - "version": "6.0.0", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -5249,28 +5409,41 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", + "type": "tidelift" } ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2026-02-06T04:37:53+00:00" }, { "name": "phpunit/phpunit", - "version": "10.5.63", + "version": "13.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "33198268dad71e926626b618f3ec3966661e4d90" + "reference": "d57826e8921a534680c613924bfd921ded8047f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", - "reference": "33198268dad71e926626b618f3ec3966661e4d90", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d57826e8921a534680c613924bfd921ded8047f4", + "reference": "d57826e8921a534680c613924bfd921ded8047f4", "shasum": "" }, "require": { @@ -5283,26 +5456,23 @@ "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.5", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.4", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.1", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" + "php": ">=8.4.1", + "phpunit/php-code-coverage": "^13.0.1", + "phpunit/php-file-iterator": "^7.0.0", + "phpunit/php-invoker": "^7.0.0", + "phpunit/php-text-template": "^6.0.0", + "phpunit/php-timer": "^9.0.0", + "sebastian/cli-parser": "^5.0.0", + "sebastian/comparator": "^8.0.0", + "sebastian/diff": "^8.0.0", + "sebastian/environment": "^9.0.0", + "sebastian/exporter": "^8.0.0", + "sebastian/global-state": "^9.0.0", + "sebastian/object-enumerator": "^8.0.0", + "sebastian/recursion-context": "^8.0.0", + "sebastian/type": "^7.0.0", + "sebastian/version": "^7.0.0", + "staabm/side-effects-detector": "^1.0.5" }, "bin": [ "phpunit" @@ -5310,7 +5480,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.5-dev" + "dev-main": "13.0-dev" } }, "autoload": { @@ -5342,7 +5512,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + "source": "https://github.com/sebastianbergmann/phpunit/tree/13.0.5" }, "funding": [ { @@ -5366,7 +5536,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T05:48:37+00:00" + "time": "2026-02-18T12:40:03+00:00" }, { "name": "psr/container", @@ -6009,28 +6179,28 @@ }, { "name": "sebastian/cli-parser", - "version": "2.0.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -6054,155 +6224,59 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - } - ], - "time": "2024-03-02T07:12:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" - }, - "funding": [ + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:58:43+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2026-02-06T04:39:44+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.5", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/29b232ddc29c2b114c0358c69b3084e7c3da0d58", + "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.4", + "sebastian/diff": "^8.0", + "sebastian/exporter": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -6242,7 +6316,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/comparator/tree/8.0.0" }, "funding": [ { @@ -6262,33 +6336,33 @@ "type": "tidelift" } ], - "time": "2026-01-24T09:25:16+00:00" + "time": "2026-02-06T04:40:39+00:00" }, { "name": "sebastian/complexity", - "version": "3.2.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "reference": "c5651c795c98093480df79350cb050813fc7a2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", + "reference": "c5651c795c98093480df79350cb050813fc7a2f3", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6312,41 +6386,53 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", + "type": "tidelift" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2026-02-06T04:41:32+00:00" }, { "name": "sebastian/diff", - "version": "5.1.1", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2b6d09d7729ee87d605a439469f9dcc39be5ea3", + "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" + "phpunit/phpunit": "^13.0", + "symfony/process": "^7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -6379,35 +6465,47 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + "source": "https://github.com/sebastianbergmann/diff/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "type": "tidelift" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2026-02-06T04:42:27+00:00" }, { "name": "sebastian/environment", - "version": "6.1.0", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "reference": "bb64d08145b021b67d5f253308a498b73ab0461e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/bb64d08145b021b67d5f253308a498b73ab0461e", + "reference": "bb64d08145b021b67d5f253308a498b73ab0461e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-posix": "*" @@ -6415,7 +6513,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -6443,42 +6541,54 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + "source": "https://github.com/sebastianbergmann/environment/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2026-02-06T04:43:29+00:00" }, { "name": "sebastian/exporter", - "version": "5.1.4", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" + "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", + "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "php": ">=8.4", + "sebastian/recursion-context": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -6521,7 +6631,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/8.0.0" }, "funding": [ { @@ -6541,35 +6651,35 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:09:11+00:00" + "time": "2026-02-06T04:44:28+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.2", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7", + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -6595,41 +6705,53 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2026-02-06T04:45:13+00:00" }, { "name": "sebastian/lines-of-code", - "version": "2.0.2", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -6653,42 +6775,54 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "type": "tidelift" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2026-02-06T04:45:54+00:00" }, { "name": "sebastian/object-enumerator", - "version": "5.0.0", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -6710,40 +6844,53 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", + "type": "tidelift" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2026-02-06T04:46:36+00:00" }, { "name": "sebastian/object-reflector", - "version": "3.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6765,40 +6912,53 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", + "type": "tidelift" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2026-02-06T04:47:13+00:00" }, { "name": "sebastian/recursion-context", - "version": "5.0.1", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -6829,7 +6989,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" }, "funding": [ { @@ -6849,32 +7009,32 @@ "type": "tidelift" } ], - "time": "2025-08-10T07:50:56+00:00" + "time": "2026-02-06T04:51:28+00:00" }, { "name": "sebastian/type", - "version": "4.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "reference": "42412224607bd3931241bbd17f38e0f972f5a916" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/42412224607bd3931241bbd17f38e0f972f5a916", + "reference": "42412224607bd3931241bbd17f38e0f972f5a916", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -6897,37 +7057,50 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2026-02-06T04:52:09+00:00" }, { "name": "sebastian/version", - "version": "4.0.1", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -6950,15 +7123,28 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/version", + "type": "tidelift" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2026-02-06T04:52:52+00:00" }, { "name": "sebastianfeldmann/camino", @@ -7137,48 +7323,92 @@ "time": "2026-01-26T20:59:18+00:00" }, { - "name": "symfony/console", - "version": "v7.4.4", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/console", + "version": "v8.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b", + "reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7212,7 +7442,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.4" + "source": "https://github.com/symfony/console/tree/v8.0.4" }, "funding": [ { @@ -7232,28 +7462,28 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-01-13T13:06:50+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.4", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746" + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<6.4", + "symfony/security-http": "<7.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -7262,14 +7492,14 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7297,7 +7527,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4" }, "funding": [ { @@ -7317,7 +7547,7 @@ "type": "tidelift" } ], - "time": "2026-01-05T11:45:34+00:00" + "time": "2026-01-05T11:45:55+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -7397,23 +7627,23 @@ }, { "name": "symfony/finder", - "version": "v7.4.5", + "version": "v8.0.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + "reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0", + "reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" + "symfony/filesystem": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7441,7 +7671,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.5" + "source": "https://github.com/symfony/finder/tree/v8.0.5" }, "funding": [ { @@ -7461,24 +7691,24 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-01-26T15:08:38+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.4.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "b38026df55197f9e39a44f3215788edf83187b80" + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", - "reference": "b38026df55197f9e39a44f3215788edf83187b80", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7", + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -7512,7 +7742,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.4.0" + "source": "https://github.com/symfony/options-resolver/tree/v8.0.0" }, "funding": [ { @@ -7532,7 +7762,7 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2025-11-12T15:55:31+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -7863,20 +8093,20 @@ }, { "name": "symfony/process", - "version": "v7.4.5", + "version": "v8.0.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "608476f4604102976d687c483ac63a79ba18cc97" + "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", - "reference": "608476f4604102976d687c483ac63a79ba18cc97", + "url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674", + "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "type": "library", "autoload": { @@ -7904,7 +8134,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.5" + "source": "https://github.com/symfony/process/tree/v8.0.5" }, "funding": [ { @@ -7924,7 +8154,7 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-01-26T15:08:38+00:00" }, { "name": "symfony/service-contracts", @@ -8015,20 +8245,20 @@ }, { "name": "symfony/stopwatch", - "version": "v7.4.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "8a24af0a2e8a872fb745047180649b8418303084" + "reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8a24af0a2e8a872fb745047180649b8418303084", - "reference": "8a24af0a2e8a872fb745047180649b8418303084", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/67df1914c6ccd2d7b52f70d40cf2aea02159d942", + "reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -8057,7 +8287,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.4.0" + "source": "https://github.com/symfony/stopwatch/tree/v8.0.0" }, "funding": [ { @@ -8077,39 +8307,38 @@ "type": "tidelift" } ], - "time": "2025-08-04T07:05:15+00:00" + "time": "2025-08-04T07:36:47+00:00" }, { "name": "symfony/string", - "version": "v7.4.4", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" + "reference": "758b372d6882506821ed666032e43020c4f57194" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", + "reference": "758b372d6882506821ed666032e43020c4f57194", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8148,7 +8377,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.4" + "source": "https://github.com/symfony/string/tree/v8.0.4" }, "funding": [ { @@ -8168,7 +8397,7 @@ "type": "tidelift" } ], - "time": "2026-01-12T10:54:30+00:00" + "time": "2026-01-12T12:37:40+00:00" }, { "name": "symplify/coding-standard", @@ -8291,23 +8520,23 @@ }, { "name": "theseer/tokenizer", - "version": "1.3.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.1" }, "type": "library", "autoload": { @@ -8329,7 +8558,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + "source": "https://github.com/theseer/tokenizer/tree/2.0.1" }, "funding": [ { @@ -8337,12 +8566,13 @@ "type": "github" } ], - "time": "2025-11-17T20:03:58+00:00" + "time": "2025-12-08T11:19:18+00:00" } ], "aliases": [], "minimum-stability": "dev", "stability-flags": { + "adaures/castopod-plugins-manager": 20, "codeigniter4/tasks": 20, "opawg/user-agents-v2-php": 20, "yassinedoghri/podcast-feed": 20 @@ -8350,7 +8580,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.1" + "php": "^8.4" }, "platform-dev": {}, "plugin-api-version": "2.9.0" diff --git a/docker/ci/Dockerfile b/docker/ci/Dockerfile index 1e98f266..bae95838 100644 --- a/docker/ci/Dockerfile +++ b/docker/ci/Dockerfile @@ -4,7 +4,7 @@ # ⚠️ NOT optimized for production # should be used only for continuous integration #--------------------------------------------------- -FROM php:8.1-fpm-alpine3.22 +FROM php:8.5-fpm-alpine3.23 LABEL maintainer="Yassine Doghri " @@ -32,9 +32,8 @@ RUN \ mysqli \ && apk del .php-ext-build-dep \ # install pnpm - && wget -qO- https://get.pnpm.io/install.sh | ENV="~/.shrc" SHELL="$(which sh)" sh - \ - && mv ~/.local/share/pnpm/pnpm /usr/bin/pnpm \ - && rm -rf ~/.local \ + && npm install --global corepack@latest \ + && corepack enable pnpm \ # set pnpm store directory && pnpm config set store-dir .pnpm-store \ # set composer cache directory diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index dc719c71..f55d3cfc 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -7,7 +7,7 @@ # 2. BUILD the FrankenPHP/debian based prod image #--------------------------------------------------- -ARG PHP_VERSION="8.4" +ARG PHP_VERSION="8.5" #################################################### # BUNDLE STAGE diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 12243d1b..d641ec7f 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -1,6 +1,7 @@ // @ts-check import { defineConfig } from "astro/config"; import starlight from "@astrojs/starlight"; +import starlightOpenAPI from "starlight-openapi"; const site = "https://docs.castopod.org/"; const base = process.env.BASE ?? "/docs"; @@ -143,6 +144,17 @@ export default defineConfig({ lang: "zh-Hans", }, }, + plugins: [ + // Generate the OpenAPI documentation pages. + starlightOpenAPI([ + { + base: "en/api", + label: "API reference", + schema: "../modules/Api/Rest/V1/schema.yaml", + collapsed: true, + }, + ]), + ], sidebar: [ { label: "Introduction", @@ -214,6 +226,90 @@ export default defineConfig({ }, ], }, + { + label: "Plugins", + items: [ + { + label: "Introduction", + link: "/plugins/", + }, + { + label: "Install plugins", + link: "/plugins/install", + }, + { + label: "Create a plugin", + link: "/plugins/create", + }, + { + label: "Share your plugin", + link: "/plugins/share", + }, + { + label: "Reference", + items: [ + { + label: "plugins.json", + link: "/plugins/reference/plugins-json", + }, + { + label: "plugins-lock.json", + link: "/plugins/reference/plugins-lock-json", + }, + { + label: "manifest.json", + link: "/plugins/reference/manifest", + }, + { + label: "hooks", + link: "/plugins/reference/hooks", + }, + ], + }, + ], + }, + // TODO: openapi plugin does not handle i18n, manual sidebar workaround + // Add the generated sidebar group to the sidebar. + // ...openAPISidebarGroups, + { + label: "API reference", + translations: {}, + items: [ + { + label: "Overview", + link: "/api", + }, + { + label: "Operations", + items: [ + { + label: "Get all podcasts", + link: "/api/operations/get-all-podcasts", + }, + { + label: "Get podcast by ID", + link: "/api/operations/get-podcast-by-id", + }, + { + label: "Get all episodes", + link: "/api/operations/get-all-episodes", + }, + { + label: "Add a new episode", + link: "/api/operations/add-episode", + }, + { + label: "Get episode by ID", + link: "/api/operations/get-episode-by-id", + }, + { + label: "Publish an episode", + link: "/api/operations/publish-episode", + }, + ], + }, + ], + }, { label: "User guide", translations: {}, @@ -225,6 +321,7 @@ export default defineConfig({ { label: "Manage your instance", translations: {}, + collapsed: true, items: [ { label: "Introduction", @@ -266,6 +363,7 @@ export default defineConfig({ { label: "Manage your podcasts", translations: {}, + collapsed: true, items: [ { label: "Introduction", diff --git a/docs/package.json b/docs/package.json index 5edaeb8c..9258fab6 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,7 +14,8 @@ "@astrojs/starlight": "^0.37.6", "@fontsource/inter": "^5.2.8", "@fontsource/rubik": "^5.2.8", - "astro": "^5.17.1", - "sharp": "^0.34.5" + "astro": "^5.17.2", + "sharp": "^0.34.5", + "starlight-openapi": "^0.22.0" } } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index fc96242c..45dac61d 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -9,7 +9,7 @@ importers: dependencies: "@astrojs/starlight": specifier: ^0.37.6 - version: 0.37.6(astro@5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2)) + version: 0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)) "@fontsource/inter": specifier: ^5.2.8 version: 5.2.8 @@ -17,25 +17,29 @@ importers: specifier: ^5.2.8 version: 5.2.8 astro: - specifier: ^5.17.1 - version: 5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2) + specifier: ^5.17.2 + version: 5.17.2(rollup@4.57.1)(typescript@5.9.3) sharp: specifier: ^0.34.5 version: 0.34.5 + starlight-openapi: + specifier: ^0.22.0 + version: 0.22.0(@astrojs/markdown-remark@6.3.10)(@astrojs/starlight@0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)))(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3))(openapi-types@12.1.3) packages: + "@apidevtools/json-schema-ref-parser@13.0.5": + resolution: + { + integrity: sha512-xfh4xVJD62gG6spIc7lwxoWT+l16nZu1ELyU8FkjaP/oD2yP09EvLAU6KhtudN9aML2Khhs9pY6Slr7KGTES3w==, + } + engines: { node: ">= 16" } + "@astrojs/compiler@2.13.1": resolution: { integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==, } - "@astrojs/internal-helpers@0.7.2": - resolution: - { - integrity: sha512-KCkCqR3Goym79soqEtbtLzJfqhTWMyVaizUi35FLzgGSzBotSw8DB1qwsu7U96ihOJgYhDk2nVPz+3LnXPeX6g==, - } - "@astrojs/internal-helpers@0.7.5": resolution: { @@ -48,16 +52,10 @@ packages: integrity: sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==, } - "@astrojs/markdown-remark@6.3.6": + "@astrojs/mdx@4.3.13": resolution: { - integrity: sha512-bwylYktCTsLMVoCOEHbn2GSUA3c5KT/qilekBKA3CBng0bo1TYjNZPr761vxumRk9kJGqTOtU+fgCAp5Vwokug==, - } - - "@astrojs/mdx@4.3.4": - resolution: - { - integrity: sha512-Ew3iP+6zuzzJWNEH5Qr1iknrue1heEfgmfuMpuwLaSwqlUiJQ0NDb2oxKosgWU1ROYmVf1H4KCmS6QdMWKyFjw==, + integrity: sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==, } engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } peerDependencies: @@ -70,10 +68,10 @@ packages: } engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } - "@astrojs/sitemap@3.5.1": + "@astrojs/sitemap@3.7.0": resolution: { - integrity: sha512-uX5z52GLtQTgOe8r3jeGmFRYrFe52mdpLYJzqjvL1cdy5Kg3MLOZEvaZ/OCH0fSq0t7e50uJQ6oBMZG0ffszBg==, + integrity: sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==, } "@astrojs/starlight@0.37.6": @@ -91,6 +89,13 @@ packages: } engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } + "@babel/code-frame@7.29.0": + resolution: + { + integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==, + } + engines: { node: ">=6.9.0" } + "@babel/helper-string-parser@7.27.1": resolution: { @@ -113,10 +118,10 @@ packages: engines: { node: ">=6.0.0" } hasBin: true - "@babel/runtime@7.28.3": + "@babel/runtime@7.28.6": resolution: { - integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==, + integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==, } engines: { node: ">=6.9.0" } @@ -134,10 +139,10 @@ packages: } engines: { node: ">=18" } - "@ctrl/tinycolor@4.1.0": + "@ctrl/tinycolor@4.2.0": resolution: { - integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==, + integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==, } engines: { node: ">=14" } @@ -147,262 +152,496 @@ packages: integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, } - "@esbuild/aix-ppc64@0.25.9": + "@esbuild/aix-ppc64@0.25.12": resolution: { - integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==, + integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==, } engines: { node: ">=18" } cpu: [ppc64] os: [aix] - "@esbuild/android-arm64@0.25.9": + "@esbuild/aix-ppc64@0.27.3": resolution: { - integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==, + integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/android-arm64@0.25.12": + resolution: + { + integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==, } engines: { node: ">=18" } cpu: [arm64] os: [android] - "@esbuild/android-arm@0.25.9": + "@esbuild/android-arm64@0.27.3": resolution: { - integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==, + integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm@0.25.12": + resolution: + { + integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==, } engines: { node: ">=18" } cpu: [arm] os: [android] - "@esbuild/android-x64@0.25.9": + "@esbuild/android-arm@0.27.3": resolution: { - integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==, + integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-x64@0.25.12": + resolution: + { + integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==, } engines: { node: ">=18" } cpu: [x64] os: [android] - "@esbuild/darwin-arm64@0.25.9": + "@esbuild/android-x64@0.27.3": resolution: { - integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==, + integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/darwin-arm64@0.25.12": + resolution: + { + integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==, } engines: { node: ">=18" } cpu: [arm64] os: [darwin] - "@esbuild/darwin-x64@0.25.9": + "@esbuild/darwin-arm64@0.27.3": resolution: { - integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==, + integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-x64@0.25.12": + resolution: + { + integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==, } engines: { node: ">=18" } cpu: [x64] os: [darwin] - "@esbuild/freebsd-arm64@0.25.9": + "@esbuild/darwin-x64@0.27.3": resolution: { - integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==, + integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/freebsd-arm64@0.25.12": + resolution: + { + integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==, } engines: { node: ">=18" } cpu: [arm64] os: [freebsd] - "@esbuild/freebsd-x64@0.25.9": + "@esbuild/freebsd-arm64@0.27.3": resolution: { - integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==, + integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.25.12": + resolution: + { + integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==, } engines: { node: ">=18" } cpu: [x64] os: [freebsd] - "@esbuild/linux-arm64@0.25.9": + "@esbuild/freebsd-x64@0.27.3": resolution: { - integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==, + integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/linux-arm64@0.25.12": + resolution: + { + integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==, } engines: { node: ">=18" } cpu: [arm64] os: [linux] - "@esbuild/linux-arm@0.25.9": + "@esbuild/linux-arm64@0.27.3": resolution: { - integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==, + integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm@0.25.12": + resolution: + { + integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==, } engines: { node: ">=18" } cpu: [arm] os: [linux] - "@esbuild/linux-ia32@0.25.9": + "@esbuild/linux-arm@0.27.3": resolution: { - integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==, + integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-ia32@0.25.12": + resolution: + { + integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==, } engines: { node: ">=18" } cpu: [ia32] os: [linux] - "@esbuild/linux-loong64@0.25.9": + "@esbuild/linux-ia32@0.27.3": resolution: { - integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==, + integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-loong64@0.25.12": + resolution: + { + integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==, } engines: { node: ">=18" } cpu: [loong64] os: [linux] - "@esbuild/linux-mips64el@0.25.9": + "@esbuild/linux-loong64@0.27.3": resolution: { - integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==, + integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-mips64el@0.25.12": + resolution: + { + integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==, } engines: { node: ">=18" } cpu: [mips64el] os: [linux] - "@esbuild/linux-ppc64@0.25.9": + "@esbuild/linux-mips64el@0.27.3": resolution: { - integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==, + integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-ppc64@0.25.12": + resolution: + { + integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==, } engines: { node: ">=18" } cpu: [ppc64] os: [linux] - "@esbuild/linux-riscv64@0.25.9": + "@esbuild/linux-ppc64@0.27.3": resolution: { - integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==, + integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-riscv64@0.25.12": + resolution: + { + integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==, } engines: { node: ">=18" } cpu: [riscv64] os: [linux] - "@esbuild/linux-s390x@0.25.9": + "@esbuild/linux-riscv64@0.27.3": resolution: { - integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==, + integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-s390x@0.25.12": + resolution: + { + integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==, } engines: { node: ">=18" } cpu: [s390x] os: [linux] - "@esbuild/linux-x64@0.25.9": + "@esbuild/linux-s390x@0.27.3": resolution: { - integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==, + integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-x64@0.25.12": + resolution: + { + integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==, } engines: { node: ">=18" } cpu: [x64] os: [linux] - "@esbuild/netbsd-arm64@0.25.9": + "@esbuild/linux-x64@0.27.3": resolution: { - integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==, + integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/netbsd-arm64@0.25.12": + resolution: + { + integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==, } engines: { node: ">=18" } cpu: [arm64] os: [netbsd] - "@esbuild/netbsd-x64@0.25.9": + "@esbuild/netbsd-arm64@0.27.3": resolution: { - integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==, + integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.25.12": + resolution: + { + integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==, } engines: { node: ">=18" } cpu: [x64] os: [netbsd] - "@esbuild/openbsd-arm64@0.25.9": + "@esbuild/netbsd-x64@0.27.3": resolution: { - integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==, + integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.25.12": + resolution: + { + integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==, } engines: { node: ">=18" } cpu: [arm64] os: [openbsd] - "@esbuild/openbsd-x64@0.25.9": + "@esbuild/openbsd-arm64@0.27.3": resolution: { - integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==, + integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.25.12": + resolution: + { + integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==, } engines: { node: ">=18" } cpu: [x64] os: [openbsd] - "@esbuild/openharmony-arm64@0.25.9": + "@esbuild/openbsd-x64@0.27.3": resolution: { - integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==, + integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/openharmony-arm64@0.25.12": + resolution: + { + integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==, } engines: { node: ">=18" } cpu: [arm64] os: [openharmony] - "@esbuild/sunos-x64@0.25.9": + "@esbuild/openharmony-arm64@0.27.3": resolution: { - integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==, + integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openharmony] + + "@esbuild/sunos-x64@0.25.12": + resolution: + { + integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==, } engines: { node: ">=18" } cpu: [x64] os: [sunos] - "@esbuild/win32-arm64@0.25.9": + "@esbuild/sunos-x64@0.27.3": resolution: { - integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==, + integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/win32-arm64@0.25.12": + resolution: + { + integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==, } engines: { node: ">=18" } cpu: [arm64] os: [win32] - "@esbuild/win32-ia32@0.25.9": + "@esbuild/win32-arm64@0.27.3": resolution: { - integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==, + integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-ia32@0.25.12": + resolution: + { + integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==, } engines: { node: ">=18" } cpu: [ia32] os: [win32] - "@esbuild/win32-x64@0.25.9": + "@esbuild/win32-ia32@0.27.3": resolution: { - integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==, + integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-x64@0.25.12": + resolution: + { + integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==, } engines: { node: ">=18" } cpu: [x64] os: [win32] - "@expressive-code/core@0.41.3": + "@esbuild/win32-x64@0.27.3": resolution: { - integrity: sha512-9qzohqU7O0+JwMEEgQhnBPOw5DtsQRBXhW++5fvEywsuX44vCGGof1SL5OvPElvNgaWZ4pFZAFSlkNOkGyLwSQ==, + integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@expressive-code/core@0.41.6": + resolution: + { + integrity: sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g==, } - "@expressive-code/plugin-frames@0.41.3": + "@expressive-code/plugin-frames@0.41.6": resolution: { - integrity: sha512-rFQtmf/3N2CK3Cq/uERweMTYZnBu+CwxBdHuOftEmfA9iBE7gTVvwpbh82P9ZxkPLvc40UMhYt7uNuAZexycRQ==, + integrity: sha512-d+hkSYXIQot6fmYnOmWAM+7TNWRv/dhfjMsNq+mIZz8Tb4mPHOcgcfZeEM5dV9TDL0ioQNvtcqQNuzA1sRPjxg==, } - "@expressive-code/plugin-shiki@0.41.3": + "@expressive-code/plugin-shiki@0.41.6": resolution: { - integrity: sha512-RlTARoopzhFJIOVHLGvuXJ8DCEme/hjV+ZnRJBIxzxsKVpGPW4Oshqg9xGhWTYdHstTsxO663s0cdBLzZj9TQA==, + integrity: sha512-Y6zmKBmsIUtWTzdefqlzm/h9Zz0Rc4gNdt2GTIH7fhHH2I9+lDYCa27BDwuBhjqcos6uK81Aca9dLUC4wzN+ng==, } - "@expressive-code/plugin-text-markers@0.41.3": + "@expressive-code/plugin-text-markers@0.41.6": resolution: { - integrity: sha512-SN8tkIzDpA0HLAscEYD2IVrfLiid6qEdE9QLlGVSxO1KEw7qYvjpbNBQjUjMr5/jvTJ7ys6zysU2vLPHE0sb2g==, + integrity: sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q==, } "@fontsource/inter@5.2.8": @@ -417,6 +656,13 @@ packages: integrity: sha512-PIc8QR7FqWPcYhbdRiGff56vQlKqg/ytES1YqecSq1GkgxiH4TBshrFvDEOZ9JonUF9m1qQ+qXxJj7wD5zgXEw==, } + "@humanwhocodes/momoa@2.0.4": + resolution: + { + integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==, + } + engines: { node: ">=10.10.0" } + "@img/colour@1.0.0": resolution: { @@ -651,10 +897,10 @@ packages: integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, } - "@mdx-js/mdx@3.1.0": + "@mdx-js/mdx@3.1.1": resolution: { - integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==, + integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==, } "@oslojs/encoding@1.1.0": @@ -663,52 +909,85 @@ packages: integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==, } - "@pagefind/darwin-arm64@1.3.0": + "@pagefind/darwin-arm64@1.4.0": resolution: { - integrity: sha512-365BEGl6ChOsauRjyVpBjXybflXAOvoMROw3TucAROHIcdBvXk9/2AmEvGFU0r75+vdQI4LJdJdpH4Y6Yqaj4A==, + integrity: sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==, } cpu: [arm64] os: [darwin] - "@pagefind/darwin-x64@1.3.0": + "@pagefind/darwin-x64@1.4.0": resolution: { - integrity: sha512-zlGHA23uuXmS8z3XxEGmbHpWDxXfPZ47QS06tGUq0HDcZjXjXHeLG+cboOy828QIV5FXsm9MjfkP5e4ZNbOkow==, + integrity: sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==, } cpu: [x64] os: [darwin] - "@pagefind/default-ui@1.3.0": + "@pagefind/default-ui@1.4.0": resolution: { - integrity: sha512-CGKT9ccd3+oRK6STXGgfH+m0DbOKayX6QGlq38TfE1ZfUcPc5+ulTuzDbZUnMo+bubsEOIypm4Pl2iEyzZ1cNg==, + integrity: sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==, } - "@pagefind/linux-arm64@1.3.0": + "@pagefind/freebsd-x64@1.4.0": resolution: { - integrity: sha512-8lsxNAiBRUk72JvetSBXs4WRpYrQrVJXjlRRnOL6UCdBN9Nlsz0t7hWstRk36+JqHpGWOKYiuHLzGYqYAqoOnQ==, + integrity: sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==, + } + cpu: [x64] + os: [freebsd] + + "@pagefind/linux-arm64@1.4.0": + resolution: + { + integrity: sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==, } cpu: [arm64] os: [linux] - "@pagefind/linux-x64@1.3.0": + "@pagefind/linux-x64@1.4.0": resolution: { - integrity: sha512-hAvqdPJv7A20Ucb6FQGE6jhjqy+vZ6pf+s2tFMNtMBG+fzcdc91uTw7aP/1Vo5plD0dAOHwdxfkyw0ugal4kcQ==, + integrity: sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==, } cpu: [x64] os: [linux] - "@pagefind/windows-x64@1.3.0": + "@pagefind/windows-x64@1.4.0": resolution: { - integrity: sha512-BR1bIRWOMqkf8IoU576YDhij1Wd/Zf2kX/kCI0b2qzCKC8wcc2GQJaaRMCpzvCCrmliO4vtJ6RITp/AnoYUUmQ==, + integrity: sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==, } cpu: [x64] os: [win32] + "@readme/better-ajv-errors@2.4.0": + resolution: + { + integrity: sha512-9WODaOAKSl/mU+MYNZ2aHCrkoRSvmQ+1YkLj589OEqqjOAhbn8j7Z+ilYoiTu/he6X63/clsxxAB4qny9/dDzg==, + } + engines: { node: ">=18" } + peerDependencies: + ajv: 4.11.8 - 8 + + "@readme/openapi-parser@4.1.2": + resolution: + { + integrity: sha512-lAFH88r/CHs5VZDUocEda0OSMSQsr6801sziIjOKyVA+0hSFN+BPuelPF5XvkMROHecnPd+XEJN1iNQqCgER/g==, + } + engines: { node: ">=20" } + peerDependencies: + openapi-types: ">=7" + + "@readme/openapi-schemas@3.1.0": + resolution: + { + integrity: sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==, + } + engines: { node: ">=18" } + "@rollup/pluginutils@5.3.0": resolution: { @@ -721,182 +1000,218 @@ packages: rollup: optional: true - "@rollup/rollup-android-arm-eabi@4.48.1": + "@rollup/rollup-android-arm-eabi@4.57.1": resolution: { - integrity: sha512-rGmb8qoG/zdmKoYELCBwu7vt+9HxZ7Koos3pD0+sH5fR3u3Wb/jGcpnqxcnWsPEKDUyzeLSqksN8LJtgXjqBYw==, + integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==, } cpu: [arm] os: [android] - "@rollup/rollup-android-arm64@4.48.1": + "@rollup/rollup-android-arm64@4.57.1": resolution: { - integrity: sha512-4e9WtTxrk3gu1DFE+imNJr4WsL13nWbD/Y6wQcyku5qadlKHY3OQ3LJ/INrrjngv2BJIHnIzbqMk1GTAC2P8yQ==, + integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==, } cpu: [arm64] os: [android] - "@rollup/rollup-darwin-arm64@4.48.1": + "@rollup/rollup-darwin-arm64@4.57.1": resolution: { - integrity: sha512-+XjmyChHfc4TSs6WUQGmVf7Hkg8ferMAE2aNYYWjiLzAS/T62uOsdfnqv+GHRjq7rKRnYh4mwWb4Hz7h/alp8A==, + integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==, } cpu: [arm64] os: [darwin] - "@rollup/rollup-darwin-x64@4.48.1": + "@rollup/rollup-darwin-x64@4.57.1": resolution: { - integrity: sha512-upGEY7Ftw8M6BAJyGwnwMw91rSqXTcOKZnnveKrVWsMTF8/k5mleKSuh7D4v4IV1pLxKAk3Tbs0Lo9qYmii5mQ==, + integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==, } cpu: [x64] os: [darwin] - "@rollup/rollup-freebsd-arm64@4.48.1": + "@rollup/rollup-freebsd-arm64@4.57.1": resolution: { - integrity: sha512-P9ViWakdoynYFUOZhqq97vBrhuvRLAbN/p2tAVJvhLb8SvN7rbBnJQcBu8e/rQts42pXGLVhfsAP0k9KXWa3nQ==, + integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==, } cpu: [arm64] os: [freebsd] - "@rollup/rollup-freebsd-x64@4.48.1": + "@rollup/rollup-freebsd-x64@4.57.1": resolution: { - integrity: sha512-VLKIwIpnBya5/saccM8JshpbxfyJt0Dsli0PjXozHwbSVaHTvWXJH1bbCwPXxnMzU4zVEfgD1HpW3VQHomi2AQ==, + integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==, } cpu: [x64] os: [freebsd] - "@rollup/rollup-linux-arm-gnueabihf@4.48.1": + "@rollup/rollup-linux-arm-gnueabihf@4.57.1": resolution: { - integrity: sha512-3zEuZsXfKaw8n/yF7t8N6NNdhyFw3s8xJTqjbTDXlipwrEHo4GtIKcMJr5Ed29leLpB9AugtAQpAHW0jvtKKaQ==, + integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==, } cpu: [arm] os: [linux] libc: [glibc] - "@rollup/rollup-linux-arm-musleabihf@4.48.1": + "@rollup/rollup-linux-arm-musleabihf@4.57.1": resolution: { - integrity: sha512-leo9tOIlKrcBmmEypzunV/2w946JeLbTdDlwEZ7OnnsUyelZ72NMnT4B2vsikSgwQifjnJUbdXzuW4ToN1wV+Q==, + integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==, } cpu: [arm] os: [linux] libc: [musl] - "@rollup/rollup-linux-arm64-gnu@4.48.1": + "@rollup/rollup-linux-arm64-gnu@4.57.1": resolution: { - integrity: sha512-Vy/WS4z4jEyvnJm+CnPfExIv5sSKqZrUr98h03hpAMbE2aI0aD2wvK6GiSe8Gx2wGp3eD81cYDpLLBqNb2ydwQ==, + integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==, } cpu: [arm64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-arm64-musl@4.48.1": + "@rollup/rollup-linux-arm64-musl@4.57.1": resolution: { - integrity: sha512-x5Kzn7XTwIssU9UYqWDB9VpLpfHYuXw5c6bJr4Mzv9kIv242vmJHbI5PJJEnmBYitUIfoMCODDhR7KoZLot2VQ==, + integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==, } cpu: [arm64] os: [linux] libc: [musl] - "@rollup/rollup-linux-loongarch64-gnu@4.48.1": + "@rollup/rollup-linux-loong64-gnu@4.57.1": resolution: { - integrity: sha512-yzCaBbwkkWt/EcgJOKDUdUpMHjhiZT/eDktOPWvSRpqrVE04p0Nd6EGV4/g7MARXXeOqstflqsKuXVM3H9wOIQ==, + integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==, } cpu: [loong64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-ppc64-gnu@4.48.1": + "@rollup/rollup-linux-loong64-musl@4.57.1": resolution: { - integrity: sha512-UK0WzWUjMAJccHIeOpPhPcKBqax7QFg47hwZTp6kiMhQHeOYJeaMwzeRZe1q5IiTKsaLnHu9s6toSYVUlZ2QtQ==, + integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==, + } + cpu: [loong64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-ppc64-gnu@4.57.1": + resolution: + { + integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==, } cpu: [ppc64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-riscv64-gnu@4.48.1": + "@rollup/rollup-linux-ppc64-musl@4.57.1": resolution: { - integrity: sha512-3NADEIlt+aCdCbWVZ7D3tBjBX1lHpXxcvrLt/kdXTiBrOds8APTdtk2yRL2GgmnSVeX4YS1JIf0imFujg78vpw==, + integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==, + } + cpu: [ppc64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-riscv64-gnu@4.57.1": + resolution: + { + integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==, } cpu: [riscv64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-riscv64-musl@4.48.1": + "@rollup/rollup-linux-riscv64-musl@4.57.1": resolution: { - integrity: sha512-euuwm/QTXAMOcyiFCcrx0/S2jGvFlKJ2Iro8rsmYL53dlblp3LkUQVFzEidHhvIPPvcIsxDhl2wkBE+I6YVGzA==, + integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==, } cpu: [riscv64] os: [linux] libc: [musl] - "@rollup/rollup-linux-s390x-gnu@4.48.1": + "@rollup/rollup-linux-s390x-gnu@4.57.1": resolution: { - integrity: sha512-w8mULUjmPdWLJgmTYJx/W6Qhln1a+yqvgwmGXcQl2vFBkWsKGUBRbtLRuKJUln8Uaimf07zgJNxOhHOvjSQmBQ==, + integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==, } cpu: [s390x] os: [linux] libc: [glibc] - "@rollup/rollup-linux-x64-gnu@4.48.1": + "@rollup/rollup-linux-x64-gnu@4.57.1": resolution: { - integrity: sha512-90taWXCWxTbClWuMZD0DKYohY1EovA+W5iytpE89oUPmT5O1HFdf8cuuVIylE6vCbrGdIGv85lVRzTcpTRZ+kA==, + integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==, } cpu: [x64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-x64-musl@4.48.1": + "@rollup/rollup-linux-x64-musl@4.57.1": resolution: { - integrity: sha512-2Gu29SkFh1FfTRuN1GR1afMuND2GKzlORQUP3mNMJbqdndOg7gNsa81JnORctazHRokiDzQ5+MLE5XYmZW5VWg==, + integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==, } cpu: [x64] os: [linux] libc: [musl] - "@rollup/rollup-win32-arm64-msvc@4.48.1": + "@rollup/rollup-openbsd-x64@4.57.1": resolution: { - integrity: sha512-6kQFR1WuAO50bxkIlAVeIYsz3RUx+xymwhTo9j94dJ+kmHe9ly7muH23sdfWduD0BA8pD9/yhonUvAjxGh34jQ==, + integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==, + } + cpu: [x64] + os: [openbsd] + + "@rollup/rollup-openharmony-arm64@4.57.1": + resolution: + { + integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==, + } + cpu: [arm64] + os: [openharmony] + + "@rollup/rollup-win32-arm64-msvc@4.57.1": + resolution: + { + integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==, } cpu: [arm64] os: [win32] - "@rollup/rollup-win32-ia32-msvc@4.48.1": + "@rollup/rollup-win32-ia32-msvc@4.57.1": resolution: { - integrity: sha512-RUyZZ/mga88lMI3RlXFs4WQ7n3VyU07sPXmMG7/C1NOi8qisUg57Y7LRarqoGoAiopmGmChUhSwfpvQ3H5iGSQ==, + integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==, } cpu: [ia32] os: [win32] - "@rollup/rollup-win32-x64-msvc@4.48.1": + "@rollup/rollup-win32-x64-gnu@4.57.1": resolution: { - integrity: sha512-8a/caCUN4vkTChxkaIJcMtwIVcBhi4X2PQRoT+yCK3qRYaZ7cURrmJFL5Ux9H9RaMIXj9RuihckdmkBX3zZsgg==, + integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==, } cpu: [x64] os: [win32] - "@shikijs/core@3.11.0": + "@rollup/rollup-win32-x64-msvc@4.57.1": resolution: { - integrity: sha512-oJwU+DxGqp6lUZpvtQgVOXNZcVsirN76tihOLBmwILkKuRuwHteApP8oTXmL4tF5vS5FbOY0+8seXmiCoslk4g==, + integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==, } + cpu: [x64] + os: [win32] "@shikijs/core@3.22.0": resolution: @@ -904,60 +1219,30 @@ packages: integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==, } - "@shikijs/engine-javascript@3.11.0": - resolution: - { - integrity: sha512-6/ov6pxrSvew13k9ztIOnSBOytXeKs5kfIR7vbhdtVRg+KPzvp2HctYGeWkqv7V6YIoLicnig/QF3iajqyElZA==, - } - "@shikijs/engine-javascript@3.22.0": resolution: { integrity: sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==, } - "@shikijs/engine-oniguruma@3.11.0": - resolution: - { - integrity: sha512-4DwIjIgETK04VneKbfOE4WNm4Q7WC1wo95wv82PoHKdqX4/9qLRUwrfKlmhf0gAuvT6GHy0uc7t9cailk6Tbhw==, - } - "@shikijs/engine-oniguruma@3.22.0": resolution: { integrity: sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==, } - "@shikijs/langs@3.11.0": - resolution: - { - integrity: sha512-Njg/nFL4HDcf/ObxcK2VeyidIq61EeLmocrwTHGGpOQx0BzrPWM1j55XtKQ1LvvDWH15cjQy7rg96aJ1/l63uw==, - } - "@shikijs/langs@3.22.0": resolution: { integrity: sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==, } - "@shikijs/themes@3.11.0": - resolution: - { - integrity: sha512-BhhWRzCTEk2CtWt4S4bgsOqPJRkapvxdsifAwqP+6mk5uxboAQchc0etiJ0iIasxnMsb764qGD24DK9albcU9Q==, - } - "@shikijs/themes@3.22.0": resolution: { integrity: sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==, } - "@shikijs/types@3.11.0": - resolution: - { - integrity: sha512-RB7IMo2E7NZHyfkqAuaf4CofyY8bPzjWPjJRzn6SEak3b46fIQyG6Vx5fG/obqkfppQ+g8vEsiD7Uc6lqQt32Q==, - } - "@shikijs/types@3.22.0": resolution: { @@ -1000,6 +1285,12 @@ packages: integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==, } + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + "@types/mdast@4.0.4": resolution: { @@ -1030,12 +1321,6 @@ packages: integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==, } - "@types/node@24.3.0": - resolution: - { - integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==, - } - "@types/sax@1.2.7": resolution: { @@ -1076,6 +1361,23 @@ packages: engines: { node: ">=0.4.0" } hasBin: true + ajv-draft-04@1.0.0: + resolution: + { + integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==, + } + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: + { + integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==, + } + ansi-align@3.0.1: resolution: { @@ -1089,17 +1391,17 @@ packages: } engines: { node: ">=8" } - ansi-regex@6.2.0: + ansi-regex@6.2.2: resolution: { - integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==, + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, } engines: { node: ">=12" } - ansi-styles@6.2.1: + ansi-styles@6.2.3: resolution: { - integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==, + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, } engines: { node: ">=12" } @@ -1142,18 +1444,18 @@ packages: } hasBin: true - astro-expressive-code@0.41.3: + astro-expressive-code@0.41.6: resolution: { - integrity: sha512-u+zHMqo/QNLE2eqYRCrK3+XMlKakv33Bzuz+56V1gs8H0y6TZ0hIi3VNbIxeTn51NLn+mJfUV/A0kMNfE4rANw==, + integrity: sha512-l47tb1uhmVIebHUkw+HEPtU/av0G4O8Q34g2cbkPvC7/e9ZhANcjUUciKt9Hp6gSVDdIuXBBLwJQn2LkeGMOAw==, } peerDependencies: - astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta - astro@5.17.1: + astro@5.17.2: resolution: { - integrity: sha512-oD3tlxTaVWGq/Wfbqk6gxzVRz98xa/rYlpe+gU2jXJMSD01k6sEDL01ZlT8mVSYB/rMgnvIOfiQQ3BbLdN237A==, + integrity: sha512-7jnMqGo53hOQNwo1N/wqeOvUp8wwW/p+DeerSjSkHNx8L/1mhy6P7rVo7EhdmF8DpKqw0tl/B5Fx1WcIzg1ysA==, } engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: ">=9.6.5", pnpm: ">=7.1.0" } @@ -1216,10 +1518,10 @@ packages: integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, } - chalk@5.6.0: + chalk@5.6.2: resolution: { - integrity: sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==, + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, } engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } @@ -1325,10 +1627,10 @@ packages: integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==, } - css-selector-parser@3.1.3: + css-selector-parser@3.3.0: resolution: { - integrity: sha512-gJMigczVZqYAk0hPVzx/M4Hm1D9QOtqkdQk9005TNzDIUGzo5cnHEDiKUT7jGPximL/oYb+LIitcHFQ4aKupxg==, + integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==, } css-tree@2.2.1: @@ -1367,18 +1669,6 @@ packages: } engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: ">=7.0.0" } - debug@4.4.1: - resolution: - { - integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, - } - engines: { node: ">=6.0" } - peerDependencies: - supports-color: "*" - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: { @@ -1391,10 +1681,10 @@ packages: supports-color: optional: true - decode-named-character-reference@1.2.0: + decode-named-character-reference@1.3.0: resolution: { - integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==, + integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==, } defu@6.1.4: @@ -1494,10 +1784,10 @@ packages: } engines: { node: ">=4" } - emoji-regex@10.4.0: + emoji-regex@10.6.0: resolution: { - integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==, + integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, } emoji-regex@8.0.0: @@ -1538,10 +1828,18 @@ packages: integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==, } - esbuild@0.25.9: + esbuild@0.25.12: resolution: { - integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==, + integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==, + } + engines: { node: ">=18" } + hasBin: true + + esbuild@0.27.3: + resolution: + { + integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==, } engines: { node: ">=18" } hasBin: true @@ -1601,16 +1899,16 @@ packages: integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, } - eventemitter3@5.0.1: + eventemitter3@5.0.4: resolution: { - integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==, + integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, } - expressive-code@0.41.3: + expressive-code@0.41.6: resolution: { - integrity: sha512-YLnD62jfgBZYrXIPQcJ0a51Afv9h8VlWqEGK9uU2T5nL/5rb8SnA86+7+mgCZe5D34Tff5RNEA5hjNVJYHzrFg==, + integrity: sha512-W/5+IQbrpCIM5KGLjO35wlp1NCwDOOVQb+PAvzEoGkW1xjGM807ZGfBKptNWH6UECvt6qgmLyWolCMYKh7eQmA==, } extend@3.0.2: @@ -1619,6 +1917,18 @@ packages: integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, } + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, + } + fdir@6.5.0: resolution: { @@ -1659,10 +1969,10 @@ packages: engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] - get-east-asian-width@1.3.0: + get-east-asian-width@1.4.0: resolution: { - integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==, + integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==, } engines: { node: ">=18" } @@ -1768,10 +2078,10 @@ packages: integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==, } - hast-util-to-parse5@8.0.0: + hast-util-to-parse5@8.0.1: resolution: { - integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==, + integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==, } hast-util-to-string@3.0.1: @@ -1828,22 +2138,16 @@ packages: integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==, } - import-meta-resolve@4.1.0: - resolution: - { - integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==, - } - import-meta-resolve@4.2.0: resolution: { integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==, } - inline-style-parser@0.2.4: + inline-style-parser@0.2.7: resolution: { - integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==, + integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==, } iron-webcrypto@1.2.1: @@ -1906,19 +2210,18 @@ packages: } engines: { node: ">=12" } - is-wsl@3.1.0: + is-wsl@3.1.1: resolution: { - integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==, + integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==, } engines: { node: ">=16" } - js-yaml@4.1.0: + js-tokens@4.0.0: resolution: { - integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, } - hasBin: true js-yaml@4.1.1: resolution: @@ -1927,6 +2230,19 @@ packages: } hasBin: true + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, + } + + jsonpointer@5.0.1: + resolution: + { + integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==, + } + engines: { node: ">=0.10.0" } + kleur@3.0.3: resolution: { @@ -1934,13 +2250,6 @@ packages: } engines: { node: ">=6" } - kleur@4.1.5: - resolution: - { - integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==, - } - engines: { node: ">=6" } - klona@2.0.6: resolution: { @@ -1948,25 +2257,26 @@ packages: } engines: { node: ">= 8" } + leven@3.1.0: + resolution: + { + integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, + } + engines: { node: ">=6" } + longest-streak@3.1.0: resolution: { integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, } - lru-cache@11.2.5: + lru-cache@11.2.6: resolution: { - integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==, + integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==, } engines: { node: 20 || >=22 } - magic-string@0.30.18: - resolution: - { - integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==, - } - magic-string@0.30.21: resolution: { @@ -2082,10 +2392,10 @@ packages: integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==, } - mdast-util-to-hast@13.2.0: + mdast-util-to-hast@13.2.1: resolution: { - integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==, + integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==, } mdast-util-to-markdown@2.1.2: @@ -2405,18 +2715,18 @@ packages: integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==, } - oniguruma-to-es@4.3.3: - resolution: - { - integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==, - } - oniguruma-to-es@4.3.4: resolution: { integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==, } + openapi-types@12.1.3: + resolution: + { + integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==, + } + p-limit@6.2.0: resolution: { @@ -2444,10 +2754,10 @@ packages: integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==, } - pagefind@1.3.0: + pagefind@1.4.0: resolution: { - integrity: sha512-8KPLGT5g9s+olKMRTU9LFekLizkVIu9tes90O1/aigJ0T5LmyPqTzGJrETnSw3meSYg58YH7JTzhTTW/3z6VAw==, + integrity: sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==, } hasBin: true @@ -2532,12 +2842,6 @@ packages: } engines: { node: ">= 6" } - property-information@6.5.0: - resolution: - { - integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==, - } - property-information@7.1.0: resolution: { @@ -2595,16 +2899,16 @@ packages: integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==, } - regex@6.0.1: + regex@6.1.0: resolution: { - integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==, + integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==, } - rehype-expressive-code@0.41.3: + rehype-expressive-code@0.41.6: resolution: { - integrity: sha512-8d9Py4c/V6I/Od2VIXFAdpiO2kc0SV2qTJsRAaqSIcM9aruW4ASLNe2kOEo1inXAAkIhpFzAHTc358HKbvpNUg==, + integrity: sha512-aBMX8kxPtjmDSFUdZlAWJkMvsQ4ZMASfee90JWIAV8tweltXLzkWC3q++43ToTelI8ac5iC0B3/S/Cl4Ql1y2g==, } rehype-format@5.0.1: @@ -2655,10 +2959,10 @@ packages: integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==, } - remark-mdx@3.1.0: + remark-mdx@3.1.1: resolution: { - integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==, + integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==, } remark-parse@11.0.0: @@ -2686,6 +2990,13 @@ packages: integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, } + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, + } + engines: { node: ">=0.10.0" } + retext-latin@4.0.0: resolution: { @@ -2710,19 +3021,20 @@ packages: integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==, } - rollup@4.48.1: + rollup@4.57.1: resolution: { - integrity: sha512-jVG20NvbhTYDkGAty2/Yh7HK6/q3DGSRH4o8ALKGArmMuaauM9kLfoMZ+WliPwA5+JHr2lTn3g557FxBV87ifg==, + integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==, } engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true - sax@1.4.1: + sax@1.4.4: resolution: { - integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==, + integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==, } + engines: { node: ">=11.0.0" } semver@7.7.4: resolution: @@ -2739,12 +3051,6 @@ packages: } engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - shiki@3.11.0: - resolution: - { - integrity: sha512-VgKumh/ib38I1i3QkMn6mAQA6XjjQubqaAYhfge71glAll0/4xnt8L2oSuC45Qcr/G5Kbskj4RliMQddGmy/Og==, - } - shiki@3.22.0: resolution: { @@ -2757,22 +3063,14 @@ packages: integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==, } - sitemap@8.0.0: + sitemap@8.0.2: resolution: { - integrity: sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==, + integrity: sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==, } engines: { node: ">=14.0.0", npm: ">=6.0.0" } - deprecated: "SECURITY: Multiple vulnerabilities fixed in 8.0.1 (XML injection, path traversal, command injection, protocol injection). Upgrade immediately: npm install sitemap@8.0.1" hasBin: true - smol-toml@1.4.2: - resolution: - { - integrity: sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==, - } - engines: { node: ">= 18" } - smol-toml@1.6.0: resolution: { @@ -2800,6 +3098,17 @@ packages: integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==, } + starlight-openapi@0.22.0: + resolution: + { + integrity: sha512-4H/fywAoTcvKbcv+xBr9LdrjMc5geDOSnydvzpiOxjxkHvI6g+7uPhWNCejnJPyZUd/MC1MI23tvYug85d/WzA==, + } + engines: { node: ">=18.17.1" } + peerDependencies: + "@astrojs/markdown-remark": ">=6.0.1" + "@astrojs/starlight": ">=0.34.0" + astro: ">=5.5.0" + stream-replace-string@2.0.0: resolution: { @@ -2833,23 +3142,23 @@ packages: } engines: { node: ">=8" } - strip-ansi@7.1.0: + strip-ansi@7.1.2: resolution: { - integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==, + integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==, } engines: { node: ">=12" } - style-to-js@1.1.17: + style-to-js@1.1.21: resolution: { - integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==, + integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==, } - style-to-object@1.0.9: + style-to-object@1.0.14: resolution: { - integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==, + integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==, } svgo@4.0.0: @@ -2918,20 +3227,14 @@ packages: } engines: { node: ">=16" } - typescript@5.9.2: + typescript@5.9.3: resolution: { - integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==, + integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, } engines: { node: ">=14.17" } hasBin: true - ufo@1.6.1: - resolution: - { - integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==, - } - ufo@1.6.3: resolution: { @@ -2950,22 +3253,16 @@ packages: integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==, } - undici-types@7.10.0: - resolution: - { - integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==, - } - unified@11.0.5: resolution: { integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==, } - unifont@0.7.3: + unifont@0.7.4: resolution: { - integrity: sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA==, + integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==, } unist-util-find-after@5.0.0: @@ -2974,10 +3271,10 @@ packages: integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==, } - unist-util-is@6.0.0: + unist-util-is@6.0.1: resolution: { - integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==, + integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==, } unist-util-modify-children@4.0.0: @@ -3016,22 +3313,16 @@ packages: integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==, } - unist-util-visit-parents@6.0.1: - resolution: - { - integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==, - } - unist-util-visit-parents@6.0.2: resolution: { integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==, } - unist-util-visit@5.0.0: + unist-util-visit@5.1.0: resolution: { - integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==, + integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==, } unstorage@1.17.4: @@ -3099,6 +3390,13 @@ packages: uploadthing: optional: true + url-template@3.1.1: + resolution: + { + integrity: sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + util-deprecate@1.0.2: resolution: { @@ -3197,10 +3495,10 @@ packages: } engines: { node: ">=18" } - wrap-ansi@9.0.0: + wrap-ansi@9.0.2: resolution: { - integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==, + integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, } engines: { node: ">=18" } @@ -3217,10 +3515,10 @@ packages: } engines: { node: ">=12" } - yocto-queue@1.2.1: + yocto-queue@1.2.2: resolution: { - integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==, + integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==, } engines: { node: ">=12.20" } @@ -3268,9 +3566,12 @@ packages: } snapshots: - "@astrojs/compiler@2.13.1": {} + "@apidevtools/json-schema-ref-parser@13.0.5": + dependencies: + "@types/json-schema": 7.0.15 + js-yaml: 4.1.1 - "@astrojs/internal-helpers@0.7.2": {} + "@astrojs/compiler@2.13.1": {} "@astrojs/internal-helpers@0.7.5": {} @@ -3294,53 +3595,27 @@ snapshots: smol-toml: 1.6.0 unified: 11.0.5 unist-util-remove-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 unist-util-visit-parents: 6.0.2 vfile: 6.0.3 transitivePeerDependencies: - supports-color - "@astrojs/markdown-remark@6.3.6": + "@astrojs/mdx@4.3.13(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3))": dependencies: - "@astrojs/internal-helpers": 0.7.2 - "@astrojs/prism": 3.3.0 - github-slugger: 2.0.0 - hast-util-from-html: 2.0.3 - hast-util-to-text: 4.0.2 - import-meta-resolve: 4.1.0 - js-yaml: 4.1.0 - mdast-util-definitions: 6.0.0 - rehype-raw: 7.0.0 - rehype-stringify: 10.0.1 - remark-gfm: 4.0.1 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - remark-smartypants: 3.0.2 - shiki: 3.11.0 - smol-toml: 1.4.2 - unified: 11.0.5 - unist-util-remove-position: 5.0.0 - unist-util-visit: 5.0.0 - unist-util-visit-parents: 6.0.1 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - "@astrojs/mdx@4.3.4(astro@5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2))": - dependencies: - "@astrojs/markdown-remark": 6.3.6 - "@mdx-js/mdx": 3.1.0(acorn@8.15.0) + "@astrojs/markdown-remark": 6.3.10 + "@mdx-js/mdx": 3.1.1 acorn: 8.15.0 - astro: 5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2) + astro: 5.17.2(rollup@4.57.1)(typescript@5.9.3) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 - kleur: 4.1.5 + piccolore: 0.1.3 rehype-raw: 7.0.0 remark-gfm: 4.0.1 remark-smartypants: 3.0.2 source-map: 0.7.6 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color @@ -3349,42 +3624,42 @@ snapshots: dependencies: prismjs: 1.30.0 - "@astrojs/sitemap@3.5.1": + "@astrojs/sitemap@3.7.0": dependencies: - sitemap: 8.0.0 + sitemap: 8.0.2 stream-replace-string: 2.0.0 zod: 3.25.76 - "@astrojs/starlight@0.37.6(astro@5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2))": + "@astrojs/starlight@0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3))": dependencies: - "@astrojs/markdown-remark": 6.3.6 - "@astrojs/mdx": 4.3.4(astro@5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2)) - "@astrojs/sitemap": 3.5.1 - "@pagefind/default-ui": 1.3.0 + "@astrojs/markdown-remark": 6.3.10 + "@astrojs/mdx": 4.3.13(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)) + "@astrojs/sitemap": 3.7.0 + "@pagefind/default-ui": 1.4.0 "@types/hast": 3.0.4 "@types/js-yaml": 4.0.9 "@types/mdast": 4.0.4 - astro: 5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2) - astro-expressive-code: 0.41.3(astro@5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2)) + astro: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + astro-expressive-code: 0.41.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.4 hast-util-to-string: 3.0.1 hastscript: 9.0.1 i18next: 23.16.8 - js-yaml: 4.1.0 + js-yaml: 4.1.1 klona: 2.0.6 - magic-string: 0.30.18 + magic-string: 0.30.21 mdast-util-directive: 3.1.0 mdast-util-to-markdown: 2.1.2 mdast-util-to-string: 4.0.0 - pagefind: 1.3.0 + pagefind: 1.4.0 rehype: 13.0.2 rehype-format: 5.0.1 remark-directive: 3.0.1 ultrahtml: 1.6.0 unified: 11.0.5 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color @@ -3396,11 +3671,17 @@ snapshots: dlv: 1.1.3 dset: 3.1.4 is-docker: 3.0.0 - is-wsl: 3.1.0 + is-wsl: 3.1.1 which-pm-runs: 1.1.0 transitivePeerDependencies: - supports-color + "@babel/code-frame@7.29.0": + dependencies: + "@babel/helper-validator-identifier": 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + "@babel/helper-string-parser@7.27.1": {} "@babel/helper-validator-identifier@7.28.5": {} @@ -3409,7 +3690,7 @@ snapshots: dependencies: "@babel/types": 7.29.0 - "@babel/runtime@7.28.3": {} + "@babel/runtime@7.28.6": {} "@babel/types@7.29.0": dependencies: @@ -3420,120 +3701,200 @@ snapshots: dependencies: fontkitten: 1.0.2 - "@ctrl/tinycolor@4.1.0": {} + "@ctrl/tinycolor@4.2.0": {} "@emnapi/runtime@1.8.1": dependencies: tslib: 2.8.1 optional: true - "@esbuild/aix-ppc64@0.25.9": + "@esbuild/aix-ppc64@0.25.12": optional: true - "@esbuild/android-arm64@0.25.9": + "@esbuild/aix-ppc64@0.27.3": optional: true - "@esbuild/android-arm@0.25.9": + "@esbuild/android-arm64@0.25.12": optional: true - "@esbuild/android-x64@0.25.9": + "@esbuild/android-arm64@0.27.3": optional: true - "@esbuild/darwin-arm64@0.25.9": + "@esbuild/android-arm@0.25.12": optional: true - "@esbuild/darwin-x64@0.25.9": + "@esbuild/android-arm@0.27.3": optional: true - "@esbuild/freebsd-arm64@0.25.9": + "@esbuild/android-x64@0.25.12": optional: true - "@esbuild/freebsd-x64@0.25.9": + "@esbuild/android-x64@0.27.3": optional: true - "@esbuild/linux-arm64@0.25.9": + "@esbuild/darwin-arm64@0.25.12": optional: true - "@esbuild/linux-arm@0.25.9": + "@esbuild/darwin-arm64@0.27.3": optional: true - "@esbuild/linux-ia32@0.25.9": + "@esbuild/darwin-x64@0.25.12": optional: true - "@esbuild/linux-loong64@0.25.9": + "@esbuild/darwin-x64@0.27.3": optional: true - "@esbuild/linux-mips64el@0.25.9": + "@esbuild/freebsd-arm64@0.25.12": optional: true - "@esbuild/linux-ppc64@0.25.9": + "@esbuild/freebsd-arm64@0.27.3": optional: true - "@esbuild/linux-riscv64@0.25.9": + "@esbuild/freebsd-x64@0.25.12": optional: true - "@esbuild/linux-s390x@0.25.9": + "@esbuild/freebsd-x64@0.27.3": optional: true - "@esbuild/linux-x64@0.25.9": + "@esbuild/linux-arm64@0.25.12": optional: true - "@esbuild/netbsd-arm64@0.25.9": + "@esbuild/linux-arm64@0.27.3": optional: true - "@esbuild/netbsd-x64@0.25.9": + "@esbuild/linux-arm@0.25.12": optional: true - "@esbuild/openbsd-arm64@0.25.9": + "@esbuild/linux-arm@0.27.3": optional: true - "@esbuild/openbsd-x64@0.25.9": + "@esbuild/linux-ia32@0.25.12": optional: true - "@esbuild/openharmony-arm64@0.25.9": + "@esbuild/linux-ia32@0.27.3": optional: true - "@esbuild/sunos-x64@0.25.9": + "@esbuild/linux-loong64@0.25.12": optional: true - "@esbuild/win32-arm64@0.25.9": + "@esbuild/linux-loong64@0.27.3": optional: true - "@esbuild/win32-ia32@0.25.9": + "@esbuild/linux-mips64el@0.25.12": optional: true - "@esbuild/win32-x64@0.25.9": + "@esbuild/linux-mips64el@0.27.3": optional: true - "@expressive-code/core@0.41.3": + "@esbuild/linux-ppc64@0.25.12": + optional: true + + "@esbuild/linux-ppc64@0.27.3": + optional: true + + "@esbuild/linux-riscv64@0.25.12": + optional: true + + "@esbuild/linux-riscv64@0.27.3": + optional: true + + "@esbuild/linux-s390x@0.25.12": + optional: true + + "@esbuild/linux-s390x@0.27.3": + optional: true + + "@esbuild/linux-x64@0.25.12": + optional: true + + "@esbuild/linux-x64@0.27.3": + optional: true + + "@esbuild/netbsd-arm64@0.25.12": + optional: true + + "@esbuild/netbsd-arm64@0.27.3": + optional: true + + "@esbuild/netbsd-x64@0.25.12": + optional: true + + "@esbuild/netbsd-x64@0.27.3": + optional: true + + "@esbuild/openbsd-arm64@0.25.12": + optional: true + + "@esbuild/openbsd-arm64@0.27.3": + optional: true + + "@esbuild/openbsd-x64@0.25.12": + optional: true + + "@esbuild/openbsd-x64@0.27.3": + optional: true + + "@esbuild/openharmony-arm64@0.25.12": + optional: true + + "@esbuild/openharmony-arm64@0.27.3": + optional: true + + "@esbuild/sunos-x64@0.25.12": + optional: true + + "@esbuild/sunos-x64@0.27.3": + optional: true + + "@esbuild/win32-arm64@0.25.12": + optional: true + + "@esbuild/win32-arm64@0.27.3": + optional: true + + "@esbuild/win32-ia32@0.25.12": + optional: true + + "@esbuild/win32-ia32@0.27.3": + optional: true + + "@esbuild/win32-x64@0.25.12": + optional: true + + "@esbuild/win32-x64@0.27.3": + optional: true + + "@expressive-code/core@0.41.6": dependencies: - "@ctrl/tinycolor": 4.1.0 + "@ctrl/tinycolor": 4.2.0 hast-util-select: 6.0.4 hast-util-to-html: 9.0.5 hast-util-to-text: 4.0.2 hastscript: 9.0.1 postcss: 8.5.6 postcss-nested: 6.2.0(postcss@8.5.6) - unist-util-visit: 5.0.0 - unist-util-visit-parents: 6.0.1 + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 - "@expressive-code/plugin-frames@0.41.3": + "@expressive-code/plugin-frames@0.41.6": dependencies: - "@expressive-code/core": 0.41.3 + "@expressive-code/core": 0.41.6 - "@expressive-code/plugin-shiki@0.41.3": + "@expressive-code/plugin-shiki@0.41.6": dependencies: - "@expressive-code/core": 0.41.3 - shiki: 3.11.0 + "@expressive-code/core": 0.41.6 + shiki: 3.22.0 - "@expressive-code/plugin-text-markers@0.41.3": + "@expressive-code/plugin-text-markers@0.41.6": dependencies: - "@expressive-code/core": 0.41.3 + "@expressive-code/core": 0.41.6 "@fontsource/inter@5.2.8": {} "@fontsource/rubik@5.2.8": {} + "@humanwhocodes/momoa@2.0.4": {} + "@img/colour@1.0.0": {} "@img/sharp-darwin-arm64@0.34.5": @@ -3632,12 +3993,13 @@ snapshots: "@jridgewell/sourcemap-codec@1.5.5": {} - "@mdx-js/mdx@3.1.0(acorn@8.15.0)": + "@mdx-js/mdx@3.1.1": dependencies: "@types/estree": 1.0.8 "@types/estree-jsx": 1.0.5 "@types/hast": 3.0.4 "@types/mdx": 2.0.13 + acorn: 8.15.0 collapse-white-space: 2.1.0 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 @@ -3649,112 +4011,144 @@ snapshots: recma-jsx: 1.0.1(acorn@8.15.0) recma-stringify: 1.0.0 rehype-recma: 1.0.0 - remark-mdx: 3.1.0 + remark-mdx: 3.1.1 remark-parse: 11.0.0 remark-rehype: 11.1.2 source-map: 0.7.6 unified: 11.0.5 unist-util-position-from-estree: 2.0.0 unist-util-stringify-position: 4.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 transitivePeerDependencies: - - acorn - supports-color "@oslojs/encoding@1.1.0": {} - "@pagefind/darwin-arm64@1.3.0": + "@pagefind/darwin-arm64@1.4.0": optional: true - "@pagefind/darwin-x64@1.3.0": + "@pagefind/darwin-x64@1.4.0": optional: true - "@pagefind/default-ui@1.3.0": {} + "@pagefind/default-ui@1.4.0": {} - "@pagefind/linux-arm64@1.3.0": + "@pagefind/freebsd-x64@1.4.0": optional: true - "@pagefind/linux-x64@1.3.0": + "@pagefind/linux-arm64@1.4.0": optional: true - "@pagefind/windows-x64@1.3.0": + "@pagefind/linux-x64@1.4.0": optional: true - "@rollup/pluginutils@5.3.0(rollup@4.48.1)": + "@pagefind/windows-x64@1.4.0": + optional: true + + "@readme/better-ajv-errors@2.4.0(ajv@8.18.0)": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/runtime": 7.28.6 + "@humanwhocodes/momoa": 2.0.4 + ajv: 8.18.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + picocolors: 1.1.1 + + "@readme/openapi-parser@4.1.2(openapi-types@12.1.3)": + dependencies: + "@apidevtools/json-schema-ref-parser": 13.0.5 + "@readme/better-ajv-errors": 2.4.0(ajv@8.18.0) + "@readme/openapi-schemas": 3.1.0 + "@types/json-schema": 7.0.15 + ajv: 8.18.0 + ajv-draft-04: 1.0.0(ajv@8.18.0) + openapi-types: 12.1.3 + + "@readme/openapi-schemas@3.1.0": {} + + "@rollup/pluginutils@5.3.0(rollup@4.57.1)": dependencies: "@types/estree": 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.48.1 + rollup: 4.57.1 - "@rollup/rollup-android-arm-eabi@4.48.1": + "@rollup/rollup-android-arm-eabi@4.57.1": optional: true - "@rollup/rollup-android-arm64@4.48.1": + "@rollup/rollup-android-arm64@4.57.1": optional: true - "@rollup/rollup-darwin-arm64@4.48.1": + "@rollup/rollup-darwin-arm64@4.57.1": optional: true - "@rollup/rollup-darwin-x64@4.48.1": + "@rollup/rollup-darwin-x64@4.57.1": optional: true - "@rollup/rollup-freebsd-arm64@4.48.1": + "@rollup/rollup-freebsd-arm64@4.57.1": optional: true - "@rollup/rollup-freebsd-x64@4.48.1": + "@rollup/rollup-freebsd-x64@4.57.1": optional: true - "@rollup/rollup-linux-arm-gnueabihf@4.48.1": + "@rollup/rollup-linux-arm-gnueabihf@4.57.1": optional: true - "@rollup/rollup-linux-arm-musleabihf@4.48.1": + "@rollup/rollup-linux-arm-musleabihf@4.57.1": optional: true - "@rollup/rollup-linux-arm64-gnu@4.48.1": + "@rollup/rollup-linux-arm64-gnu@4.57.1": optional: true - "@rollup/rollup-linux-arm64-musl@4.48.1": + "@rollup/rollup-linux-arm64-musl@4.57.1": optional: true - "@rollup/rollup-linux-loongarch64-gnu@4.48.1": + "@rollup/rollup-linux-loong64-gnu@4.57.1": optional: true - "@rollup/rollup-linux-ppc64-gnu@4.48.1": + "@rollup/rollup-linux-loong64-musl@4.57.1": optional: true - "@rollup/rollup-linux-riscv64-gnu@4.48.1": + "@rollup/rollup-linux-ppc64-gnu@4.57.1": optional: true - "@rollup/rollup-linux-riscv64-musl@4.48.1": + "@rollup/rollup-linux-ppc64-musl@4.57.1": optional: true - "@rollup/rollup-linux-s390x-gnu@4.48.1": + "@rollup/rollup-linux-riscv64-gnu@4.57.1": optional: true - "@rollup/rollup-linux-x64-gnu@4.48.1": + "@rollup/rollup-linux-riscv64-musl@4.57.1": optional: true - "@rollup/rollup-linux-x64-musl@4.48.1": + "@rollup/rollup-linux-s390x-gnu@4.57.1": optional: true - "@rollup/rollup-win32-arm64-msvc@4.48.1": + "@rollup/rollup-linux-x64-gnu@4.57.1": optional: true - "@rollup/rollup-win32-ia32-msvc@4.48.1": + "@rollup/rollup-linux-x64-musl@4.57.1": optional: true - "@rollup/rollup-win32-x64-msvc@4.48.1": + "@rollup/rollup-openbsd-x64@4.57.1": optional: true - "@shikijs/core@3.11.0": - dependencies: - "@shikijs/types": 3.11.0 - "@shikijs/vscode-textmate": 10.0.2 - "@types/hast": 3.0.4 - hast-util-to-html: 9.0.5 + "@rollup/rollup-openharmony-arm64@4.57.1": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.57.1": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.57.1": + optional: true + + "@rollup/rollup-win32-x64-gnu@4.57.1": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.57.1": + optional: true "@shikijs/core@3.22.0": dependencies: @@ -3763,49 +4157,25 @@ snapshots: "@types/hast": 3.0.4 hast-util-to-html: 9.0.5 - "@shikijs/engine-javascript@3.11.0": - dependencies: - "@shikijs/types": 3.11.0 - "@shikijs/vscode-textmate": 10.0.2 - oniguruma-to-es: 4.3.3 - "@shikijs/engine-javascript@3.22.0": dependencies: "@shikijs/types": 3.22.0 "@shikijs/vscode-textmate": 10.0.2 oniguruma-to-es: 4.3.4 - "@shikijs/engine-oniguruma@3.11.0": - dependencies: - "@shikijs/types": 3.11.0 - "@shikijs/vscode-textmate": 10.0.2 - "@shikijs/engine-oniguruma@3.22.0": dependencies: "@shikijs/types": 3.22.0 "@shikijs/vscode-textmate": 10.0.2 - "@shikijs/langs@3.11.0": - dependencies: - "@shikijs/types": 3.11.0 - "@shikijs/langs@3.22.0": dependencies: "@shikijs/types": 3.22.0 - "@shikijs/themes@3.11.0": - dependencies: - "@shikijs/types": 3.11.0 - "@shikijs/themes@3.22.0": dependencies: "@shikijs/types": 3.22.0 - "@shikijs/types@3.11.0": - dependencies: - "@shikijs/vscode-textmate": 10.0.2 - "@types/hast": 3.0.4 - "@shikijs/types@3.22.0": dependencies: "@shikijs/vscode-textmate": 10.0.2 @@ -3829,6 +4199,8 @@ snapshots: "@types/js-yaml@4.0.9": {} + "@types/json-schema@7.0.15": {} + "@types/mdast@4.0.4": dependencies: "@types/unist": 3.0.3 @@ -3843,13 +4215,9 @@ snapshots: "@types/node@17.0.45": {} - "@types/node@24.3.0": - dependencies: - undici-types: 7.10.0 - "@types/sax@1.2.7": dependencies: - "@types/node": 24.3.0 + "@types/node": 17.0.45 "@types/unist@2.0.11": {} @@ -3863,15 +4231,26 @@ snapshots: acorn@8.15.0: {} + ajv-draft-04@1.0.0(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-align@3.0.1: dependencies: string-width: 4.2.3 ansi-regex@5.0.1: {} - ansi-regex@6.2.0: {} + ansi-regex@6.2.2: {} - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} anymatch@3.1.3: dependencies: @@ -3888,12 +4267,12 @@ snapshots: astring@1.9.0: {} - astro-expressive-code@0.41.3(astro@5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2)): + astro-expressive-code@0.41.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)): dependencies: - astro: 5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2) - rehype-expressive-code: 0.41.3 + astro: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + rehype-expressive-code: 0.41.6 - astro@5.17.1(@types/node@24.3.0)(rollup@4.48.1)(typescript@5.9.2): + astro@5.17.2(rollup@4.57.1)(typescript@5.9.3): dependencies: "@astrojs/compiler": 2.13.1 "@astrojs/internal-helpers": 0.7.5 @@ -3901,7 +4280,7 @@ snapshots: "@astrojs/telemetry": 3.3.0 "@capsizecss/unpack": 4.0.0 "@oslojs/encoding": 1.1.0 - "@rollup/pluginutils": 5.3.0(rollup@4.48.1) + "@rollup/pluginutils": 5.3.0(rollup@4.57.1) acorn: 8.15.0 aria-query: 5.3.2 axobject-query: 4.1.0 @@ -3918,7 +4297,7 @@ snapshots: dlv: 1.1.3 dset: 3.1.4 es-module-lexer: 1.7.0 - esbuild: 0.25.9 + esbuild: 0.27.3 estree-walker: 3.0.3 flattie: 1.1.1 fontace: 0.4.1 @@ -3944,20 +4323,20 @@ snapshots: svgo: 4.0.0 tinyexec: 1.0.2 tinyglobby: 0.2.15 - tsconfck: 3.1.6(typescript@5.9.2) + tsconfck: 3.1.6(typescript@5.9.3) ultrahtml: 1.6.0 - unifont: 0.7.3 - unist-util-visit: 5.0.0 + unifont: 0.7.4 + unist-util-visit: 5.1.0 unstorage: 1.17.4 vfile: 6.0.3 - vite: 6.4.1(@types/node@24.3.0) - vitefu: 1.1.1(vite@6.4.1(@types/node@24.3.0)) + vite: 6.4.1 + vitefu: 1.1.1(vite@6.4.1) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.3 zod: 3.25.76 zod-to-json-schema: 3.25.1(zod@3.25.76) - zod-to-ts: 1.2.0(typescript@5.9.2)(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) optionalDependencies: sharp: 0.34.5 transitivePeerDependencies: @@ -4015,18 +4394,18 @@ snapshots: dependencies: ansi-align: 3.0.1 camelcase: 8.0.0 - chalk: 5.6.0 + chalk: 5.6.2 cli-boxes: 3.0.0 string-width: 7.2.0 type-fest: 4.41.0 widest-line: 5.0.0 - wrap-ansi: 9.0.0 + wrap-ansi: 9.0.2 camelcase@8.0.0: {} ccount@2.0.1: {} - chalk@5.6.0: {} + chalk@5.6.2: {} character-entities-html4@2.1.0: {} @@ -4070,7 +4449,7 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 - css-selector-parser@3.1.3: {} + css-selector-parser@3.3.0: {} css-tree@2.2.1: dependencies: @@ -4090,15 +4469,11 @@ snapshots: dependencies: css-tree: 2.2.1 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 - decode-named-character-reference@1.2.0: + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -4146,7 +4521,7 @@ snapshots: dset@3.1.4: {} - emoji-regex@10.4.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -4170,34 +4545,63 @@ snapshots: esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 - esbuild@0.25.9: + esbuild@0.25.12: optionalDependencies: - "@esbuild/aix-ppc64": 0.25.9 - "@esbuild/android-arm": 0.25.9 - "@esbuild/android-arm64": 0.25.9 - "@esbuild/android-x64": 0.25.9 - "@esbuild/darwin-arm64": 0.25.9 - "@esbuild/darwin-x64": 0.25.9 - "@esbuild/freebsd-arm64": 0.25.9 - "@esbuild/freebsd-x64": 0.25.9 - "@esbuild/linux-arm": 0.25.9 - "@esbuild/linux-arm64": 0.25.9 - "@esbuild/linux-ia32": 0.25.9 - "@esbuild/linux-loong64": 0.25.9 - "@esbuild/linux-mips64el": 0.25.9 - "@esbuild/linux-ppc64": 0.25.9 - "@esbuild/linux-riscv64": 0.25.9 - "@esbuild/linux-s390x": 0.25.9 - "@esbuild/linux-x64": 0.25.9 - "@esbuild/netbsd-arm64": 0.25.9 - "@esbuild/netbsd-x64": 0.25.9 - "@esbuild/openbsd-arm64": 0.25.9 - "@esbuild/openbsd-x64": 0.25.9 - "@esbuild/openharmony-arm64": 0.25.9 - "@esbuild/sunos-x64": 0.25.9 - "@esbuild/win32-arm64": 0.25.9 - "@esbuild/win32-ia32": 0.25.9 - "@esbuild/win32-x64": 0.25.9 + "@esbuild/aix-ppc64": 0.25.12 + "@esbuild/android-arm": 0.25.12 + "@esbuild/android-arm64": 0.25.12 + "@esbuild/android-x64": 0.25.12 + "@esbuild/darwin-arm64": 0.25.12 + "@esbuild/darwin-x64": 0.25.12 + "@esbuild/freebsd-arm64": 0.25.12 + "@esbuild/freebsd-x64": 0.25.12 + "@esbuild/linux-arm": 0.25.12 + "@esbuild/linux-arm64": 0.25.12 + "@esbuild/linux-ia32": 0.25.12 + "@esbuild/linux-loong64": 0.25.12 + "@esbuild/linux-mips64el": 0.25.12 + "@esbuild/linux-ppc64": 0.25.12 + "@esbuild/linux-riscv64": 0.25.12 + "@esbuild/linux-s390x": 0.25.12 + "@esbuild/linux-x64": 0.25.12 + "@esbuild/netbsd-arm64": 0.25.12 + "@esbuild/netbsd-x64": 0.25.12 + "@esbuild/openbsd-arm64": 0.25.12 + "@esbuild/openbsd-x64": 0.25.12 + "@esbuild/openharmony-arm64": 0.25.12 + "@esbuild/sunos-x64": 0.25.12 + "@esbuild/win32-arm64": 0.25.12 + "@esbuild/win32-ia32": 0.25.12 + "@esbuild/win32-x64": 0.25.12 + + esbuild@0.27.3: + optionalDependencies: + "@esbuild/aix-ppc64": 0.27.3 + "@esbuild/android-arm": 0.27.3 + "@esbuild/android-arm64": 0.27.3 + "@esbuild/android-x64": 0.27.3 + "@esbuild/darwin-arm64": 0.27.3 + "@esbuild/darwin-x64": 0.27.3 + "@esbuild/freebsd-arm64": 0.27.3 + "@esbuild/freebsd-x64": 0.27.3 + "@esbuild/linux-arm": 0.27.3 + "@esbuild/linux-arm64": 0.27.3 + "@esbuild/linux-ia32": 0.27.3 + "@esbuild/linux-loong64": 0.27.3 + "@esbuild/linux-mips64el": 0.27.3 + "@esbuild/linux-ppc64": 0.27.3 + "@esbuild/linux-riscv64": 0.27.3 + "@esbuild/linux-s390x": 0.27.3 + "@esbuild/linux-x64": 0.27.3 + "@esbuild/netbsd-arm64": 0.27.3 + "@esbuild/netbsd-x64": 0.27.3 + "@esbuild/openbsd-arm64": 0.27.3 + "@esbuild/openbsd-x64": 0.27.3 + "@esbuild/openharmony-arm64": 0.27.3 + "@esbuild/sunos-x64": 0.27.3 + "@esbuild/win32-arm64": 0.27.3 + "@esbuild/win32-ia32": 0.27.3 + "@esbuild/win32-x64": 0.27.3 escape-string-regexp@5.0.0: {} @@ -4236,17 +4640,21 @@ snapshots: dependencies: "@types/estree": 1.0.8 - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} - expressive-code@0.41.3: + expressive-code@0.41.6: dependencies: - "@expressive-code/core": 0.41.3 - "@expressive-code/plugin-frames": 0.41.3 - "@expressive-code/plugin-shiki": 0.41.3 - "@expressive-code/plugin-text-markers": 0.41.3 + "@expressive-code/core": 0.41.6 + "@expressive-code/plugin-frames": 0.41.6 + "@expressive-code/plugin-shiki": 0.41.6 + "@expressive-code/plugin-text-markers": 0.41.6 extend@3.0.2: {} + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -4264,7 +4672,7 @@ snapshots: fsevents@2.3.3: optional: true - get-east-asian-width@1.3.0: {} + get-east-asian-width@1.4.0: {} github-slugger@2.0.0: {} @@ -4293,7 +4701,7 @@ snapshots: hast-util-phrasing: 3.0.1 hast-util-whitespace: 3.0.0 html-whitespace-sensitive-tag-names: 3.0.1 - unist-util-visit-parents: 6.0.1 + unist-util-visit-parents: 6.0.2 hast-util-from-html@2.0.3: dependencies: @@ -4333,7 +4741,7 @@ snapshots: hast-util-embedded: 3.0.0 hast-util-is-element: 3.0.0 hast-util-whitespace: 3.0.0 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 hast-util-parse-selector@4.0.0: dependencies: @@ -4353,12 +4761,12 @@ snapshots: "@types/unist": 3.0.3 "@ungap/structured-clone": 1.3.0 hast-util-from-parse5: 8.0.3 - hast-util-to-parse5: 8.0.0 + hast-util-to-parse5: 8.0.1 html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.0 + mdast-util-to-hast: 13.2.1 parse5: 7.3.0 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -4369,7 +4777,7 @@ snapshots: "@types/unist": 3.0.3 bcp-47-match: 2.0.3 comma-separated-tokens: 2.0.3 - css-selector-parser: 3.1.3 + css-selector-parser: 3.3.0 devlop: 1.1.0 direction: 2.0.1 hast-util-has-property: 3.0.0 @@ -4378,7 +4786,7 @@ snapshots: nth-check: 2.1.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 zwitch: 2.0.4 hast-util-to-estree@3.1.3: @@ -4396,7 +4804,7 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.17 + style-to-js: 1.1.21 unist-util-position: 5.0.0 zwitch: 2.0.4 transitivePeerDependencies: @@ -4410,7 +4818,7 @@ snapshots: comma-separated-tokens: 2.0.3 hast-util-whitespace: 3.0.0 html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.0 + mdast-util-to-hast: 13.2.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 stringify-entities: 4.0.4 @@ -4430,18 +4838,18 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.17 + style-to-js: 1.1.21 unist-util-position: 5.0.0 vfile-message: 4.0.3 transitivePeerDependencies: - supports-color - hast-util-to-parse5@8.0.0: + hast-util-to-parse5@8.0.1: dependencies: "@types/hast": 3.0.4 comma-separated-tokens: 2.0.3 devlop: 1.1.0 - property-information: 6.5.0 + property-information: 7.1.0 space-separated-tokens: 2.0.2 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -4479,13 +4887,11 @@ snapshots: i18next@23.16.8: dependencies: - "@babel/runtime": 7.28.3 - - import-meta-resolve@4.1.0: {} + "@babel/runtime": 7.28.6 import-meta-resolve@4.2.0: {} - inline-style-parser@0.2.4: {} + inline-style-parser@0.2.7: {} iron-webcrypto@1.2.1: {} @@ -4510,31 +4916,29 @@ snapshots: is-plain-obj@4.1.0: {} - is-wsl@3.1.0: + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 + js-tokens@4.0.0: {} js-yaml@4.1.1: dependencies: argparse: 2.0.1 - kleur@3.0.3: {} + json-schema-traverse@1.0.0: {} - kleur@4.1.5: {} + jsonpointer@5.0.1: {} + + kleur@3.0.3: {} klona@2.0.6: {} + leven@3.1.0: {} + longest-streak@3.1.0: {} - lru-cache@11.2.5: {} - - magic-string@0.30.18: - dependencies: - "@jridgewell/sourcemap-codec": 1.5.5 + lru-cache@11.2.6: {} magic-string@0.30.21: dependencies: @@ -4554,7 +4958,7 @@ snapshots: dependencies: "@types/mdast": 4.0.4 "@types/unist": 3.0.3 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 mdast-util-directive@3.1.0: dependencies: @@ -4566,7 +4970,7 @@ snapshots: mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 - unist-util-visit-parents: 6.0.1 + unist-util-visit-parents: 6.0.2 transitivePeerDependencies: - supports-color @@ -4574,14 +4978,14 @@ snapshots: dependencies: "@types/mdast": 4.0.4 escape-string-regexp: 5.0.0 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 mdast-util-from-markdown@2.0.2: dependencies: "@types/mdast": 4.0.4 "@types/unist": 3.0.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 micromark: 4.0.2 @@ -4703,9 +5107,9 @@ snapshots: mdast-util-phrasing@4.1.0: dependencies: "@types/mdast": 4.0.4 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 - mdast-util-to-hast@13.2.0: + mdast-util-to-hast@13.2.1: dependencies: "@types/hast": 3.0.4 "@types/mdast": 4.0.4 @@ -4714,7 +5118,7 @@ snapshots: micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 mdast-util-to-markdown@2.1.2: @@ -4726,7 +5130,7 @@ snapshots: mdast-util-to-string: 4.0.0 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 zwitch: 2.0.4 mdast-util-to-string@4.0.0: @@ -4739,7 +5143,7 @@ snapshots: micromark-core-commonmark@2.0.3: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 @@ -4945,7 +5349,7 @@ snapshots: micromark-util-decode-string@2.0.1: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 micromark-util-character: 2.1.1 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 @@ -4992,8 +5396,8 @@ snapshots: micromark@4.0.2: dependencies: "@types/debug": 4.1.12 - debug: 4.4.1 - decode-named-character-reference: 1.2.0 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-factory-space: 2.0.1 @@ -5037,51 +5441,48 @@ snapshots: dependencies: destr: 2.0.5 node-fetch-native: 1.6.7 - ufo: 1.6.1 + ufo: 1.6.3 ohash@2.0.11: {} oniguruma-parser@0.12.1: {} - oniguruma-to-es@4.3.3: - dependencies: - oniguruma-parser: 0.12.1 - regex: 6.0.1 - regex-recursion: 6.0.2 - oniguruma-to-es@4.3.4: dependencies: oniguruma-parser: 0.12.1 - regex: 6.0.1 + regex: 6.1.0 regex-recursion: 6.0.2 + openapi-types@12.1.3: {} + p-limit@6.2.0: dependencies: - yocto-queue: 1.2.1 + yocto-queue: 1.2.2 p-queue@8.1.1: dependencies: - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 p-timeout: 6.1.4 p-timeout@6.1.4: {} package-manager-detector@1.6.0: {} - pagefind@1.3.0: + pagefind@1.4.0: optionalDependencies: - "@pagefind/darwin-arm64": 1.3.0 - "@pagefind/darwin-x64": 1.3.0 - "@pagefind/linux-arm64": 1.3.0 - "@pagefind/linux-x64": 1.3.0 - "@pagefind/windows-x64": 1.3.0 + "@pagefind/darwin-arm64": 1.4.0 + "@pagefind/darwin-x64": 1.4.0 + "@pagefind/freebsd-x64": 1.4.0 + "@pagefind/linux-arm64": 1.4.0 + "@pagefind/linux-x64": 1.4.0 + "@pagefind/windows-x64": 1.4.0 parse-entities@4.0.2: dependencies: "@types/unist": 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 @@ -5130,8 +5531,6 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - property-information@6.5.0: {} - property-information@7.1.0: {} radix3@1.1.2: {} @@ -5173,13 +5572,13 @@ snapshots: regex-utilities@2.3.0: {} - regex@6.0.1: + regex@6.1.0: dependencies: regex-utilities: 2.3.0 - rehype-expressive-code@0.41.3: + rehype-expressive-code@0.41.6: dependencies: - expressive-code: 0.41.3 + expressive-code: 0.41.6 rehype-format@5.0.1: dependencies: @@ -5239,7 +5638,7 @@ snapshots: transitivePeerDependencies: - supports-color - remark-mdx@3.1.0: + remark-mdx@3.1.1: dependencies: mdast-util-mdx: 3.0.0 micromark-extension-mdxjs: 3.0.0 @@ -5259,7 +5658,7 @@ snapshots: dependencies: "@types/hast": 3.0.4 "@types/mdast": 4.0.4 - mdast-util-to-hast: 13.2.0 + mdast-util-to-hast: 13.2.1 unified: 11.0.5 vfile: 6.0.3 @@ -5268,7 +5667,7 @@ snapshots: retext: 9.0.0 retext-smartypants: 6.2.0 unified: 11.0.5 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 remark-stringify@11.0.0: dependencies: @@ -5276,6 +5675,8 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + require-from-string@2.0.2: {} + retext-latin@4.0.0: dependencies: "@types/nlcst": 2.0.3 @@ -5286,7 +5687,7 @@ snapshots: dependencies: "@types/nlcst": 2.0.3 nlcst-to-string: 4.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 retext-stringify@4.0.0: dependencies: @@ -5301,33 +5702,38 @@ snapshots: retext-stringify: 4.0.0 unified: 11.0.5 - rollup@4.48.1: + rollup@4.57.1: dependencies: "@types/estree": 1.0.8 optionalDependencies: - "@rollup/rollup-android-arm-eabi": 4.48.1 - "@rollup/rollup-android-arm64": 4.48.1 - "@rollup/rollup-darwin-arm64": 4.48.1 - "@rollup/rollup-darwin-x64": 4.48.1 - "@rollup/rollup-freebsd-arm64": 4.48.1 - "@rollup/rollup-freebsd-x64": 4.48.1 - "@rollup/rollup-linux-arm-gnueabihf": 4.48.1 - "@rollup/rollup-linux-arm-musleabihf": 4.48.1 - "@rollup/rollup-linux-arm64-gnu": 4.48.1 - "@rollup/rollup-linux-arm64-musl": 4.48.1 - "@rollup/rollup-linux-loongarch64-gnu": 4.48.1 - "@rollup/rollup-linux-ppc64-gnu": 4.48.1 - "@rollup/rollup-linux-riscv64-gnu": 4.48.1 - "@rollup/rollup-linux-riscv64-musl": 4.48.1 - "@rollup/rollup-linux-s390x-gnu": 4.48.1 - "@rollup/rollup-linux-x64-gnu": 4.48.1 - "@rollup/rollup-linux-x64-musl": 4.48.1 - "@rollup/rollup-win32-arm64-msvc": 4.48.1 - "@rollup/rollup-win32-ia32-msvc": 4.48.1 - "@rollup/rollup-win32-x64-msvc": 4.48.1 + "@rollup/rollup-android-arm-eabi": 4.57.1 + "@rollup/rollup-android-arm64": 4.57.1 + "@rollup/rollup-darwin-arm64": 4.57.1 + "@rollup/rollup-darwin-x64": 4.57.1 + "@rollup/rollup-freebsd-arm64": 4.57.1 + "@rollup/rollup-freebsd-x64": 4.57.1 + "@rollup/rollup-linux-arm-gnueabihf": 4.57.1 + "@rollup/rollup-linux-arm-musleabihf": 4.57.1 + "@rollup/rollup-linux-arm64-gnu": 4.57.1 + "@rollup/rollup-linux-arm64-musl": 4.57.1 + "@rollup/rollup-linux-loong64-gnu": 4.57.1 + "@rollup/rollup-linux-loong64-musl": 4.57.1 + "@rollup/rollup-linux-ppc64-gnu": 4.57.1 + "@rollup/rollup-linux-ppc64-musl": 4.57.1 + "@rollup/rollup-linux-riscv64-gnu": 4.57.1 + "@rollup/rollup-linux-riscv64-musl": 4.57.1 + "@rollup/rollup-linux-s390x-gnu": 4.57.1 + "@rollup/rollup-linux-x64-gnu": 4.57.1 + "@rollup/rollup-linux-x64-musl": 4.57.1 + "@rollup/rollup-openbsd-x64": 4.57.1 + "@rollup/rollup-openharmony-arm64": 4.57.1 + "@rollup/rollup-win32-arm64-msvc": 4.57.1 + "@rollup/rollup-win32-ia32-msvc": 4.57.1 + "@rollup/rollup-win32-x64-gnu": 4.57.1 + "@rollup/rollup-win32-x64-msvc": 4.57.1 fsevents: 2.3.3 - sax@1.4.1: {} + sax@1.4.4: {} semver@7.7.4: {} @@ -5362,17 +5768,6 @@ snapshots: "@img/sharp-win32-ia32": 0.34.5 "@img/sharp-win32-x64": 0.34.5 - shiki@3.11.0: - dependencies: - "@shikijs/core": 3.11.0 - "@shikijs/engine-javascript": 3.11.0 - "@shikijs/engine-oniguruma": 3.11.0 - "@shikijs/langs": 3.11.0 - "@shikijs/themes": 3.11.0 - "@shikijs/types": 3.11.0 - "@shikijs/vscode-textmate": 10.0.2 - "@types/hast": 3.0.4 - shiki@3.22.0: dependencies: "@shikijs/core": 3.22.0 @@ -5386,14 +5781,12 @@ snapshots: sisteransi@1.0.5: {} - sitemap@8.0.0: + sitemap@8.0.2: dependencies: "@types/node": 17.0.45 "@types/sax": 1.2.7 arg: 5.0.2 - sax: 1.4.1 - - smol-toml@1.4.2: {} + sax: 1.4.4 smol-toml@1.6.0: {} @@ -5403,6 +5796,17 @@ snapshots: space-separated-tokens@2.0.2: {} + starlight-openapi@0.22.0(@astrojs/markdown-remark@6.3.10)(@astrojs/starlight@0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)))(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3))(openapi-types@12.1.3): + dependencies: + "@astrojs/markdown-remark": 6.3.10 + "@astrojs/starlight": 0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)) + "@readme/openapi-parser": 4.1.2(openapi-types@12.1.3) + astro: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + github-slugger: 2.0.0 + url-template: 3.1.1 + transitivePeerDependencies: + - openapi-types + stream-replace-string@2.0.0: {} string-width@4.2.3: @@ -5413,9 +5817,9 @@ snapshots: string-width@7.2.0: dependencies: - emoji-regex: 10.4.0 - get-east-asian-width: 1.3.0 - strip-ansi: 7.1.0 + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 stringify-entities@4.0.4: dependencies: @@ -5426,17 +5830,17 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.1.2: dependencies: - ansi-regex: 6.2.0 + ansi-regex: 6.2.2 - style-to-js@1.1.17: + style-to-js@1.1.21: dependencies: - style-to-object: 1.0.9 + style-to-object: 1.0.14 - style-to-object@1.0.9: + style-to-object@1.0.14: dependencies: - inline-style-parser: 0.2.4 + inline-style-parser: 0.2.7 svgo@4.0.0: dependencies: @@ -5446,7 +5850,7 @@ snapshots: css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 - sax: 1.4.1 + sax: 1.4.4 tiny-inflate@1.0.3: {} @@ -5461,18 +5865,16 @@ snapshots: trough@2.2.0: {} - tsconfck@3.1.6(typescript@5.9.2): + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 tslib@2.8.1: optional: true type-fest@4.41.0: {} - typescript@5.9.2: {} - - ufo@1.6.1: {} + typescript@5.9.3: {} ufo@1.6.3: {} @@ -5480,8 +5882,6 @@ snapshots: uncrypto@0.1.3: {} - undici-types@7.10.0: {} - unified@11.0.5: dependencies: "@types/unist": 3.0.3 @@ -5492,7 +5892,7 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - unifont@0.7.3: + unifont@0.7.4: dependencies: css-tree: 3.1.0 ofetch: 1.5.1 @@ -5501,9 +5901,9 @@ snapshots: unist-util-find-after@5.0.0: dependencies: "@types/unist": 3.0.3 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 - unist-util-is@6.0.0: + unist-util-is@6.0.1: dependencies: "@types/unist": 3.0.3 @@ -5523,7 +5923,7 @@ snapshots: unist-util-remove-position@5.0.0: dependencies: "@types/unist": 3.0.3 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 unist-util-stringify-position@4.0.0: dependencies: @@ -5533,21 +5933,16 @@ snapshots: dependencies: "@types/unist": 3.0.3 - unist-util-visit-parents@6.0.1: - dependencies: - "@types/unist": 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents@6.0.2: dependencies: "@types/unist": 3.0.3 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 - unist-util-visit@5.0.0: + unist-util-visit@5.1.0: dependencies: "@types/unist": 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 unstorage@1.17.4: dependencies: @@ -5555,11 +5950,13 @@ snapshots: chokidar: 5.0.0 destr: 2.0.5 h3: 1.15.5 - lru-cache: 11.2.5 + lru-cache: 11.2.6 node-fetch-native: 1.6.7 ofetch: 1.5.1 ufo: 1.6.3 + url-template@3.1.1: {} + util-deprecate@1.0.2: {} vfile-location@5.0.3: @@ -5577,21 +5974,20 @@ snapshots: "@types/unist": 3.0.3 vfile-message: 4.0.3 - vite@6.4.1(@types/node@24.3.0): + vite@6.4.1: dependencies: - esbuild: 0.25.9 + esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.48.1 + rollup: 4.57.1 tinyglobby: 0.2.15 optionalDependencies: - "@types/node": 24.3.0 fsevents: 2.3.3 - vitefu@1.1.1(vite@6.4.1(@types/node@24.3.0)): + vitefu@1.1.1(vite@6.4.1): optionalDependencies: - vite: 6.4.1(@types/node@24.3.0) + vite: 6.4.1 web-namespaces@2.0.1: {} @@ -5601,17 +5997,17 @@ snapshots: dependencies: string-width: 7.2.0 - wrap-ansi@9.0.0: + wrap-ansi@9.0.2: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 xxhash-wasm@1.1.0: {} yargs-parser@21.1.1: {} - yocto-queue@1.2.1: {} + yocto-queue@1.2.2: {} yocto-spinner@0.2.3: dependencies: @@ -5623,9 +6019,9 @@ snapshots: dependencies: zod: 3.25.76 - zod-to-ts@1.2.0(typescript@5.9.2)(zod@3.25.76): + zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): dependencies: - typescript: 5.9.2 + typescript: 5.9.3 zod: 3.25.76 zod@3.25.76: {} diff --git a/docs/src/.i18n-filter b/docs/src/.i18n-filter index f113dae9..9871cee8 100644 --- a/docs/src/.i18n-filter +++ b/docs/src/.i18n-filter @@ -1,6 +1,6 @@ +en ca de -en es fr nn-no diff --git a/docs/src/content/docs/ar/getting-started/auth.mdx b/docs/src/content/docs/ar/getting-started/auth.mdx index c5f2040e..3c8978ab 100644 --- a/docs/src/content/docs/ar/getting-started/auth.mdx +++ b/docs/src/content/docs/ar/getting-started/auth.mdx @@ -13,24 +13,25 @@ níveis: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -39,13 +40,13 @@ níveis: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Por cargos de podcast -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -54,11 +55,11 @@ níveis: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Por permissões de podcast -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -82,4 +83,4 @@ níveis: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/ar/getting-started/docker.mdx b/docs/src/content/docs/ar/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/ar/getting-started/docker.mdx +++ b/docs/src/content/docs/ar/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/ar/getting-started/install.mdx b/docs/src/content/docs/ar/getting-started/install.mdx index 393ead8d..170fef0f 100644 --- a/docs/src/content/docs/ar/getting-started/install.mdx +++ b/docs/src/content/docs/ar/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/ar/index.mdx b/docs/src/content/docs/ar/index.mdx index 4d6bced7..7cf1a592 100644 --- a/docs/src/content/docs/ar/index.mdx +++ b/docs/src/content/docs/ar/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/br/getting-started/auth.mdx b/docs/src/content/docs/br/getting-started/auth.mdx index aea953e5..4ec2be83 100644 --- a/docs/src/content/docs/br/getting-started/auth.mdx +++ b/docs/src/content/docs/br/getting-started/auth.mdx @@ -12,73 +12,74 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podkaster | General users of Castopod. | admin.access | +| role | description | permissions | +| --------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Dreistmerour·ez | Ur c'hontroll klok en deus war Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Merour·ez | Merañ a ra endalc'had Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podkaster | Implijerien·ezed kustum Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} -| permission | description | -| ----------------------- | ------------------------------------------------------------------ | -| admin.access | Can access the Castopod admin area. | -| admin.settings | Can access the Castopod settings. | -| users.manage | Can manage Castopod users. | -| persons.manage | Can manage persons. | -| pages.manage | Can manage pages. | -| podcasts.view | Can view all podcasts. | -| podcasts.create | Can create new podcasts. | -| podcasts.import | Can import podcasts. | -| fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | +| permission | description | +| ----------------------- | ------------------------------------------------------------------------------------------------- | +| admin.access | Gallout a ra gwelet taolenn-stur Castopod. | +| admin.settings | Gallout a ra gwelet arventennoù Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | +| users.manage | Gallout a ra ober war-dro implijerien·ezed Castopod. | +| persons.manage | Gallout a ra merañ an emellerien·ezed. | +| pages.manage | Gallout a ra merañ ar pajennoù. | +| podcasts.view | Gallout a ra gwelet an holl bodkastoù. | +| podcasts.create | Gallout a ra krouiñ podkastoù nevez. | +| podcasts.import | Gallout a ra enporzhiañ podkastoù. | +| fediverse.manage-blocks | Gallout a ra mirout aktourien·ezed pe domanioù ar Fediverse ouzh kaout darempredoù gant Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Admin | Has complete control of podcast #\{id\}. | \* | -| Editor | Manages content and publications of podcast #\{id\}. | view, edit, manage-import, manage-persons, manage-platforms, manage-publications, manage-notifications, interact-as, episodes.view, episodes.create, episodes.edit, episodes.delete, episodes.manage-persons, episodes.manage-clips, episodes.manage-publications, episodes.manage-comments | -| Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | -| Guest | General contributor of the podcast #\{id\}. | view, episodes.view | +| role | description | permissions | +| ---------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Merour·ez | Ur c'hontroll klok en deus war ar podkast #\{id\}. | \* | +| Embanner | Merañ a ra endalc'had hag embannadurioù ar podkast #\{id\}. | view, edit, manage-import, manage-persons, manage-platforms, manage-publications, manage-notifications, interact-as, episodes.view, episodes.create, episodes.edit, episodes.delete, episodes.manage-persons, episodes.manage-clips, episodes.manage-publications, episodes.manage-comments | +| Aozer·ez | Merañ a ra endalc'had ar podkast #\{id\} met ne c'hall ket embann anezho. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | +| Kouviad·ez | Perzhiad·ez eus ar podkast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} -| permission | description | -| ---------------------------- | -------------------------------------------------------------------------- | -| view | Can view dashboard and analytics of podcast #\{id\}. | -| edit | Can edit podcast #\{id\}. | -| delete | Can delete podcast #\{id\}. | -| manage-import | Can synchronize imported podcast #\{id\}. | -| manage-persons | Can manage subscriptions of podcast #\{id\}. | -| manage-subscriptions | Can manage subscriptions of podcast #\{id\}. | -| manage-contributors | Can manage contributors of podcast #\{id\}. | -| manage-platforms | Can set/remove platform links of podcast #\{id\}. | -| manage-publications | Can publish podcast #\{id\}. | -| manage-notifications | Can view and mark notifications as read for podcast #\{id\}. | -| interact-as | Can interact as the podcast #\{id\} to favourite, share or reply to posts. | -| episodes.view | Can view dashboards and analytics of podcast #\{id\}'s episodes. | -| episodes.create | Can create episodes for podcast #\{id\}. | -| episodes.edit | Can edit episodes of podcast #\{id\}. | -| episodes.delete | Can delete episodes of podcast #\{id\}. | -| episodes.manage-persons | Can manage episode persons of podcast #\{id\}. | -| episodes.manage-clips | Can manage video clips or soundbites of podcast #\{id\}. | -| episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | -| episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | +| permission | description | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| view | Gallout a ra gwelet taolenn-stur ha muzulioù heklev ar podkast #\{id\}. | +| edit | Gallout a ra kemmañ ar podkast #\{id\}. | +| delete | Gallout a ra lemel ar podkast #\{id\}. | +| manage-import | Gallout a ra sinkronekaat ar podkast enporzhiet #\{id\}. | +| manage-persons | Gallout a ra merañ koumanantoù ar podkast #\{id\}. | +| manage-subscriptions | Gallout a ra merañ koumanantoù ar podkast #\{id\}. | +| manage-contributors | Gallout a ra merañ perzhidi ha perzhiadezed ar podkast #\{id\}. | +| manage-platforms | Gallout a ra ouzhpennañ pe lemel liammoù etrezek savennoù diavaez evit ar podkast #\{id\}. | +| manage-publications | Gallout a ra embann ar podkast #\{id\}. | +| manage-notifications | Gallout a ra gwelet kemennoù ar podkast #\{id\} ha lakaat anezho evel lennet. | +| interact-as | Gallout a ra ober traoù gant identelezh ar podkast #\{id\}: ouzhpennañ ur gemennadenn d'ar re garetañ, rannañ anezhi pe respont dezhi. | +| episodes.view | Gallout a ra gwelet taolennoù-stur ha muzulioù heklev rannoù ar podkast #\{id\}. | +| episodes.create | Gallout a ra krouiñ rannoù evit podkast #\{id\}. | +| episodes.edit | Gallout a ra kemmañ rannoù ar podkast #\{id\}. | +| episodes.delete | Gallout a ra lemel rannoù ar podkast #\{id\}. | +| episodes.manage-persons | Gallout a ra merañ emellerien·ezed ar podkast #\{id\}. | +| episodes.manage-clips | Gallout a ra merañ klipoù video pe tennadoù son ar podkast #\{id\}. | +| episodes.manage-publications | Gallout a ra embann pe diembann rannoù ha kemennadennoù ar podkast #\{id\}. | +| episodes.manage-comments | Gallout a ra krouiñ/lemel evezhiadennoù evit rannoù ar podkast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/br/getting-started/docker.mdx b/docs/src/content/docs/br/getting-started/docker.mdx index 6304854e..a171367c 100644 --- a/docs/src/content/docs/br/getting-started/docker.mdx +++ b/docs/src/content/docs/br/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/br/getting-started/install.mdx b/docs/src/content/docs/br/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/br/getting-started/install.mdx +++ b/docs/src/content/docs/br/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/br/index.mdx b/docs/src/content/docs/br/index.mdx index d0c45c04..1b0f1806 100644 --- a/docs/src/content/docs/br/index.mdx +++ b/docs/src/content/docs/br/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivácia diff --git a/docs/src/content/docs/ca/getting-started/auth.mdx b/docs/src/content/docs/ca/getting-started/auth.mdx index a1ef11fd..017c9ddf 100644 --- a/docs/src/content/docs/ca/getting-started/auth.mdx +++ b/docs/src/content/docs/ca/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------ | -| Super administrador | Té control complet sobre Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Administrador | Administra el contingut de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Usos generals de Castopod. | admin.access | +| role | description | permissions | +| ------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------ | +| Super administrador | Té control complet sobre Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Administrador | Administra el contingut de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Usos generals de Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | -------------------------------------------------------------------- | | admin.access | Pot accedir a l'àrea d'administració de Castopod. | | admin.settings | Pot accedir a la configuració de Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Pot administrar els usuaris de Castopod. | | persons.manage | Pot administrar persones. | | pages.manage | Pot administrar pàgines. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Pot importar pòdcasts. | | fediverse.manage-blocks | Pot evitar que actors/dominis del fedivers interactuen amb Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Autor | Administra el contingut del podcast #\{id\} però no el pot publicar. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Convidat | Col·laborador general del podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Pot publicar/anul·lar la publicació d'episodis i publicacions del pòdcast #\{id\}. | | episodes.manage-comments | Pot crear/eliminar comentaris d'episodi del pòdcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/ca/getting-started/docker.mdx b/docs/src/content/docs/ca/getting-started/docker.mdx index fbab23ad..6880d206 100644 --- a/docs/src/content/docs/ca/getting-started/docker.mdx +++ b/docs/src/content/docs/ca/getting-started/docker.mdx @@ -7,8 +7,8 @@ process: - [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod): an all in one castopod image using nginx unit -- [**`castopod/app`**](https://hub.docker.com/r/castopod/app): el - paquet incloent Castopod i totes les dependències +- [**`castopod/app`**](https://hub.docker.com/r/castopod/app): el paquet + incloent Castopod i totes les dependències - [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server): una configuració de Nginx per a Castopod @@ -88,13 +88,12 @@ can be added as a cache handler. ``` Heu d'adaptar algunes variables a les vostres necessitats (per exemple, - `CP_BASEURL`, `MYSQL_ROOT_PASSWORD`, `MYSQL_PASSWORD` i - `CP_ANALYTICS_SALT`). + `CP_BASEURL`, `MYSQL_ROOT_PASSWORD`, `MYSQL_PASSWORD` i `CP_ANALYTICS_SALT`). 3. Configureu un `reverse proxy` per a TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/ca/getting-started/install.mdx b/docs/src/content/docs/ca/getting-started/install.mdx index c62ea41d..3788694b 100644 --- a/docs/src/content/docs/ca/getting-started/install.mdx +++ b/docs/src/content/docs/ca/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/ca/index.mdx b/docs/src/content/docs/ca/index.mdx index 0a485990..73265ee0 100644 --- a/docs/src/content/docs/ca/index.mdx +++ b/docs/src/content/docs/ca/index.mdx @@ -16,14 +16,14 @@ molt petita. ## Característiques - 🌱  Gratis i de codi obert (llicència AGPL v3) -- 🔐  Centrat en la sobirania de les dades: el vostre contingut, audiència - i estadístiques us pertanyen, i només a vosaltres -- 🪄  Funcions de podcasting 2.0: GUID, bloqueigos, transcripcions, - finançament, capítols, geo-localització, persones, fragments d'àudio, … +- 🔐  Centrat en la sobirania de les dades: el vostre contingut, audiència i + estadístiques us pertanyen, i només a vosaltres +- 🪄  Funcions de podcasting 2.0: GUID, bloqueigos, transcripcions, finançament, + capítols, geo-localització, persones, fragments d'àudio, … - 💬  Xarxa social integrada: - 🚀  Castopod forma part de Fediverse, una xarxa social descentralitzada - - ❤️  Creeu publicacions, compartiu-les, afegiu-hi com a preferits i - comenteu episodis + - ❤️  Creeu publicacions, compartiu-les, afegiu-hi com a preferits i comenteu + episodis - 📈  Estadístiques integrades: - ⚖️  Complint amb GDPR / CCPA / LGPD - 🪙  Mesura d'audiència segons l'estàndard IABv2 @@ -34,8 +34,7 @@ molt petita. - 🎨  Colors del tema personalitzables - 🎬  Genereu videoclips d'episodis preparats per compartir - 🔉  Genera fragments d'àudio de cada episodi - - ▶️  Reproductor incrustable, per incrustar els episodis a qualsevol - lloc web + - ▶️  Reproductor incrustable, per incrustar els episodis a qualsevol lloc web - 💸  Monetització: - 🔗  Enllaços de finançament - 📲  Anuncis per escoltar-fent-clic @@ -49,8 +48,8 @@ molt petita. - 📤  Traieu el vostre podcast fora de Castopod - 🔀  Multi-podcast: allotgeu tants programes com vulgueu en un mateix lloc - 👥  Multi-usuari: afegiu col·laboradors i definiu rols -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivació diff --git a/docs/src/content/docs/da/getting-started/auth.mdx b/docs/src/content/docs/da/getting-started/auth.mdx index 4bca1389..c4853b74 100644 --- a/docs/src/content/docs/da/getting-started/auth.mdx +++ b/docs/src/content/docs/da/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Rolloù an istañs -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Aotreoù war an istañs -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/da/getting-started/docker.mdx b/docs/src/content/docs/da/getting-started/docker.mdx index 0a9f23af..4adf4250 100644 --- a/docs/src/content/docs/da/getting-started/docker.mdx +++ b/docs/src/content/docs/da/getting-started/docker.mdx @@ -90,8 +90,8 @@ can be added as a cache handler. 3. Podesite obrnuti proksi za TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/da/getting-started/install.mdx b/docs/src/content/docs/da/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/da/getting-started/install.mdx +++ b/docs/src/content/docs/da/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/da/index.mdx b/docs/src/content/docs/da/index.mdx index 75532de1..d4e9db42 100644 --- a/docs/src/content/docs/da/index.mdx +++ b/docs/src/content/docs/da/index.mdx @@ -18,12 +18,11 @@ otiskom (footprint). - 🌱 Besplatan i otvorenog koda (AGPL v3 License) - 🔐 Fokusiran an suverenitet podataka: vaš sadržaj, publika i analitika pripada vama i samo vama -- 🪄  Podkasting 2.0 funkcionalnosti: GUID, zaključan, transkripti, - podrška, poglavlja, lokacija, posobe, zvučni isečci, … +- 🪄  Podkasting 2.0 funkcionalnosti: GUID, zaključan, transkripti, podrška, + poglavlja, lokacija, posobe, zvučni isečci, … - 💬  Ugrađena društvena mreža: - 🚀  Castopod je deo Fediversa, decentralizovane društvene mreže - - ❤️  Napravite objave, delite, dodajte u omiljene i komentarišite - epizode + - ❤️  Napravite objave, delite, dodajte u omiljene i komentarišite epizode - 📈  Ugrađena analitika: - ⚖️  U skladu sa GDPR / CCPA / LGPD - 🪙  Merenje publike putem IABv2 standarda @@ -48,8 +47,8 @@ otiskom (footprint). - 📤  Prebacite svoj podkast sa Castopod-a - 🔀  Mreža: hostujte koliko god želite podkasta - 👥  Više korisnika: dodajte saradnike i odredite njihove uloge -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivacija diff --git a/docs/src/content/docs/de/getting-started/auth.mdx b/docs/src/content/docs/de/getting-started/auth.mdx index ec8d4637..87c45ed0 100644 --- a/docs/src/content/docs/de/getting-started/auth.mdx +++ b/docs/src/content/docs/de/getting-started/auth.mdx @@ -13,24 +13,25 @@ definiert: ### Instanz Rollen -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------ | -| Super-Administrator | Hat die vollständige Kontrolle über Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Verwaltet Castopods Inhalte. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Allgemeine Benutzer von Castopod. | admin.access | +| role | description | permissions | +| ------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super-Administrator | Hat die vollständige Kontrolle über Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Verwaltet Castopods Inhalte. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Allgemeine Benutzer von Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instanz Berechtigungen -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ---------------------------------------------------------------------------- | | admin.access | Kann auf den Admin-Bereich von Castopod zugreifen. | | admin.settings | Kann auf die Einstellungen von Castopod zugreifen. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Kann Castopod-Benutzer verwalten. | | persons.manage | Kann Mitwirkende verwalten. | | pages.manage | Kann Seiten verwalten. | @@ -39,13 +40,13 @@ definiert: | podcasts.import | Kann Podcasts importieren. | | fediverse.manage-blocks | Kann föderierte Nutzer/Domains davon abhalten, mit Castopod zu interagieren. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Pro Podcast Rollen und Berechtigungen ### Pro Podcast Rollen -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -54,11 +55,11 @@ definiert: | Autor | Verwaltet Inhalte von Podcast #\{id\}, kann diese aber nicht veröffentlichen. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Gast | Allgemeiner Mitwirkender des Podcasts #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Pro Podcast Berechtigung -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------------------------------------- | @@ -82,4 +83,4 @@ definiert: | episodes.manage-publications | Kann Episoden und Posts von Podcast #\{id\} veröffentlichen/zurückziehen. | | episodes.manage-comments | Kann Kommentare von Folgen des Podcasts #\{id\} erstellen und löschen. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/de/getting-started/docker.mdx b/docs/src/content/docs/de/getting-started/docker.mdx index 68db48b6..e56d430b 100644 --- a/docs/src/content/docs/de/getting-started/docker.mdx +++ b/docs/src/content/docs/de/getting-started/docker.mdx @@ -87,7 +87,8 @@ kann als Cache-Handler hinzugefügt werden. castopod-db: ``` - Es müssen einige Variablen an deine Bedürfnisse angepasst werden (z.B. `CP_BASEURL`, `MYSQL_ROOT_PASSWORD`, `MYSQL_PASSWORD` und + Es müssen einige Variablen an deine Bedürfnisse angepasst werden (z.B. + `CP_BASEURL`, `MYSQL_ROOT_PASSWORD`, `MYSQL_PASSWORD` und `CP_ANALYTICS_SALT`). 3. Einen Reverse-Proxy für TLS (SSL/HTTPS) einrichten @@ -104,8 +105,8 @@ kann als Cache-Handler hinzugefügt werden. ``` 4. Führe `docker-compose up -d` aus, warte darauf, dass es initialisiert wird - und gehe auf `https://castopod.example.com/cp-install` um die Einrichtung - von Castopod abzuschließen! + und gehe auf `https://castopod.example.com/cp-install` um die Einrichtung von + Castopod abzuschließen! 5. Ist alles da? Dann kann das Podcasten beginnen! 🎙️🚀 diff --git a/docs/src/content/docs/de/getting-started/install.mdx b/docs/src/content/docs/de/getting-started/install.mdx index 05e9d26f..997705b5 100644 --- a/docs/src/content/docs/de/getting-started/install.mdx +++ b/docs/src/content/docs/de/getting-started/install.mdx @@ -10,15 +10,15 @@ installieren. ## Voraussetzungen -- PHP v8.1 oder höher -- MySQL Version 5.7 oder höher oder MariaDB Version 10.2 oder höher +- PHP v8.5 oder höher +- MySQL Version 8.4 oder höher oder MariaDB Version 11.4 oder höher - HTTPS-Unterstützung - Eine [ntp-synchronisierte Uhr](https://wiki.debian.org/NTP) um die eingehenden Anfragen zu überprüfen -### PHP v8.1 oder höher +### PHP v8.5 oder höher -PHP Version 8.1 oder höher ist erforderlich, mit folgenden Erweiterungen +PHP version 8.5 oder höher ist erforderlich, mit folgenden Erweiterungen installiert: - [intl](https://php.net/manual/en/intl.requirements.php) @@ -161,8 +161,7 @@ email.SMTPPass="your_smtp_password" ### Media storage Standardmäßig werden Dateien im Ordner `public/media` über das Dateisystem -gespeichert. -Wenn Sie den Ordner `media` an einen anderen Ort verlegen müssen, +gespeichert. Wenn Sie den Ordner `media` an einen anderen Ort verlegen müssen, können Sie es in Ihrer `.env` Datei angeben wie unten gezeigt: ```ini @@ -172,7 +171,8 @@ media.root="media" media.storage="/mnt/storage" ``` -In diesem Beispiel werden die Dateien im Ordner /mnt/storage/media gespeichert. Stellen Sie sicher, dass Sie auch Ihre Webserver-Konfiguration aktualisieren, um +In diesem Beispiel werden die Dateien im Ordner /mnt/storage/media gespeichert. +Stellen Sie sicher, dass Sie auch Ihre Webserver-Konfiguration aktualisieren, um diese Änderung wiederzugeben. ### S3 diff --git a/docs/src/content/docs/de/getting-started/update.mdx b/docs/src/content/docs/de/getting-started/update.mdx index 2f863450..048b065b 100644 --- a/docs/src/content/docs/de/getting-started/update.mdx +++ b/docs/src/content/docs/de/getting-started/update.mdx @@ -34,8 +34,7 @@ improvements ⚡. diff --git a/docs/src/content/docs/de/index.mdx b/docs/src/content/docs/de/index.mdx index 77eb0595..5bc2e0ca 100644 --- a/docs/src/content/docs/de/index.mdx +++ b/docs/src/content/docs/de/index.mdx @@ -21,8 +21,7 @@ mit einem sehr kleinen Fußabdruck. - 🪄  Podcasting 2.0 Features: GUID, gesperrt, Transkripte, Finanzierung, Kapitel, Standort, Personen, Soundbites, … - 💬  Integriertes soziales Netzwerk: - - 🚀  Castopod ist Teil des Fediversums, einem dezentralen sozialen - Netzwerk + - 🚀  Castopod ist Teil des Fediversums, einem dezentralen sozialen Netzwerk - ❤️  Erstelle Beiträge, teile, favorisiere und kommentiere Episoden - 📈  Integrierte Analyse: - ⚖️  GDPR / CCPA / LGPD konform @@ -41,16 +40,15 @@ mit einem sehr kleinen Fußabdruck. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/el/getting-started/auth.mdx b/docs/src/content/docs/el/getting-started/auth.mdx index 704d4368..30b0ee8d 100644 --- a/docs/src/content/docs/el/getting-started/auth.mdx +++ b/docs/src/content/docs/el/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Ρόλοι εμφάνισης -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ---------------- | ------------------------------------- | ------------------------------------------------------------------------------------------ | -| Υπερδιαχειριστής | Έχει πλήρη έλεγχο του Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Διαχειριστής | Διαχείριση περιεχομένου του Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Γενικοί χρήστες του Castopod. | admin.access | +| role | description | permissions | +| ---------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Υπερδιαχειριστής | Έχει πλήρη έλεγχο του Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Διαχειριστής | Διαχείριση περιεχομένου του Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Γενικοί χρήστες του Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | -------------------------------------------------------------------------------------- | | admin.access | Μπορεί να έχει πρόσβαση στην περιοχή διαχείρισης Castopod. | | admin.settings | Μπορεί να έχει πρόσβαση στις ρυθμίσεις Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Μπορεί να διαχειριστεί τους χρήστες Castopod. | | persons.manage | Μπορεί να διαχειριστεί τα άτομα. | | pages.manage | Μπορεί να διαχειριστεί τις σελίδες. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Μπορεί να εισάγει podcasts. | | fediverse.manage-blocks | Μπορεί να εμποδίσει τους ψευτογενείς ηθοποιούς/τομείς να αλληλεπιδρούν με το Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Συντάκτης | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Επισκέπτης | Γενικός συντελεστής του podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/el/getting-started/docker.mdx b/docs/src/content/docs/el/getting-started/docker.mdx index e1755437..54176d7e 100644 --- a/docs/src/content/docs/el/getting-started/docker.mdx +++ b/docs/src/content/docs/el/getting-started/docker.mdx @@ -93,8 +93,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/el/getting-started/install.mdx b/docs/src/content/docs/el/getting-started/install.mdx index 11665b74..c9afd649 100644 --- a/docs/src/content/docs/el/getting-started/install.mdx +++ b/docs/src/content/docs/el/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/el/index.mdx b/docs/src/content/docs/el/index.mdx index 068c3c88..432b8f0d 100644 --- a/docs/src/content/docs/el/index.mdx +++ b/docs/src/content/docs/el/index.mdx @@ -17,10 +17,10 @@ import { LinkCard } from "@astrojs/starlight/components"; ## Χαρακτηριστικά - 🌱  Δωρεάν & open-source (AGPL v3 License) -- 🔐  Εστιάζει στην κυριαρχία των δεδομένων: το περιεχόμενο, το κοινό και - τα αναλυτικά στοιχεία ανήκουν σε εσάς μόνο -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Εστιάζει στην κυριαρχία των δεδομένων: το περιεχόμενο, το κοινό και τα + αναλυτικά στοιχεία ανήκουν σε εσάς μόνο +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Ενσωματωμένο κοινωνικό δίκτυο: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -41,15 +41,15 @@ import { LinkCard } from "@astrojs/starlight/components"; - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Κίνητρα diff --git a/docs/src/content/docs/en/getting-started/auth.mdx b/docs/src/content/docs/en/getting-started/auth.mdx index d4dd4dd5..8282b972 100644 --- a/docs/src/content/docs/en/getting-started/auth.mdx +++ b/docs/src/content/docs/en/getting-started/auth.mdx @@ -14,11 +14,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: {/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | {/* AUTH-INSTANCE-ROLES-LIST:END */} @@ -30,6 +30,7 @@ coupled with custom rules. Roles and permissions are defined at two levels: | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | diff --git a/docs/src/content/docs/en/getting-started/create-episode.mdx b/docs/src/content/docs/en/getting-started/create-episode.mdx index 045b660f..3fd353be 100644 --- a/docs/src/content/docs/en/getting-started/create-episode.mdx +++ b/docs/src/content/docs/en/getting-started/create-episode.mdx @@ -2,33 +2,37 @@ title: Create your first episode --- -To add an episode to your podcast, choose Podcasts -> All podcasts from your instance management left hand sidebar. -Then choose Podcast dashboard and press the Add an episode button in the upper right hand corner and fill out the +To add an episode to your podcast, choose Podcasts -> All podcasts from your +instance management left hand sidebar. Then choose Podcast dashboard and press +the Add an episode button in the upper right hand corner and fill out the following fields. ## Episode Info ### Audio file -Press the `Choose File` button to select the audio file to upload to Castopod. The audio file must be an mp3 or m4a -file and cannot be larger than 512 megabytes. +Press the `Choose File` button to select the audio file to upload to Castopod. +The audio file must be an mp3 or m4a file and cannot be larger than 512 +megabytes. ### Episode cover -You can optionally add a different podcast cover / artwork. Press `Choose File` and select an image from your computer -that must be at least 1400px wide and tall and no larger than 3000px wide and tall. If you do not choose an -episode cover, your default podcast artwork will be used. +You can optionally add a different podcast cover / artwork. Press `Choose File` +and select an image from your computer that must be at least 1400px wide and +tall and no larger than 3000px wide and tall. If you do not choose an episode +cover, your default podcast artwork will be used. ### Title -Enter the name for your new episode. Do not add the season or episode number in the **Title** field. Choose a clear -and concise episode name to help your listeners. +Enter the name for your new episode. Do not add the season or episode number in +the **Title** field. Choose a clear and concise episode name to help your +listeners. ### Permalink -The permalink is the link to the public episode page. This will be automatically filled out based on the title -you entered above. If you wish to have a different permalink, press the edit button to the right of the -displayed permalink. +The permalink is the link to the public episode page. This will be automatically +filled out based on the title you entered above. If you wish to have a different +permalink, press the edit button to the right of the displayed permalink. ### Season and Episode @@ -36,46 +40,54 @@ You can optionally add a season and / or episode number to your episode. ## Show Notes -Describe your episode in detail. You can use up to 4000 characters, and you can use Markdown to style your show -notes. You can expand the text box by pressing and holding the bottom right corner of the text box. +Describe your episode in detail. You can use up to 4000 characters, and you can +use Markdown to style your show notes. You can expand the text box by pressing +and holding the bottom right corner of the text box. -When your show notes are complete, press `Preview` to view how your show notes will be displayed. +When your show notes are complete, press `Preview` to view how your show notes +will be displayed. ## Additional Files ### Transcripts -You can add a transcript to your episode by choosing a file in SRT or VTT format to upload. Transcripts will be -shown in a tab on the episode page and some podcast apps such as Apple Podcasts can display the transcript. -Transcripts help users who may have a hearing disability and can also help with search engine optimization. +You can add a transcript to your episode by choosing a file in SRT or VTT format +to upload. Transcripts will be shown in a tab on the episode page and some +podcast apps such as Apple Podcasts can display the transcript. +Transcripts help users who may have a hearing disability and can also help with +search engine optimization. ### Chapters -You can optionally upload a chapters file in JSON format. To learn more about chapters and for an example of the -correct format, visit the [Podcast Namespace](https://github.com/Podcastindex-org/podcast-namespace/blob/main/chapters/jsonChapters.md). +You can optionally upload a chapters file in JSON format. To learn more about +chapters and for an example of the correct format, visit the +[Podcast Namespace](https://github.com/Podcastindex-org/podcast-namespace/blob/main/chapters/jsonChapters.md). -!!! note -Not all podcast players natively support chapters in JSON format. More modern players, such as Fountain and -Apple Podcasts, do support chapters in JSON format. +!!! note Not all podcast players natively support chapters in JSON format. More +modern players, such as Fountain and Apple Podcasts, do support chapters in JSON +format. ## Publish your episode -When complete, press the `Create episode` button at the bottom of the page. You will be automatically directed to -the next step to publish your episode. Your episode is in draft mode and is not yet published. You can preview -how your episode will look when published or publish your episode. To publish your episode, press the `Publish` button -in the upper right hand corner. +When complete, press the `Create episode` button at the bottom of the page. You +will be automatically directed to the next step to publish your episode. Your +episode is in draft mode and is not yet published. You can preview how your +episode will look when published or publish your episode. To publish your +episode, press the `Publish` button in the upper right hand corner. ### Create your announcement post -After pressing `Publish` you will be directed to the next page to draft your announcement post. Write your message -in the text box provided. This will be the message broadcast to the Fediverse and published on your podcast's home -page. +After pressing `Publish` you will be directed to the next page to draft your +announcement post. Write your message in the text box provided. This will be the +message broadcast to the Fediverse and published on your podcast's home page. ### Publication date -Choose `Now` or `Schedule` to publish your episode. If you choose `Now`, your episode will be live. Or you can -schedule the day and time to publish your episode by choosing the `Schedule` radio button and choosing the day and -time. Press `Publish` to finish. +Choose `Now` or `Schedule` to publish your episode. If you choose `Now`, your +episode will be live. Or you can schedule the day and time to publish your +episode by choosing the `Schedule` radio button and choosing the day and time. +Press `Publish` to finish. -Your RSS feed will be automatically updated with your episode information and listeners who subscribe will be -notified of a new episode. Congratulations on publishing your first episode! +Your RSS feed will be automatically updated with your episode information and +listeners who subscribe will be notified of a new episode. Congratulations on +publishing your first episode! diff --git a/docs/src/content/docs/en/getting-started/create-podcast.mdx b/docs/src/content/docs/en/getting-started/create-podcast.mdx index 65645f89..f36e101d 100644 --- a/docs/src/content/docs/en/getting-started/create-podcast.mdx +++ b/docs/src/content/docs/en/getting-started/create-podcast.mdx @@ -2,15 +2,16 @@ title: Create your first podcast --- -From the left hand navigation sidebar, press the `+` sign to the right of Podcasts to create your first podcast. +From the left hand navigation sidebar, press the `+` sign to the right of +Podcasts to create your first podcast. ## Podcast Identity ### Podcast Cover -To upload your podcast cover art, press the `Choose File` button and choose your cover art from your computer. The -cover art needs to be in JPG or PNG format and a minimum of 1400px wide and tall with a maximum of 3000px wide and -tall. +To upload your podcast cover art, press the `Choose File` button and choose your +cover art from your computer. The cover art needs to be in JPG or PNG format and +a minimum of 1400px wide and tall with a maximum of 3000px wide and tall. ### Title @@ -18,13 +19,15 @@ Enter the name of your podcast in the **Title** field. ### Description -Describe what your podcast is about. You can use Markdown to style the text and you can resize the text box by -pressing and holding the bottom right hand corner of the text box. +Describe what your podcast is about. You can use Markdown to style the text and +you can resize the text box by pressing and holding the bottom right hand corner +of the text box. ### Type -Choose how your listeners should listen to your podcast. **Episodic** lets listeners know they can consume your podcast -in any order, such as an interview podcast. Choose **Serial** if your episodes are meant to be listened to in +Choose how your listeners should listen to your podcast. **Episodic** lets +listeners know they can consume your podcast in any order, such as an interview +podcast. Choose **Serial** if your episodes are meant to be listened to in sequential order. ### Medium @@ -32,8 +35,10 @@ sequential order. Choose the type of audio for your podcast: - **Podcast**: a standard podcast. -- **Music**: A feed of music organized into an "album" with each item a song within the album. -- **Audiobook**: A specific type of audio with one item per feed, or where items represent chapters within a book. +- **Music**: A feed of music organized into an "album" with each item a song + within the album. +- **Audiobook**: A specific type of audio with one item per feed, or where items + represent chapters within a book. ## Classification @@ -43,7 +48,8 @@ From the dropdown menu, choose which language is spoken in your podcast. ### Category -Choose the category that represents your podcast, such as Arts, Comedy, Sports, Technology, etc. +Choose the category that represents your podcast, such as Arts, Comedy, Sports, +Technology, etc. ### Other categories (optional) @@ -51,21 +57,25 @@ You can choose a second category in addition to the main category you set up. ### Parental advisory -Choose if your podcast has explicit content or swearing or choose Clean if your podcast is suitable for everyone. You -can also choose to leave this category as undefined. When [creating a new episode](../podcast/episodes), -you will also have the opportunity to choose clean, explicit, or undefined on a per episode basis. +Choose if your podcast has explicit content or swearing or choose Clean if your +podcast is suitable for everyone. You can also choose to leave this category as +undefined. When [creating a new episode](../podcast/episodes), you will also +have the opportunity to choose clean, explicit, or undefined on a per episode +basis. ## Author ### Owner name and email -Enter the owner name and email in the provided fields. This is only visible in the RSS feed and is used by other -podcasting platforms to verify your ownership of your podcast. You can choose to remove the owner email -from the public RSS feed by using the provided toggle. +Enter the owner name and email in the provided fields. This is only visible in +the RSS feed and is used by other podcasting platforms to verify your ownership +of your podcast. You can choose to remove the owner email from the public RSS +feed by using the provided toggle. ### Publisher -If your podcast is part of a podcast network or is produced by a company, enter the publisher here. +If your podcast is part of a podcast network or is produced by a company, enter +the publisher here. ### Copyright @@ -73,40 +83,47 @@ You can optionally add the copyright holder in this field. ### Fediverse identity -Enter the handle (or nickname) for your podcast. This will allow people on Mastodon and other Fediverse services -to follow your podcast. Your handle will be shown as @yourdomain.com@handle on the Fediverse. +Enter the handle (or nickname) for your podcast. This will allow people on +Mastodon and other Fediverse services to follow your podcast. Your handle will +be shown as @yourdomain.com@handle on the Fediverse. -To learn more about Fediverse integration, visit the [Fediverse documentation page](../instance/fediverse). +To learn more about Fediverse integration, visit the +[Fediverse documentation page](../instance/fediverse). ### Podcast banner -Upload a banner image to be displayed at the top of your podcast's home page. The banner must have a 3:1 ration and -be at least 1500px wide. +Upload a banner image to be displayed at the top of your podcast's home page. +The banner must have a 3:1 ration and be at least 1500px wide. ### Premium -Toggle this setting to set all episodes by default as premium. When creating an episode, it will default to premium, -and you can still choose to make some episodes, trailers, or bonus content as free and public. +Toggle this setting to set all episodes by default as premium. When creating an +episode, it will default to premium, and you can still choose to make some +episodes, trailers, or bonus content as free and public. ## Open Podcast Prefix Project (OP3) -The [Open Podcast Prefix Project](https://op3.dev) is an open source and trusted third party analytics service. If -you toggle this to enabled, you will be able to view analytics for your podcast over time including the number of -listens over time, episode comparison charts, and more. +The [Open Podcast Prefix Project](https://op3.dev) is an open source and trusted +third party analytics service. If you toggle this to enabled, you will be able +to view analytics for your podcast over time including the number of listens +over time, episode comparison charts, and more. ## Location You can optionally add a real or fictitious location name in this field. When -[creating a new episode](../podcast/episodes) you also can add a location to an individual episode. +[creating a new episode](../podcast/episodes) you also can add a location to an +individual episode. ## Advanced Parameters You can optionally toggle the following settings: -- **Prevent podcast from being copied**: this locks your podcast and does not allow other podcast platforms to import - your podcast. If you decide in the future to migrate away from Castopod to a new platform, this toggle will need to be - unchecked. -- **Podcast should be hidden from public catalogues**: If toggled, a best effort is made to hide the entire podcast from - appearing in Apple Podcasts, YouTube Music, and any other third party podcast apps. (Not guaranteed) -- **Podcast will not be having new episodes**: If your podcast comes to an end, you can toggle this to let listeners - know there will not be new episodes. +- **Prevent podcast from being copied**: this locks your podcast and does not + allow other podcast platforms to import your podcast. If you decide in the + future to migrate away from Castopod to a new platform, this toggle will need + to be unchecked. +- **Podcast should be hidden from public catalogues**: If toggled, a best effort + is made to hide the entire podcast from appearing in Apple Podcasts, YouTube + Music, and any other third party podcast apps. (Not guaranteed) +- **Podcast will not be having new episodes**: If your podcast comes to an end, + you can toggle this to let listeners know there will not be new episodes. diff --git a/docs/src/content/docs/en/getting-started/docker.mdx b/docs/src/content/docs/en/getting-started/docker.mdx index e129904d..ff3c1234 100644 --- a/docs/src/content/docs/en/getting-started/docker.mdx +++ b/docs/src/content/docs/en/getting-started/docker.mdx @@ -9,8 +9,9 @@ its automated build process: - [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod): an all-in-one image integrating [FrankenPHP](https://frankenphp.dev/) and - [Caddy](https://caddyserver.com/), optimized for production environments. - It is based on [serversideup/php](https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp). + [Caddy](https://caddyserver.com/), optimized for production environments. It + is based on + [serversideup/php](https://serversideup.net/open-source/docker-php/docs/image-variations/frankenphp). Castopod requires a MySQL-compatible database to function. Optionally, a Redis service can be configured as the caching layer. @@ -22,8 +23,9 @@ service can be configured as the caching layer. - `latest`, latest version build - `develop` [unstable], latest development branch build -Other unstable channels (e.g., `beta`, `next`, `2-next`) may be actively pushed during development phases. -See [all tags in the docker hub](https://hub.docker.com/r/castopod/castopod/tags). +Other unstable channels (e.g., `beta`, `next`, `2-next`) may be actively pushed +during development phases. See +[all tags in the docker hub](https://hub.docker.com/r/castopod/castopod/tags). + +### Plugin README + +The `README.md` file should contain with any additional information to help +guide the user in using the plugin. + +It is loaded on Castopod's admin area when the plugin is installed. + +### Plugin LICENSE + +In addition to specifying [the license in the manifest](./manifest#license), you +may add a `LICENSE.md` file. + +Just like the `README.md` file, its contents will be loaded into Castopod's +admin area, in the plugin's view page for the user to read. + +### Plugin icon + +Generally, the plugin icon is displayed next to its title, it is an SVG file +intended to give a graphical representation of the plugin. + +The icon should be squared, and be legible in a 64px by 64px circle. + +### Internationalization (i18n) + +Plugins can be translated. Translation strings live inside the `i18n` folder. +Translation files are JSON files named as locale keys: + + + +- **i18n** + - en.json // default locale + - fr.json + - de.json + - … + + + +Supported locales are: +`br`,`ca`,`de`,`en`,`es`,`fr`,`nn-no`,`pl`,`pt-br`,`sr-latn`,`zh-hans`. + +The translation strings allow you to translate the title, description and +settings keys (ie. labels, hints, helpers, etc.). + + + + ```json + // i18n/en.json + { + "title": "Hello, World!", + "description": "A Castopod plugin to greet the world!", + "settings": { + "general": { + "field-key": { + "label": "Enter a text", + "hint": "You can enter any type of character." + } + }, + "podcast": {}, + "episode": {} + } + } + ``` + + + ```json + // i18n/fr.json + { + "title": "Bonjour, le Monde !", + "description": "Un plugin castopod pour saluer le monde !", + "settings": { + "general": { + "field-key": { + "label": "Saisissez un texte", + "hint": "Vous pouvez saisir n'importe quel type de caractère." + } + }, + "podcast": {}, + "episode": {} + } + } + ``` + + diff --git a/docs/src/content/docs/en/plugins/install.mdx b/docs/src/content/docs/en/plugins/install.mdx new file mode 100644 index 00000000..9f179850 --- /dev/null +++ b/docs/src/content/docs/en/plugins/install.mdx @@ -0,0 +1,88 @@ +--- +title: Install plugins +--- + +import { Aside, Badge } from "@astrojs/starlight/components"; + +The recommended way to add and maintain plugins is via the CLI for speed, +reliability, and reproducible setups. A manual file-based method is also +available if terminal access isn’t possible. + +- [CLI](#cli): install, update, remove, or sync all plugins from a configuration + file for consistent environments. + +- [Manual](#install-manually-drag-and-drop): place the plugin folder in the + correct path; Castopod detects valid plugins automatically. + +## CLI + +Use the CLI when server access is available; it supports version pinning and +reproducible setups via configuration files. + +With that, plugins are automatically tracked in two writable configuration files +whenever you run the commands: + +- [`plugins.json`](./reference/plugins-json): lists the plugins you want to use, + along with their version requirements. Think of it as a _wish list_ of + plugins. + +- [`plugins-lock.json`](./reference/plugins-lock-json): records the exact + versions that were actually installed, along with extra metadata. This ensures + that even if a plugin is updated later, Castopod can reproduce the same + working setup you had before. + +### Commands + +- Install the latest compatible version of a plugin: + + ```sh + php spark plugins:add acme/hello-world + ``` + +- Install a specific version in order to pin environments: + + ```sh + php spark plugins:add acme/hello-world@1.0.0 + ``` + +- Update a plugin to the latest available release: + + ```sh + php spark plugins:update acme/hello-world + ``` + +- Completely remove an installed plugin: + + ```sh + php spark plugins:remove acme/hello-world + ``` + +- Install all plugins listed in [`plugins.json`](./reference/plugins-json): + + ```sh + php spark plugins:install + ``` + + + +## Install manually (drag-and-drop) + +If you only have FTP access or prefer a simple approach: + +1. Download the plugin from + [**plugins.castopod.org**](https://plugins.castopod.org/). + +2. Place the plugin folder inside your Castopod installation at: + `plugins/{vendor}/{plugin-name}/` diff --git a/docs/src/content/docs/en/plugins/reference/hooks.mdx b/docs/src/content/docs/en/plugins/reference/hooks.mdx new file mode 100644 index 00000000..54a65f1e --- /dev/null +++ b/docs/src/content/docs/en/plugins/reference/hooks.mdx @@ -0,0 +1,86 @@ +--- +title: Hooks reference +--- + +Hooks are methods of the Plugin class, they are executed in parts of the +Castopod codebase. + +## List + +| Hooks | Executes in | +| ---------------- | ----------- | +| rssBeforeChannel | RSS Feed | +| rssAfterChannel | RSS Feed | +| rssBeforeItem | RSS Feed | +| rssAfterItem | RSS Feed | +| siteHead | Website | + +### `rssBeforeChannel` + +This hook is executed just before rendering the `` tag in the Podcast +RSS feed using the given Podcast object. + +Here is a good place to alter the Podcast object. + +```php +public function rssBeforeChannel(Podcast $podcast): void +{ + // … +} +``` + +### `rssAfterChannel` + +This hook is executed after rendering all of the `` tags in the Podcast +RSS feed. + +Here is a good place to add new tags to the generated channel. + +```php +public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void +{ + // … +} +``` + +### `rssBeforeItem` + +This hook is executed before rendering an `` tag in the Podcast RSS feed +using the given Episode object. + +Here is a good place to alter the Episode object. + +```php +public function rssBeforeItem(Episode $episode): void +{ + // … +} +``` + +### `rssAfterItem` + +This hook is executed after rendering an ``'s tags in the Podcast RSS +feed. + +Here is a good place to add new tags to the generated item. + +```php +public function rssAfterItem(Epsiode $episode, RssFeed $item): void +{ + // … +} +``` + +### siteHead + +This hook is executed in the public pages' `` tag. + +Here is a good place to add meta tags, custom styles, and third-party scripts to +Castopod's public pages. + +```php +public function siteHead(HtmlHead $head): void +{ + // … +} +``` diff --git a/docs/src/content/docs/en/plugins/reference/manifest-json.mdx b/docs/src/content/docs/en/plugins/reference/manifest-json.mdx new file mode 100644 index 00000000..364e6e57 --- /dev/null +++ b/docs/src/content/docs/en/plugins/reference/manifest-json.mdx @@ -0,0 +1,189 @@ +--- +title: manifest.json reference +--- + +import { Aside } from "@astrojs/starlight/components"; + +This page details the attributes of a +[Castopod Plugin's manifest](../../../../../pages/plugin-manifest.schema.json.ts), +which must be a JSON file. + +### `name` (required) + +The plugin name, including 'vendor-name/' prefix. Examples: + +- acme/hello-world +- adaures/click + +The name must be lowercase and consist of words separated by `-`, `.` or `_`. +The complete name should match +`^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*$`. + +### `version` (required) + +The plugin's semantic version (eg. 1.0.0) - see https://semver.org/ + +### `minCastopodVersion` (required) + +The minimal version of Castopod with which the plugin is compatible. + +### `description` + +The plugin's description. This helps people discover your plugin when listed in +repositories. + +### `authors` + +Array of one or more persons having authored the plugin. A person is represented +by object with a required "name" field and optional "email" and "url" fields: + +```json +{ + "name": "Jean Deau", + "email": "jean.deau@example.com", + "url": "https://example.com/" +} +``` + + + +### `homepage` + +The URL to the plugin's homepage. + +### `license` + +**Default:** `"UNLICENSED"` + +Specify a license for your plugin so that people know how they are permitted to +use it, and any restrictions you're placing on it. + +### `private` + +**Default:** `false` + +Whether or not to publish the plugin in public directories. If set to `true`, +directories should refuse to publish the plugin. + +### `submodule` + +**Default:** `false` + +Indicates whether the plugin is part of a monorepo (a single Git repository +containing multiple plugins). If `true`, the plugin shares its repository with +other plugins. In this case, releases should be tagged using the format +`@` (eg. `acme/hello-world@1.0.0`) to ensure proper +version indexing by plugin repositories. + +### `keywords` + +Array of strings to help your plugin get discovered when listed in repositories. + +### `hooks` + +List of hooks used by the plugin. If the hook is not specified, Castopod will +not run it. + +### `settings` + +Declare settings forms for persisting user data. The plugin's settings forms can +be declared at three levels: `general`, `podcast`, and `episode`. + +Each level accepts one or more fields, identified by a key. + +```json +{ + "settings": { + "general": { // general settings form + "field-key": { + "type": "text", // default field type: a text input + "label": "Enter a text" + }, + … + }, + "podcast": {…}, // settings form for each podcast + "episode": {…}, // settings form for each episode + } +} +``` + +The `general`, `podcast`, and `episode` settings are maps with each key being a +field key and the value being a `Field` object. + +#### Field object + +A field is a form element: + +| Property | Type | Note | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `type` | `checkbox`, `datetime`, `email`, `group`, `html`, `markdown`, `number`, `radio-group`, `rss`, `select-multiple`, `select`, `text`, `textarea`, `toggler`, `url` | Default is `text` | +| `label` (required) | `string` | Can be translated (see i18n) | +| `hint` | `string` | Can be translated (see i18n) | +| `helper` | `string` | Can be translated (see i18n) | +| `defaultValue` | `string` | You can specify multiple comma separated values for `select-multiple` | +| `validationRules` | `string` \| `array` | See [available validation rules](#available-validation-rules) | +| `optional` | `boolean` | Default is `false` | +| `options` | `Options` | Required for `radio-group`, `select-multiple`, and `select` types. | +| `multiple` | `boolean` | Default is `false` | +| `fields` | `Array` | Required for `group` type | + +#### Options object + +The `Options` object properties are option keys and the value is an `Option`. + +##### Option object + +| Property | Type | Note | +| ------------------ | -------- | ---------------------------- | +| `label` (required) | `string` | Can be translated (see i18n) | +| `description` | `string` | Can be translated (see i18n) | + +### `files` + +Array of file patterns that describes the entries to be included when your +plugin is installed. + +### `repository` + +Repository where the plugin's code lives. Helpful for people who want to +contribute. + +#### Available validation rules + +The following rules are a subset of +[CodeIgniter4's validation rules](https://codeigniter.com/user_guide/libraries/validation.html#available-rules). + +| Rule | Parameter | Description | Example | +| --------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| alpha | No | Fails if field has anything other than alphabetic characters in ASCII. | | +| alpha_dash | No | Fails if field contains anything other than alphanumeric characters, underscores or dashes in ASCII. | | +| alpha_numeric | No | Fails if field contains anything other than alphanumeric characters in ASCII. | | +| alpha_numeric_punct | No | Fails if field contains anything other than alphanumeric, space, or this limited set of punctuation characters: `~` (tilde), `!` (exclamation), `#` (number), `$` (dollar), `%` (percent), `&` (ampersand), `*` (asterisk), `-` (dash), `_` (underscore), `+` (plus), `=` (equals), `\|` (vertical bar),`:`(colon),`.` (period). | | +| alpha_numeric_space | No | Fails if field contains anything other than alphanumeric or space characters in ASCII. | | +| alpha_space | No | Fails if field contains anything other than alphabetic characters or spaces in ASCII. | | +| decimal | No | Fails if field contains anything other than a decimal number. Also accepts a `+` or `-` sign for the number. | | +| differs | Yes | Fails if field does not differ from the one in the parameter. | `differs[field_name]` | +| exact_length | Yes | Fails if field length is not exactly the parameter value. One or more comma-separated values are possible. | `exact_length[5]` or `exact_length[5,8,12]` | +| greater_than | Yes | Fails if field is less than or equal to the parameter value or not numeric. | `greater_than[8]` | +| greater_than_equal_to | Yes | Fails if field is less than the parameter value, or not numeric. | `greater_than_equal_to[5]` | +| hex | No | Fails if field contains anything other than hexadecimal characters. | | +| in_list | Yes | Fails if field is not within a predetermined list. | `in_list[red,blue,green]` | +| integer | No | Fails if field contains anything other than an integer. | | +| is_natural | No | Fails if field contains anything other than a natural number: `0`, `1`, `2`, `3`, etc. | | +| is_natural_no_zero | No | Fails if field contains anything other than a natural number, except zero: `1`, `2`, `3`, etc. | | +| less_than | Yes | Fails if field is greater than or equal to the parameter value or not numeric. | `less_than[8]` | +| less_than_equal_to | Yes | Fails if field is greater than the parameter value or not numeric. | `less_than_equal_to[8]` | +| max_length | Yes | Fails if field is longer than the parameter value. | `max_length[8]` | +| min_length | Yes | Fails if field is shorter than the parameter value. | `min_length[3]` | +| not_in_list | Yes | Fails if field is within a predetermined list. | `not_in_list[red,blue,green]` | +| regex_match | Yes | Fails if field does not match the regular expression. | `regex_match[/regex/]` | +| valid_base64 | No | Fails if field contains anything other than valid Base64 characters. | | +| valid_date | Yes | Fails if field does not contain a valid date. Any string that `strtotime()` accepts is valid if you don't specify an optional parameter that matches a date format. | `valid_date[d/m/Y]` | diff --git a/docs/src/content/docs/en/plugins/reference/plugins-json.mdx b/docs/src/content/docs/en/plugins/reference/plugins-json.mdx new file mode 100644 index 00000000..e767c330 --- /dev/null +++ b/docs/src/content/docs/en/plugins/reference/plugins-json.mdx @@ -0,0 +1,35 @@ +--- +title: plugins.json +--- + +`plugins.json` declares which plugins are intended for installation and the +version ranges they have to satisfy. It is human-edited, concise, and designed +for sharing intent across environments. + +## Example + +```json +// writable/plugins.json +{ + "plugins": { + "acme/hello-world": "2.1.0", + "ad-aures/custom-rss": "^1.0.0", + "ad-aures/show-notes-signature": "dev-main" + }, + "repositories": {} +} +``` + +## Properties + +### `plugins` + +A mapping of plugin identifiers to version ranges, expressing intent for what to +install. Keys use the vendor/plugin naming convention; values accept semantic +version ranges or development versions like `dev-main`. + +### `repositories` + +An array of additional plugin sources to consult when resolving versions. + +> Not yet implemented. diff --git a/docs/src/content/docs/en/plugins/reference/plugins-lock-json.mdx b/docs/src/content/docs/en/plugins/reference/plugins-lock-json.mdx new file mode 100644 index 00000000..14d73e48 --- /dev/null +++ b/docs/src/content/docs/en/plugins/reference/plugins-lock-json.mdx @@ -0,0 +1,57 @@ +--- +title: plugins-lock.json +--- + +`plugins-lock.json` records the exact versions and artifact locations that were +resolved and installed, ensuring repeatable environments across machines and +deployments. It is generated and updated by tooling, not hand-edited. + +## Example + +```json +// manifest-lock.json +{ + "version": "1.0", + "plugins": { + "ad-aures/custom-rss": { + "version": "dev-main", + "source": { + "url": "https://github.com/ad-aures/castopod-plugins.git", + "reference": "20433933ab4e42bb44f8367ab72ee1ffae723019" + }, + "dist": { + "url": "https://plugins.castopod.org/static/plugins/db/cf/15/ad-aures_custom-rss_dev-main.zip", + "path": "", + "checksum": "dbcf153694f09c5788e6cccbca2db4cb9605faa7c1399d59960edee00fa9f853" + } + } + // ... + } +} +``` + +## Properties + +### `version` + +The lockfile schema version, used by tooling to parse and validate the file +across releases; not related to any plugin version. Changing schema may alter +field shapes or semantics. + +### `plugins` + +A mapping from a plugin identifier to a locked entry that captures the exact +resolved version, the source and distribution details. This section is the +authoritative record for reinstalls. + +Each plugin entry contains: + + - `version`: The exact version (or branch reference such as dev-main) that was resolved and installed for this plugin, guaranteeing consistent reinstallation. + + - `source`: Describes the canonical source of the plugin’s code used to produce the distribution artifact. + - `url`: The upstream Git repository. + - `path`: The root path of the plugin. + - `reference`: The exact commit or revision used, for provenance and reproducibility. + - `dist`: Points to the installable artifact and verifies its integrity. + - `url`: A direct link to the packaged plugin archive that the installer downloads. + - `checksum`: A cryptographic hash of the archive used to validate integrity and prevent tampering during install. diff --git a/docs/src/content/docs/en/plugins/share.mdx b/docs/src/content/docs/en/plugins/share.mdx new file mode 100644 index 00000000..c5c5f077 --- /dev/null +++ b/docs/src/content/docs/en/plugins/share.mdx @@ -0,0 +1,82 @@ +--- +title: Share your plugin +--- + +import { Aside } from "@astrojs/starlight/components"; + +After taking some time to create your plugin, you may want to share it with the +community for other podcasters to enjoy! + +You can choose to make your plugin discoverable by submitting it to the +[official Castopod plugin repository](https://plugins.castopod.org/) so that +other podcasters can install it in a few clicks or with one +[CLI command](./install#cli). + +## Before submitting + +The official Castopod plugin repository has stricter requirements than than the +ones needed for a plugin to merely load in Castopod; it also requires rich +metadata to improve search, categorization, and discovery. + + + +### Requirements for the official repository + +**Your plugin must be hosted in a public Git repository.** Private or local +repositories cannot be crawled or indexed by the official repository. + +Having said that, make sure to go through the following list to check that +everything is ready for indexing. + +#### Required files + +- `manifest.json` with the following required properties: + - `name` (vendor/plugin) + - `version` (semantic version; must match a Git tag to be listed) + - `description` (one concise sentence) + - `minCastopodVersion` (minimum compatible Castopod version) + - `hooks` (declared hooks the plugin implements) + - `license` (SPDX identifier or license name, matching the LICENSE file) +- `Plugin.php` (main class; implementing the declared hooks) +- `LICENSE.md` (matches the license declared in the manifest) + +#### Optional (recommended) + +- `README.md` (what it does, install/config steps, usage examples, changelog, + etc.) +- `icon.svg` (squared, readable at small sizes; displayed as a ~64x64 circle) + +#### Encouraged + +- `i18n/` translations (e.g., `en.json`, `fr.json`) to reach more users across + the globe! + +## Submit + +When everything is ready, submit at: +[plugins.castopod.org/submit](https://plugins.castopod.org/submit) + +Provide the plugin’s Git repository URL and the path to its `manifest.json` if +needed (the plugin’s root directory). + +## After submission + +The repository is queued for crawling; within a few moments, the plugin should +be indexed. + +During crawling, automated checks validate structure and metadata; all Git tags +are scanned and each release is bundled as a zip file and listed as a version. + + diff --git a/docs/src/content/docs/en/user-guide/instance/fediverse.mdx b/docs/src/content/docs/en/user-guide/instance/fediverse.mdx index 00b83a21..2f0a3a47 100644 --- a/docs/src/content/docs/en/user-guide/instance/fediverse.mdx +++ b/docs/src/content/docs/en/user-guide/instance/fediverse.mdx @@ -6,30 +6,35 @@ title: Fediverse Wikipedia defines the Fediverse as: -> The fediverse is a collection of social networking services that can communicate with each other using a common -> protocol, ActivityPub. Users of different websites can send and receive status updates, multimedia files and other -> data across the network. +> The fediverse is a collection of social networking services that can +> communicate with each other using a common protocol, ActivityPub. Users of +> different websites can send and receive status updates, multimedia files and +> other data across the network. -Your podcast is connected to the Fediverse and, for example, Mastodon users can subscribe to your handle and will -receive a message every time your podcast publishes a new episode. You can also broadcast messages to your followers -on the Fediverse from your [home page](../website#home). Mastodon users can like and share your posts. +Your podcast is connected to the Fediverse and, for example, Mastodon users can +subscribe to your handle and will receive a message every time your podcast +publishes a new episode. You can also broadcast messages to your followers on +the Fediverse from your [home page](../website#home). Mastodon users can like +and share your posts. ## Manage Fediverse Blocks -You can block a Fediverse user or an entire domain to help stop harassment or spam. +You can block a Fediverse user or an entire domain to help stop harassment or +spam. ### Blocked Accounts -To block a specific Fediverse user, enter the user's handle in the **Account Handle** text box. It should be in -the format of `@user@domain.com`. Press `Block` to block the user from following your podcast. +To block a specific Fediverse user, enter the user's handle in the **Account +Handle** text box. It should be in the format of `@user@domain.com`. Press +`Block` to block the user from following your podcast. -A list of all blocked accounts is shown below the **Account Handle** text box. You can choose to unblock a user -by pressing the `Unblock` button. +A list of all blocked accounts is shown below the **Account Handle** text box. +You can choose to unblock a user by pressing the `Unblock` button. ### Blocked Domains -To block an entire domain, enter the domain name such as `www.example.com` in the **Domain Name** field and press -`Block`. +To block an entire domain, enter the domain name such as `www.example.com` in +the **Domain Name** field and press `Block`. -A list of all blocked domains is shown below the **Domain Name** text box. You can choose to unblock a domain -by pressing the `Unblock` button. +A list of all blocked domains is shown below the **Domain Name** text box. You +can choose to unblock a domain by pressing the `Unblock` button. diff --git a/docs/src/content/docs/en/user-guide/instance/index.mdx b/docs/src/content/docs/en/user-guide/instance/index.mdx index 0e55012d..4764871d 100644 --- a/docs/src/content/docs/en/user-guide/instance/index.mdx +++ b/docs/src/content/docs/en/user-guide/instance/index.mdx @@ -6,15 +6,17 @@ import { LinkCard, CardGrid } from "@astrojs/starlight/components"; ## What is an instance? -Your podcast is hosted on your sever and connected to the [Fediverse](fediverse) in what is called an _instance_. -Your instance can manage multiple podcasts, people and users, and host additional pages found on all of your podcasts. +Your podcast is hosted on your sever and connected to the [Fediverse](fediverse) +in what is called an _instance_. Your instance can manage multiple podcasts, +people and users, and host additional pages found on all of your podcasts. -Managing your instance, including podcasts, people, and users is separate from the settings for each -individual podcast. +Managing your instance, including podcasts, people, and users is separate from +the settings for each individual podcast. ## Ready to get started? -Learn more about adding podcasts, users, and more by clicking one of the links below. +Learn more about adding podcasts, users, and more by clicking one of the links +below. diff --git a/docs/src/content/docs/en/user-guide/instance/pages.mdx b/docs/src/content/docs/en/user-guide/instance/pages.mdx index e2a83265..b1e76248 100644 --- a/docs/src/content/docs/en/user-guide/instance/pages.mdx +++ b/docs/src/content/docs/en/user-guide/instance/pages.mdx @@ -4,8 +4,8 @@ title: Pages ## Add Pages -You can add static pages linked from your podcast's home page. From the left hand navigation, choose the `+` sign or -click `Pages` -> `New Page`. +You can add static pages linked from your podcast's home page. From the left +hand navigation, choose the `+` sign or click `Pages` -> `New Page`. ### Title @@ -13,16 +13,19 @@ In the **Title** text box, enter the name for the page you are adding. ### Permalink -In the `Permalink` text box, enter the permalink if you want it different than the title. +In the `Permalink` text box, enter the permalink if you want it different than +the title. ### Content -You can add your page content in the **Content** text box. You can use Markdown to style your text. +You can add your page content in the **Content** text box. You can use Markdown +to style your text. -When finished, press the `Create page` button. You can view your page from your podcast's home page. It will be -linked in the right hand side bar navigation. +When finished, press the `Create page` button. You can view your page from your +podcast's home page. It will be linked in the right hand side bar navigation. ## Delete or Edit a Page -After adding a page, you can delete the page or edit it by choosing `Pages` -> `All Pages` from the instance -navigation sidebar on the left. Press the corresponding button to view the page, edit it, or delete it. +After adding a page, you can delete the page or edit it by choosing `Pages` -> +`All Pages` from the instance navigation sidebar on the left. Press the +corresponding button to view the page, edit it, or delete it. diff --git a/docs/src/content/docs/en/user-guide/instance/persons.mdx b/docs/src/content/docs/en/user-guide/instance/persons.mdx index ce17ec1d..7adb60da 100644 --- a/docs/src/content/docs/en/user-guide/instance/persons.mdx +++ b/docs/src/content/docs/en/user-guide/instance/persons.mdx @@ -2,21 +2,29 @@ title: Manage Podcast contributors --- -The **Persons** section allows you to add podcast contributors. It is needed in the Podcast section to assign -roles and is also used on the **Credits** page linked from your podcast's homepage. +The **Persons** section allows you to add podcast contributors. It is needed in +the Podcast section to assign roles and is also used on the **Credits** page +linked from your podcast's homepage. When Persons are assigned to a specific +episode, there will be a link on the episode's page to list all persons +assigned. -From the left hand navigation, press `Persons` to expand the menu. To view a list of all people that have been -added to Castopod, press `All Persons`. +A Person must be created in the **Persons** section before it can be +[assigned to an episode](../podcast/episodes#persons). + +From the left hand navigation, press `Persons` to expand the menu. To view a +list of all people that have been added to Castopod, press `All Persons`. ## Add a person -To add a person, press `New Person` from the left hand sidebar or press `Create a person` from the upper right hand -corner of the **All Persons** page and fill out the following fields: +To add a person, press `New Person` from the left hand sidebar or press +`Create a person` from the upper right hand corner of the **All Persons** page +and fill out the following fields: -- **Avatar**: You can optionally add a picture or avatar of the person. Press `Choose File` and upload a picture - from your computer. It must be at least 400px wide and tall. +- **Avatar**: You can optionally add a picture or avatar of the person. Press + `Choose File` and upload a picture from your computer. It must be at least + 400px wide and tall. - **Full Name**: Enter the name as you want it displayed on your website. -- **Unique Name**: Enter a unique username for the person you are adding. This will be displayed in the Podcasts - secton when assigning this person a role. -- **Information URL**: Enter a URL for the person's homepage, profile, or social media account to be linked from the - [Credits page](../website/credits). +- **Unique Name**: Enter a unique username for the person you are adding. This + will be displayed in the Podcasts secton when assigning this person a role. +- **Information URL**: Enter a URL for the person's homepage, profile, or social + media account to be linked from the [Credits page](../website/credits). diff --git a/docs/src/content/docs/en/user-guide/instance/podcast.mdx b/docs/src/content/docs/en/user-guide/instance/podcast.mdx index 8b9fd8fe..554c8d70 100644 --- a/docs/src/content/docs/en/user-guide/instance/podcast.mdx +++ b/docs/src/content/docs/en/user-guide/instance/podcast.mdx @@ -2,19 +2,23 @@ title: Create or import a podcast --- -You can create a new podcast or import an existing podcast into Castopod in the Podcasts section. If you are adding -a second podcast to Castopod, see the [Home page documentation](../website#home-page) for how that will change your +import { Aside } from "@astrojs/starlight/components"; + +You can create a new podcast or import an existing podcast into Castopod in the +Podcasts section. If you are adding a second podcast to Castopod, see the +[Home page documentation](../website#home-page) for how that will change your home page. -From the left hand navigation sidebar, press the `+` sign to the right of Podcasts to create your first podcast. +From the left hand navigation sidebar, press the `+` sign to the right of +Podcasts to create your first podcast. ## Podcast Identity ### Podcast Cover -To upload your podcast cover art, press the `Choose File` button and choose your cover art from your computer. The -cover art needs to be in JPG or PNG format and a minimum of 1400px wide and tall with a maximum of 3000px wide and -tall. +To upload your podcast cover art, press the `Choose File` button and choose your +cover art from your computer. The cover art needs to be in JPG or PNG format and +a minimum of 1400px wide and tall with a maximum of 3000px wide and tall. ### Title @@ -22,13 +26,15 @@ Enter the name of your podcast in the **Title** field. ### Description -Describe what your podcast is about. You can use Markdown to style the text and you can resize the text box by -pressing and holding the bottom right hand corner of the text box. +Describe what your podcast is about. You can use Markdown to style the text and +you can resize the text box by pressing and holding the bottom right hand corner +of the text box. ### Type -Choose how your listeners should listen to your podcast. **Episodic** lets listeners know they can consume your podcast -in any order, such as an interview podcast. Choose **Serial** if your episodes are meant to be listened to in +Choose how your listeners should listen to your podcast. **Episodic** lets +listeners know they can consume your podcast in any order, such as an interview +podcast. Choose **Serial** if your episodes are meant to be listened to in sequential order. ### Medium @@ -36,8 +42,10 @@ sequential order. Choose the type of audio for your podcast: - **Podcast**: a standard podcast. -- **Music**: A feed of music organized into an "album" with each item a song within the album. -- **Audiobook**: A specific type of audio with one item per feed, or where items represent chapters within a book. +- **Music**: A feed of music organized into an "album" with each item a song + within the album. +- **Audiobook**: A specific type of audio with one item per feed, or where items + represent chapters within a book. ## Classification @@ -47,7 +55,8 @@ From the dropdown menu, choose which language is spoken in your podcast. ### Category -Choose the category that represents your podcast, such as Arts, Comedy, Technology, etc. +Choose the category that represents your podcast, such as Arts, Comedy, +Technology, etc. ### Other categories (optional) @@ -55,21 +64,25 @@ You can choose a second category in addition to the main category you set up. ### Parental advisory -Choose if your podcast has explicit content or swearing or choose Clean if your podcast is suitable for everyone. You -can also choose to leave this category as undefined. When [creating a new episode](../podcast/episodes), -you will also have the opportunity to choose clean, explicit, or undefined on a per episode basis. +Choose if your podcast has explicit content or swearing or choose Clean if your +podcast is suitable for everyone. You can also choose to leave this category as +undefined. When [creating a new episode](../podcast/episodes), you will also +have the opportunity to choose clean, explicit, or undefined on a per episode +basis. ## Author ### Owner name and email -Enter the owner name and email in the provided fields. This is only visible in the RSS feed and is used by other -podcasting platforms to verify your ownership of your podcast. You can choose to remove the owner email -from the public RSS feed by using the provided toggle. +Enter the owner name and email in the provided fields. This is only visible in +the RSS feed and is used by other podcasting platforms to verify your ownership +of your podcast. You can choose to remove the owner email from the public RSS +feed by using the provided toggle. ### Publisher -If your podcast is part of a podcast network or is produced by a company, enter the publisher here. +If your podcast is part of a podcast network or is produced by a company, enter +the publisher here. ### Copyright @@ -77,69 +90,86 @@ You can optionally add the copyright holder in this field. ### Fediverse identity -Enter the handle (or nickname) for your podcast. This will allow people on Mastodon and other Fediverse services -to follow your podcast. Your handle will be shown as @yourdomain.com@handle on the Fediverse. +Enter the handle (or nickname) for your podcast. This will allow people on +Mastodon and other Fediverse services to follow your podcast. Your handle will +be shown as @yourdomain.com@handle on the Fediverse. -To learn more about Fediverse integration, visit the [Fediverse documentation page](../instance/fediverse). +To learn more about Fediverse integration, visit the +[Fediverse documentation page](../instance/fediverse). ### Podcast banner -Upload a banner image to be displayed at the top of your podcast's home page. The banner must have a 3:1 ration and -be at least 1500px wide. +Upload a banner image to be displayed at the top of your podcast's home page. +The banner must have a 3:1 ratio and be at least 1500px wide. ### Premium -Toggle this setting to set all episodes by default as premium. When creating an episode, it will default to premium, -and you can still choose to make some episodes, trailers, or bonus content as free and public. +Toggle this setting to set all episodes by default as premium. When creating an +episode, it will default to premium, and you can still choose to make some +episodes, trailers, or bonus content as free and public. ## Open Podcast Prefix Project (OP3) -The [Open Podcast Prefix Project](https://op3.dev) is an open source and trusted third party analytics service. If -you toggle this to enabled, you will be able to view analytics for your podcast over time including the number of -listens over time, episode comparison charts, and more. +The [Open Podcast Prefix Project](https://op3.dev) is an open source and trusted +third party analytics service. If you toggle this to enabled, you will be able +to view analytics for your podcast over time including the number of listens +over time, episode comparison charts, and more. ## Location You can optionally add a real or fictitious location name in this field. When -[creating a new episode](../podcast/episodes) you also can add a location to an individual episode. +[creating a new episode](../podcast/episodes) you also can add a location to an +individual episode. ## Advanced Parameters You can optinally toggle the following settings: -- **Prevent podcast from being copied**: this locks your podcast and does not allow other podcast platforms to import - your podcast. If you decide in the future to migrate away from Castopod to a new platform, this toggle will need to be - unchecked. -- **Podcast should be hidden from public catalogues**: If toggled, a best effort is made to hide the entire podcast from - appearing in Apple Podcasts, YouTube Music, and any other third party podcast apps. (Not guaranteed) -- **Podcast will not be having new episodes**: If your podcast comes to an end, you can toggle this to let listeners - know there will not be new episodes. +- **Prevent podcast from being copied**: this locks your podcast and does not + allow other podcast platforms to import your podcast. If you decide in the + future to migrate away from Castopod to a new platform, this toggle will need + to be unchecked. +- **Podcast should be hidden from public catalogues**: If toggled, a best effort + is made to hide the entire podcast from appearing in Apple Podcasts, YouTube + Music, and any other third party podcast apps. (Not guaranteed) +- **Podcast will not be having new episodes**: If your podcast comes to an end, + you can toggle this to let listeners know there will not be new episodes. ## Import an existing podcast -When importing a podcast, make sure you own the rights for this podcast before importing it. -Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution. +When importing a podcast, make sure you own the rights for this podcast before +importing it. Copying and broadcasting a podcast without the proper rights is +piracy and is liable to prosecution. ### Import the podcast -To import a podcast, enter the podcast's **Feed URL**. The feed must be in XML or RSS format. You may want to validate -the feed to make sure there are no errors in the RSS or XML feed prior to importing as errors may cause the import -to fail. One popular feed validator is [Cast Feed Validator](https://www.castfeedvalidator.com) from -Blubrry Podcasting. +To import a podcast, enter the podcast's **Feed URL**. The feed must be in XML +or RSS format. You may want to validate the feed to make sure there are no +errors in the RSS or XML feed prior to importing as errors may cause the import +to fail. One popular feed validator is +[Cast Feed Validator](https://www.castfeedvalidator.com) from Blubrry +Podcasting. ### Podcast information -Enter the handle for your podcast. This will be part of the URL for others to interact with your podcast on the -Fediverse. It will be yourdomain.com@nameofyourpodcast. +Enter the handle for your podcast. This will be part of the URL for others to +interact with your podcast on the Fediverse. It will be +yourdomain.com@nameofyourpodcast. Choose the language your podcast is recorded in from the drop down box. -Lastly, choose the Category of your podcast, such as Sports, Technology, Arts, etc. +Lastly, choose the Category of your podcast, such as Sports, Technology, Arts, +etc. -When complete, click Add import to queue. You will then be redirected to a Podcast imports page to view the status -of the import. You can refresh the page to see the status of the import including the import duration and how many +When complete, click Add import to queue. You will then be redirected to a +Podcast imports page to view the status of the import. You can refresh the page +to see the status of the import including the import duration and how many episodes were imported when complete. -!!! note -If your import times out, check your `max_execution_time` in your `PHP.ini` file. You may need to increase it -from 30 seconds (the default) to add more time, such as 300 seconds (5 minutes) for larger podcasts. + diff --git a/docs/src/content/docs/en/user-guide/instance/settings.mdx b/docs/src/content/docs/en/user-guide/instance/settings.mdx index 9ab8845c..91c1d911 100644 --- a/docs/src/content/docs/en/user-guide/instance/settings.mdx +++ b/docs/src/content/docs/en/user-guide/instance/settings.mdx @@ -2,7 +2,8 @@ title: Settings --- -To change or update the settings for your instance, choose `Settings` from the left hand navigation. +To change or update the settings for your instance, choose `Settings` from the +left hand navigation. ## General settings @@ -18,21 +19,26 @@ Update description of your instance in the `Site description` text box. #### Site icon -You can upload a site icon, also known as a favicon, by pressing `Choose file`. Site icons are what you see -on your browser tabs, bookmarks, and shortcuts on mobile devices. The image must be at least 500px tall and wide. +You can upload a site icon, also known as a favicon, by pressing `Choose file`. +Site icons are what you see on your browser tabs, bookmarks, and shortcuts on +mobile devices. The image must be at least 500px tall and wide. ### Images -If you come across broken or missing images on your site, press `Regenerate images`. This process may take time. +If you come across broken or missing images on your site, press +`Regenerate images`. This process may take time. ### Housekeeping You can run various tasks for your instance: -- **Reset counts**: This will reset and recalculate counts for followers, posts, and comments. -- **Rename episode files**: This option renames all episodes files to a random name. If one of your private - episodes leaks, toggle this setting to effectively hide the episode. -- **Clear all cache**: This option will flush the Redis cache or writable / cache files. +- **Reset counts**: This will reset and recalculate counts for followers, posts, + and comments. +- **Rename episode files**: This option renames all episodes files to a random + name. If one of your private episodes leaks, toggle this setting to + effectively hide the episode. +- **Clear all cache**: This option will flush the Redis cache or writable / + cache files. ## Theme diff --git a/docs/src/content/docs/en/user-guide/instance/users.mdx b/docs/src/content/docs/en/user-guide/instance/users.mdx index 1c48d32d..6592d64a 100644 --- a/docs/src/content/docs/en/user-guide/instance/users.mdx +++ b/docs/src/content/docs/en/user-guide/instance/users.mdx @@ -2,15 +2,19 @@ title: Users --- -Roles and permissions in Castopod are defined in two ways, **Instance** users and -[**Podcast**. contributors](../podcast/contributors). For a detailed list of permissions, view -he [Auth](../../getting-started/auth) page. +Roles and permissions in Castopod are defined in two ways, **Instance** users +and [**Podcast**. contributors](../podcast/contributors). For a detailed list of +permissions, view the [Auth](../../getting-started/auth) page. ## Adding a User to the instance -You can add a user to your instance by choosing `Users` -> `New User` from the left hand navigation menu. There -are three roles you can assign to an instance user: +You can add a user to your instance by choosing `Users` -> `New User` from the +left hand navigation menu. There are three roles you can assign to an instance +user: -- **Super Admin**: This user has complete control over Castopod including adding or removing podcasts or users. -- **Manager**: This user can manage Castopod's content, such as adding or importing a new podcast and managing people. -- **Podcaster**: This is for general users of Castopod who can access the admin interface. +- **Super Admin**: This user has complete control over Castopod including adding + or removing podcasts or users. +- **Manager**: This user can manage Castopod's content, such as adding or + importing a new podcast and managing people. +- **Podcaster**: This is for general users of Castopod who can access the admin + interface. diff --git a/docs/src/content/docs/en/user-guide/podcast/analytics.mdx b/docs/src/content/docs/en/user-guide/podcast/analytics.mdx index bab83578..30228427 100644 --- a/docs/src/content/docs/en/user-guide/podcast/analytics.mdx +++ b/docs/src/content/docs/en/user-guide/podcast/analytics.mdx @@ -2,22 +2,24 @@ title: Analytics --- -Castopod comes with a number of different analytics and statistics about your podcast(s). From the left hand -navigation, choose `Analytics` to view the various options. +Castopod comes with a number of different analytics and statistics about your +podcast(s). From the left hand navigation, choose `Analytics` to view the +various options. ## Audience overview -This page provides an overview of your episode's daily downloads, monthly downloads, and daily bandwidth used. +This page provides an overview of your episode's daily downloads, monthly +downloads, and daily bandwidth used. ## Unique listeners -View graphs of the number of daily and monthly users of your podcast. Use the zoom function at the top of each -graph to zoom in or out. +View graphs of the number of daily and monthly users of your podcast. Use the +zoom function at the top of each graph to zoom in or out. ## Listening time -View graphs of your listener's daily and monthly cumulative listening time. Use the zoom function at the top of each -graph to zoom in or out. +View graphs of your listener's daily and monthly cumulative listening time. Use +the zoom function at the top of each graph to zoom in or out. ## Players @@ -28,8 +30,9 @@ This page provides four pie charts to view statistics for the last week: - Episode downloads by device - Episode downloads by operating system -You can hover over each graph and press the three dots to download statistics for the graph you are hovering over. -You can interact with the graphs to download or print: +You can hover over each graph and press the three dots to download statistics +for the graph you are hovering over. You can interact with the graphs to +download or print: - Image (PNG, JPG, SVG, or PDF) - Data (JSON, CSV, XLSX, HTML, or PDF) @@ -37,13 +40,15 @@ You can interact with the graphs to download or print: ## Locations -You can view where your listeners are located by choosing _Locations_. Two pie charts are available showing -episode downloads by country for the past week and past year. At the bottom of the page, you can view a world map -showing your listener's locations. +You can view where your listeners are located by choosing _Locations_. Two pie +charts are available showing episode downloads by country for the past week and +past year. At the bottom of the page, you can view a world map showing your +listener's locations. ## Time periods -Two bar graphs displaying the week day and time of day your listeners listen to your podcsat. +Two bar graphs displaying the week day and time of day your listeners listen to +your podcsat. ## Web pages visits diff --git a/docs/src/content/docs/en/user-guide/podcast/broadcast.mdx b/docs/src/content/docs/en/user-guide/podcast/broadcast.mdx index f54cb499..9d37d3bc 100644 --- a/docs/src/content/docs/en/user-guide/podcast/broadcast.mdx +++ b/docs/src/content/docs/en/user-guide/podcast/broadcast.mdx @@ -4,31 +4,35 @@ title: Broadcast your podcast ## Add your podcast to podcast directories -Listeners can add your RSS feed to their podcast app of choice to subscribe to your podcast. Most listeners will -find your podcast in a podcast directory, such as Apple Podcasts, YouTube Music, Spotify, or the Podcast Index. -These four are the most popular podcast directories and a number of other directories pull their data from Apple -Podcasts. +Listeners can add your RSS feed to their podcast app of choice to subscribe to +your podcast. Most listeners will find your podcast in a podcast directory, such +as Apple Podcasts, YouTube Music, Spotify, or the Podcast Index. These four are +the most popular podcast directories and a number of other directories pull +their data from Apple Podcasts. -Visit [Podnews](https://podnews.net/article/all-the-podcast-directories) to learn how to add your podcast to -most of the podcast directories. +Visit [Podnews](https://podnews.net/article/all-the-podcast-directories) to +learn how to add your podcast to most of the podcast directories. -Once your podcast is listed in the podcast directories, you can link to them on your home page by choosing -`Broadcast` from the left hand navigation. +Once your podcast is listed in the podcast directories, you can link to them on +your home page by choosing `Broadcast` from the left hand navigation. ### Podcasting Apps -Castopod provides the ability to link to a majority of podcast directories where users can find your podcast. -For each directory listed fill out the link to your podcast in a specific directory and the ID. If you want -an icon displayed on your home page, toggle `Display in podcast homepage?`. +Castopod provides the ability to link to a majority of podcast directories where +users can find your podcast. For each directory listed fill out the link to your +podcast in a specific directory and the ID. If you want an icon displayed on +your home page, toggle `Display in podcast homepage?`. -After pressing `Save` in the upper right hand corner, visit your home page to see the icons with hyperlinks -on the right hand side of your home page under _Listen on_. +After pressing `Save` in the upper right hand corner, visit your home page to +see the icons with hyperlinks on the right hand side of your home page under +_Listen on_. ### Social Networks -If your podcast has a presence on social networks, Castopod provides the ability to link to your social network -profiles. Add the link to your profile page in the text field and toggle if you want it displayed on your -home page. +If your podcast has a presence on social networks, Castopod provides the ability +to link to your social network profiles. Add the link to your profile page in +the text field and toggle if you want it displayed on your home page. -After pressing `Save` in the upper right hand corner, visit your home page to see the icons with hyperlinks -on the right hand side of your home page under _Find your podcast on_. +After pressing `Save` in the upper right hand corner, visit your home page to +see the icons with hyperlinks on the right hand side of your home page under +_Find your podcast on_. diff --git a/docs/src/content/docs/en/user-guide/podcast/contributors.mdx b/docs/src/content/docs/en/user-guide/podcast/contributors.mdx index e57971ee..2bb44016 100644 --- a/docs/src/content/docs/en/user-guide/podcast/contributors.mdx +++ b/docs/src/content/docs/en/user-guide/podcast/contributors.mdx @@ -2,8 +2,9 @@ title: Contributors --- -From the podcast dashboard, click **Contributors** to add or edit users to have access to the podcast and its settings. -For a detailed list of permissions, view the [Auth](../../getting-started/auth) page. +From the podcast dashboard, click **Contributors** to add or edit users to have +access to the podcast and its settings. For a detailed list of permissions, view +the [Auth](../../getting-started/auth) page. ## Roles @@ -15,14 +16,15 @@ The admin user has complete control over the individual podcast. ### Editor -The editor has access to management functions including podcast import, managing persons, creating or deleting -episodes, and managing clips. +The editor has access to management functions including podcast import, managing +persons, creating or deleting episodes, and managing clips. ### Author -The autorh can manage content of the podcast, but cannot publish an episode. They can manage -[contributors](contributors), create clips, and create episodes. +The autorh can manage content of the podcast, but cannot publish an episode. +They can manage [contributors](contributors), create clips, and create episodes. ### Guest -The guest can view the podcast dashboard and view episodes. They cannot edit or add any content. +The guest can view the podcast dashboard and view episodes. They cannot edit or +add any content. diff --git a/docs/src/content/docs/en/user-guide/podcast/dashboard.mdx b/docs/src/content/docs/en/user-guide/podcast/dashboard.mdx index 4ec7e608..309cb354 100644 --- a/docs/src/content/docs/en/user-guide/podcast/dashboard.mdx +++ b/docs/src/content/docs/en/user-guide/podcast/dashboard.mdx @@ -2,49 +2,59 @@ title: Podcast Dashboard --- -When you choose your podcast from the Admin dashboard you will be redirected to the Podcast dashboard page. +When you choose your podcast from the Admin dashboard you will be redirected to +the Podcast dashboard page. -The Podcast dashboard shows you up to five of the latest episodes, both those published and scheduled. You can click -on any of these episodes to view them or click `See all episodes` to see a list of every episode. +The Podcast dashboard shows you up to five of the latest episodes, both those +published and scheduled. You can click on any of these episodes to view them or +click `See all episodes` to see a list of every episode. ## Edit your podcast -To edit your podcast, press `Edit podcast` in the upper right hand corner or `Edit podcast` in the left hand side -navigation. +To edit your podcast, press `Edit podcast` in the upper right hand corner or +`Edit podcast` in the left hand side navigation. -You can edit any of the [fields you filled out when creating your podcast](../instance/podcast). +You can edit any of the +[fields you filled out when creating your podcast](../instance/podcast). When finished press the `Save podcast` button in the upper right hand corner. -You can also choose to delete your podcast by pressing `Delete podcast` at the bottom of the Edit podcast page. +You can also choose to delete your podcast by pressing `Delete podcast` at the +bottom of the Edit podcast page. ## Manage persons -If you have added people to Persons in the Admin dashboard, you can assign them a role here. People assigned roles -will show up on the [Credits page](../website/credits) linked from your home page to give them +If you have added people to Persons in the Admin dashboard, you can assign them +a role here. People assigned roles will show up on the +[Credits page](../website/credits) linked from your home page to give them credit for contributing to the podcast. -Roles include Administration, Audio production, cast, guests, hosts, writing, and more. +Roles include Administration, Audio production, cast, guests, hosts, writing, +and more. -From the **Persons** drop down, choose the person you want to add. Then choose a role from the \*_Roles_ drop down and -press `Add person(s)`. +From the **Persons** drop down, choose the person you want to add. Then choose a +role from the \*_Roles_ drop down and press `Add person(s)`. -A list of all people assigned roles will be displayed. You can remove them by pressing the `Remove` button. +A list of all people assigned roles will be displayed. You can remove them by +pressing the `Remove` button. Adding persons to your podcast populates the Credits page linked from your [home page](../website). ## Podcast imports -Podcast imports show you the status of a feed that you imported into Castopod, including the feed name, -how long the import took, and how many episodes were imported. Under **Actions** you can try re-import the feed -if the original import failed or delete the status message for the imported feed. +Podcast imports show you the status of a feed that you imported into Castopod, +including the feed name, how long the import took, and how many episodes were +imported. Under **Actions** you can try re-import the feed if the original +import failed or delete the status message for the imported feed. -If the feed you imported has been updated after your import, press `Synchronize Feeds` in the upper right hand -corner to import the missing episodes. +If the feed you imported has been updated after your import, press +`Synchronize Feeds` in the upper right hand corner to import the missing +episodes. ## Sync feeds -You can also synchronize missing episode from an imported feed by choosing `Sync Feeds` from the Podcast dashboard -menu. The **Feed URL** should auto-populate if you have previously imported a feed. Press `Add to queue` to import -any missing episodes from the feed. +You can also synchronize missing episode from an imported feed by choosing +`Sync Feeds` from the Podcast dashboard menu. The **Feed URL** should +auto-populate if you have previously imported a feed. Press `Add to queue` to +import any missing episodes from the feed. diff --git a/docs/src/content/docs/en/user-guide/podcast/episodes.mdx b/docs/src/content/docs/en/user-guide/podcast/episodes.mdx index 604ee60f..d24ce22f 100644 --- a/docs/src/content/docs/en/user-guide/podcast/episodes.mdx +++ b/docs/src/content/docs/en/user-guide/podcast/episodes.mdx @@ -2,130 +2,178 @@ title: Episodes --- -From the left hand navigation, click **Episodes** and the menu will expand to **All Episodes** and **New Episode**. +import { Aside } from "@astrojs/starlight/components"; + +From the left hand navigation, click **Episodes** and the menu will expand to +**All Episodes** and **New Episode**. ## All Episodes -To view a list of all episodes, click `All Episodes`. Each episode will be displayed in a list with its name and -notes, Visibility such as published or scheduled, and how many downloads and comments each episode has. +To view a list of all episodes, click `All Episodes`. Each episode will be +displayed in a list with its name and notes, Visibility such as published or +scheduled, and how many downloads and comments each episode has. ### Actions and the Episode Dashboard -Click the three dots under **Actions** to view a menu of options for an episode or click on an individual episode to -view the available actions: +Click the three dots under **Actions** to view a menu of options for an episode +or click on an individual episode to view the available actions: -- **Go to page**: This will redirect you to the public page of the episode and display the show notes. -- **Edit**: This allows you to change the fields you filled when creating the episode (see below). +- **Go to page**: This will redirect you to the public page of the episode and + display the show notes. +- **Edit**: This allows you to change the fields you filled when creating the + episode (see below). ### Embeddable player -Choose a theme color and press the copy button to the right of the text box to copy the -code to your clipboard. Insert the code into a web page to display the episode. +Choose a theme color and press the copy button to the right of the text box to +copy the code to your clipboard. Insert the code into a web page to display the +episode. ### Persons -If you have added people in the Admin interface, you can choose a person and assign them a role, such -as guest, for a given episode. +If you have added [people in the Admin interface](../instance/persons), you can +choose a person and assign them a role, such as guest, for a given episode. When +persons are assigned to an episode, a link will be displayed on the episode page +listing the person's name and role and they will be listed in the Credits page. +Adding persons to an episode page is optional. ### Video clips and Soundbites -The process to create a video clip and soundbite is the same. It may take a minute for the file to load and you -will see the waveform. +The process to create a video clip and soundbite is the same. It may take a +minute for the file to load and you will see the waveform. -A video clip will create an MP4 video of the audio with text from the transcript matched to the audio. A soundbite -will create an audio clip. +A video clip will create an MP4 video of the audio with text from the transcript +matched to the audio. A soundbite will create an audio clip. -Enter the name of the clip you want to create. Then drag the slider to where you want to start and end your -video clip or soundbite. You can press the `Play` button to preview your clip. +Enter the name of the clip you want to create. Then drag the slider to where you +want to start and end your video clip or soundbite. You can press the `Play` +button to preview your clip. -For video clips, choose a format, including Landscape, Portrait or Squared and a background theme color. +For video clips, choose a format, including Landscape, Portrait or Squared and a +background theme color. -Then press the `Create` button, you will be redirected to a status page displaying the status of creating the clip. -Refresh the page to see if it's `Running` or completed. You may need to be patient depending on the length of the clip. +Then press the `Create` button, you will be redirected to a status page +displaying the status of creating the clip. Refresh the page to see if it's +`Running` or completed. You may need to be patient depending on the length of +the clip. -:::note -To create video clips and soundbites, your server must have `FFMPEG` installed and a transcript. -::: + ## Create episode -To add an episode, press the `Add an episode button` in the upper right hand corner of the Episode dashboard or click -`New episode` in the left hand navigation under `Episodes`. +To add an episode, press the `Add an episode button` in the upper right hand +corner of the Episode dashboard or click `New episode` in the left hand +navigation under `Episodes`. ### Episode Info #### Audio file -Press the `Choose File` button to select the audio file to upload to Castopod. The audio file must be an mp3 or m4a -file and cannot be larger than 512 megabytes. +Press the `Choose File` button to select the audio file to upload to Castopod. +The audio file must be an mp3 or m4a file and cannot be larger than 512 +megabytes. ### Episode cover -You can optionally add a different podcast cover / artwork. Press `Choose File` and select an image from your computer -that must be at least 1400px wide and tall and no larger than 3000px wide and tall. If you do not choose an -episode cover, your default podcast artwork will be used. +You can optionally add a different podcast cover / artwork. Press `Choose File` +and select an image from your computer that must be at least 1400px wide and +tall and no larger than 3000px wide and tall. If you do not choose an episode +cover, your default podcast artwork will be used. #### Title -Enter the name for your new episode. Do not add the season or episode number in the **Title** field. Choose a clear -and concise episode name to help your listeners. +Enter the name for your new episode. Do not add the season or episode number in +the **Title** field. Choose a clear and concise episode name to help your +listeners. #### Permalink -The permalink is the link to the public episode page. This will be automatically filled out based on the title -you entered above. If you wish to have a different permalink, press the edit button to the right of the -displayed permalink. +The permalink is the link to the public episode page. This will be automatically +filled out based on the title you entered above. If you wish to have a different +permalink, press the edit button to the right of the displayed permalink. #### Season and Episode -You can optionally add a season and / or episode number to your episode. If you are adding a new episode and a -previous episode had a season and /or episode number, the field(s) will be auto filled for you. You can leave it or -update with a different season and / or episode number. +You can optionally add a season and / or episode number to your episode. If you +are adding a new episode and a previous episode had a season and /or episode +number, the field(s) will be auto filled for you. You can leave it or update +with a different season and / or episode number. ### Show Notes -Describe your episode in detail. You can use up to 4000 characters, and you can use Markdown to style your show -notes. You can expand the text box by pressing and holding the bottom right corner of the text box. +Describe your episode in detail. You can use up to 4000 characters, and you can +use Markdown to style your show notes. You can expand the text box by pressing +and holding the bottom right corner of the text box. -When your show notes are complete, press `Preview` to view how your show notes will be displayed. +When your show notes are complete, press `Preview` to view how your show notes +will be displayed. ### Additional Files #### Transcripts -You can add a transcript to your episode by choosing a file in SRT or VTT format to upload. Transcripts will be -shown in a tab on the episode page and some podcast apps such as Apple Podcasts can display the transcript. -Transcripts help users who may have a hearing disability and can also help with search engine optimization. +You can add a transcript to your episode by choosing a file in SRT or VTT format +to upload. Transcripts will be shown in a tab on the episode page and some +podcast apps such as Apple Podcasts can display the transcript. Transcripts help +users who may have a hearing disability and can also help with search engine +optimization. #### Chapters -You can optionally upload a chapters file in JSON format. To learn more about chapters and for an example of the -correct format, visit the [Podcast Namespace](https://github.com/Podcastindex-org/podcast-namespace/blob/main/chapters/jsonChapters.md). +You can optionally upload a chapters file in JSON format. To learn more about +chapters and for an example of the correct format, visit the +[Podcast Namespace](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/examples/chapters/example.json). -:::note -Not all podcast players natively support chapters in JSON format. More modern players, such as Fountain and -Apple Podcasts, do support chapters in JSON format. Chapters do not allow you to click to select the chapter -in Castopod's embeddable player at this time. -::: + ### Publish your episode -When complete, press the `Create episode` button at the bottom of the page. You will be automatically directed to -the next step to publish your episode. Your episode is in draft mode and is not yet published. You can preview -how your episode will look when published or publish your episode. To publish your episode, press the `Publish` button -in the upper right hand corner. +When complete, press the `Create episode` button at the bottom of the page. You +will be automatically directed to the next step to publish your episode. Your +episode is in draft mode and is not yet published. You can preview how your +episode will look when published or publish your episode. To publish your +episode, press the `Publish` button in the upper right hand corner. ### Create your announcement post -After pressing `Publish` you will be directed to the next page to draft your announcement post. Write your message -in the text box provided. This will be the message broadcast to the Fediverse and published on your podcast's home -page. +After pressing `Publish` you will be directed to the next page to draft your +announcement post. Write your message in the text box provided. This will be the +message broadcast to the Fediverse and published on your podcast's home page. ### Publication date -Choose `Now` or `Schedule` to publish your episode. If you choose `Now`, your episode will be live. Or you can -schedule the day and time to publish your episode by choosing the `Schedule` radio button and choosing the day and -time. Press `Publish` to finish. +Choose `Now` or `Schedule` to publish your episode. If you choose `Now`, your +episode will be live. Or you can schedule the day and time to publish your +episode by choosing the `Schedule` radio button and choosing the day and time. +Press `Publish` to finish. -Your RSS feed will be automatically updated with your episode information and listeners who subscribe will be -notified of a new episode. Congratulations on publishing your first episode! +Your RSS feed will be automatically updated with your episode information and +listeners who subscribe will be notified of a new episode. Congratulations on +publishing your first episode! + +## Editing an episode + +You can edit an episode before or after it is published. You can change any of +the fields, including uploading a new episode, updating the show notes, or +updating the transcripts or chapters. Select the episode you want to edit and +choose `Edit Episode`. You can change just one field or multiple fields at the +same time. After you have made the changes, press `Save Episode` in the upper +right corner to finalize the changes. + +You can also edit the publication for the episode. Choose the episode again, and +press `Edit Publication` in the upper right hand corner. Update the announcement +post or publication date. When complete, press `Edit publication`. You can also +choose to cancel the publication and it won't be published but will not be +deleted. diff --git a/docs/src/content/docs/en/user-guide/podcast/index.mdx b/docs/src/content/docs/en/user-guide/podcast/index.mdx index 8f8eb2d1..c3482092 100644 --- a/docs/src/content/docs/en/user-guide/podcast/index.mdx +++ b/docs/src/content/docs/en/user-guide/podcast/index.mdx @@ -4,11 +4,12 @@ title: Manage Podcasts import { LinkCard, CardGrid } from "@astrojs/starlight/components"; -From the Castopod admin interface, click **Podcasts** and then **All Podcasts** and you will be redirected to the -podcast dashboard. This page displays the latest episodes, including any scheduled episodes. +From the Castopod admin interface, click **Podcasts** and then **All Podcasts** +and you will be redirected to the podcast dashboard. This page displays the +latest episodes, including any scheduled episodes. -From the podcast dashboard, you can add episodes, view analytics, add links to your homepage, and more. Learn more -by clicking the links below. +From the podcast dashboard, you can add episodes, view analytics, add links to +your homepage, and more. Learn more by clicking the links below. diff --git a/docs/src/content/docs/en/user-guide/website/index.mdx b/docs/src/content/docs/en/user-guide/website/index.mdx index fe5bdd1d..9b45f749 100644 --- a/docs/src/content/docs/en/user-guide/website/index.mdx +++ b/docs/src/content/docs/en/user-guide/website/index.mdx @@ -2,64 +2,81 @@ title: Podcast Home Page --- -The podcast home page is your landing page for your listeners to view episode announcements, messages, episodes, -and links to where to find your podcast. If you have only one podcast, your users will be automatically redirected -from your top level domain, such as `www.yourpodcast.com`, to the podcast page at -`www.yourpodcast.com/yourpodcasthandle`. +import { Aside } from "@astrojs/starlight/components"; -If you are hosting multiple podcasts, your top level page will display the artwork for each podcast. Clicking on -the podcast artwork will take you to your podcast page at `www.yourpodcast.com/yourpodcasthandle`. +The podcast home page is your landing page for your listeners to view episode +announcements, messages, episodes, and links to where to find your podcast. If +you have only one podcast, your users will be automatically redirected from your +top level domain, such as `www.yourpodcast.com`, to the podcast page at +`www.yourpodcast.com/@yourpodcasthandle`. + +If you are hosting multiple podcasts, your top level page will display the +artwork for each podcast. Clicking on the podcast artwork will take you to your +podcast page at `www.yourpodcast.com/@yourpodcasthandle`. ## Home Page -If you are logged in to your podcast, you will see a strip across the top of the page with a link to the admin -interface of your podcast, a bell that will show if you have any new notifications, and the user who is logged in. -You can click on the user for quick access to your account, changing your password, or to log out. +If you are logged in to your podcast, you will see a strip across the top of the +page with a link to the admin interface of your podcast, a bell that will show +if you have any new notifications, and the user who is logged in. You can click +on the user for quick access to your account, changing your password, or to log +out. -:::note -If you have multiple podcasts, you can click the user dropdown to switch to choose which podcast to interact with. -If your account has access to two or more podcasts, pay attention to which podcast is broadcasting messages! -::: + + +Listeners can click the `Follow` button to follow any messages, including +episode announcements, from a Fediverse app, such as Mastodon. After clicking +`Follow` a pop-up box will be displayed where the user enterse their Fediverse handle and then clicks `Proceed to follow`. ### Podcast RSS feed -On the right hand side is a link to your RSS Podcast feed. Users can copy that and subscribe directly in their -podcast app. +On the right hand side is a link to your RSS Podcast feed. Users can copy that +and subscribe directly in their podcast app. ### Social Networks -Below the RSS feed is are icons that link to all of the social networks you toggled on in the -[Broadcast](../podcast/broadcast#podcasting-apps) section of the podcast's admin section. +Below the RSS feed is are icons that link to all of the social networks you +toggled on in the [Broadcast](../podcast/broadcast#podcasting-apps) section of +the podcast's admin section. ### Podcast Directories -Below the social networking links is **Listen On** which displays icons linked to the podcast directories you -toggled on in the [Broadcast](../podcast/broadcast#social-networks) section of the podcast's -admin section. +Below the social networking links is **Listen On** which displays icons linked +to the podcast directories you toggled on in the +[Broadcast](../podcast/broadcast#social-networks) section of the podcast's admin +section. ## Activity -The **Activity** tab is located directly under your podcast artwork is the default home page for your podcast. Website -visitors are presented with a list of messages in chronological order. If you are logged in as a podcast -administrator, you will see a text box where you can write a message to be broadcasted to your followers. If you -include the episode URL, a preview of that episode will be included in your message. +The **Activity** tab is located directly under your podcast artwork is the +default home page for your podcast. Website visitors are presented with a list +of messages in chronological order. If you are logged in as a podcast +administrator, you will see a text box where you can write a message to be +broadcasted to your followers. If you include the episode URL, a preview of that +episode will be included in your message. Below each message there are four icons: -- **Comments**: This will show you the number of comments on a given message. Clicking this will show you all replies to - your message. You can click the three dots to block a user or a domain in case of spam. -- **Shares**: This shows the number of times your message was shared by other users. +- **Comments**: This will show you the number of comments on a given message. + Clicking this will show you all replies to your message. You can click the + three dots to block a user or a domain in case of spam. +- **Shares**: This shows the number of times your message was shared by other + users. - **Favorites**: This shows how many people liked your message. ## Episodes -The **Episodes** tab shows a list of all episode announcements you have shared. If you are using season numbers, it -will default to the current season. Users can click the **Season** link above all of the messages for a dropdown menu -to view past episodes by season. +The **Episodes** tab shows a list of all episode announcements you have shared. +If you are using season numbers, it will default to the current season. Users +can click the **Season** link above all of the messages for a dropdown menu to +view past episodes by season. ## About @@ -67,7 +84,8 @@ The About tab shares general information about your podcast, including: - Podcast description - Podcast category -- The [people](../podcast/dashboard#manage-persons) who contribute to the podcast +- The [people](../podcast/dashboard#manage-persons) who contribute to the + podcast - Statistics - Number of seasons - Number of episodes diff --git a/docs/src/content/docs/es/getting-started/auth.mdx b/docs/src/content/docs/es/getting-started/auth.mdx index 8862b4b0..208a243d 100644 --- a/docs/src/content/docs/es/getting-started/auth.mdx +++ b/docs/src/content/docs/es/getting-started/auth.mdx @@ -13,24 +13,25 @@ niveles: ### Roles de instancia -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------ | -| Super administrador | Tiene control completo sobre Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Administrador | Administrar contenido de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Usuarios generales de Castopod. | admin.access | +| role | description | permissions | +| ------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super administrador | Tiene control completo sobre Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Administrador | Administrar contenido de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Usuarios generales de Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Permisos de instancia -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------------------ | | admin.access | Puedes acceder al área de administración de Castopod. | | admin.settings | Puede acceder a la configuración de Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Puede administrar usuarios de Castopod. | | persons.manage | Puede administrar personas. | | pages.manage | Puede administrar páginas. | @@ -39,13 +40,13 @@ niveles: | podcasts.import | Puede importar podcasts. | | fediverse.manage-blocks | Puedes bloquear la interacción de actores/dominios del fediverso con Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Permisos y roles por podcast ### Roles por podcast -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -54,11 +55,11 @@ niveles: | Autor | Gestiona el contenido del podcast #\{id\} pero no puede publicarlo. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Invitado | Colaborador general del podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Permisos por podcast -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | --------------------------------------------------------------------------------------------------------------- | @@ -82,4 +83,4 @@ niveles: | episodes.manage-publications | Puede publicar/despublicar episodios y publicaciones del podcast #\{id\}. | | episodes.manage-comments | Puede crear/eliminar los comentarios de episodio del podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/es/getting-started/docker.mdx b/docs/src/content/docs/es/getting-started/docker.mdx index af60d871..2ccfd7d0 100644 --- a/docs/src/content/docs/es/getting-started/docker.mdx +++ b/docs/src/content/docs/es/getting-started/docker.mdx @@ -12,7 +12,8 @@ construcción automatizada: - [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server): una configuración de Nginx para Castopod -Adicionalmente, Castopod requiere una base de datos compatible con MySQL. También se puede añadir una base de datos Redis como gestor de caché. +Adicionalmente, Castopod requiere una base de datos compatible con MySQL. +También se puede añadir una base de datos Redis como gestor de caché. ## Etiquetas admitidas diff --git a/docs/src/content/docs/es/getting-started/install.mdx b/docs/src/content/docs/es/getting-started/install.mdx index d2b9b571..688b9562 100644 --- a/docs/src/content/docs/es/getting-started/install.mdx +++ b/docs/src/content/docs/es/getting-started/install.mdx @@ -10,15 +10,15 @@ compatibles con PHP-MySQL. ## Requisitos -- PHP v8.1 or higher -- MySQL versión 5.7 o superior o MariaDB versión 10.2 o superior +- PHP v8.5 or higher +- MySQL versión 8.4 o superior o MariaDB versión 11.4 o superior - Soporte HTTPS - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/es/getting-started/update.mdx b/docs/src/content/docs/es/getting-started/update.mdx index ac804bd5..c4fa9178 100644 --- a/docs/src/content/docs/es/getting-started/update.mdx +++ b/docs/src/content/docs/es/getting-started/update.mdx @@ -34,8 +34,7 @@ de errores 🐛 y mejoras de rendimiento ⚡. @@ -78,9 +77,9 @@ Alternativamente, puedes encontrar la versión en el archivo ### No he actualizado mi instancia en mucho tiempo… ¿Qué debo hacer? -No problem! Just get the latest release as described above. Simplemente cuando vaya a través de las instrucciones de lanzamiento (4), -realice la actualización secuencialmente, desde el más antiguo hasta el más -reciente. +No problem! Just get the latest release as described above. Simplemente cuando +vaya a través de las instrucciones de lanzamiento (4), realice la actualización +secuencialmente, desde el más antiguo hasta el más reciente. > Puede que quieras hacer una copia de seguridad de tu instancia dependiendo del > tiempo que no hayas actualizado Castopod. diff --git a/docs/src/content/docs/es/index.mdx b/docs/src/content/docs/es/index.mdx index 01d16e2a..576dd341 100644 --- a/docs/src/content/docs/es/index.mdx +++ b/docs/src/content/docs/es/index.mdx @@ -15,15 +15,15 @@ Castopod es fácil de instalar y se ha desarrollado sobre ## Características - 🌱  Gratis & de código abierto (licencia AGPL v3). -- 🔐  Centrado en la soberanía de los datos: tu contenido, tu audiencia, y - tus estadísticas te pertenecen a ti y solo a ti. +- 🔐  Centrado en la soberanía de los datos: tu contenido, tu audiencia, y tus + estadísticas te pertenecen a ti y solo a ti. - 🪄  Funciones de Podcasting 2.0: GUID (interfaz gráfica de usuario), protección y bloqueo del podcast, transcripciones, monetización, episodios, geo-localización, personas, fragmentos de audio, … - 💬  Integración con redes sociales: - 🚀  Castopod es parte del Fediverso, una red social descentralizada - - ❤️  Se puede crear mensajes, compartir, agregar a favoritos y comentar - en episodios + - ❤️  Se puede crear mensajes, compartir, agregar a favoritos y comentar en + episodios - 📈  Análisis estadísticos integrados: - ⚖️  Compatible con GDPR / CCPA / LGPD - 🪙  Medida de audiencia con el estándar IABv2 @@ -35,8 +35,8 @@ Castopod es fácil de instalar y se ha desarrollado sobre - 🎬  Generar recortes de vídeo listos para compartir, a partir de tus episodios - 🔉  Generate soundbites - - ▶️  Reproductor incrustable, con el que insertar tus episodios en - cualquier sitio web! + - ▶️  Reproductor incrustable, con el que insertar tus episodios en cualquier + sitio web! - 💸  Monetización: - 🔗  Enlaces de financiación - 📲  Anuncios publicitarios del tipo click-para-escuchar @@ -44,17 +44,17 @@ Castopod es fácil de instalar y se ha desarrollado sobre contenido). - 💎  Premium podcasts - 📡  Publica tus episodios en todas partes con RSS: - - 📱  En todos los directorios y aplicaciones: Podcast Index, Apple - Podcasts, Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  En todos los directorios y aplicaciones: Podcast Index, Apple Podcasts, + Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Emite tus episodios al instante con WebSub. -- 📥  Importación de podcasts: mueve tu podcast existente en otro servicio - a tu servidor Castopod. +- 📥  Importación de podcasts: mueve tu podcast existente en otro servicio a tu + servidor Castopod. - 📤  Mueve tu podcast fuera de Castopod. - 🔀  Multi-podcast: aloja tantos podcasts como quieras en una misma instancia/panel de CASTOPOD. - 👥  Multi-usuario: añade colaboradores y define roles. -- 🌎  Soporte multilingüe i18n: traducido al Inglés, Francés, Polaco, - Alemán, Portugués brasileño, Español, Noruego, … ¡y +- 🌎  Soporte multilingüe i18n: traducido al Inglés, Francés, Polaco, Alemán, + Portugués brasileño, Español, Noruego, … ¡y [más por venir](https://translate.castopod.org)! ## Motivación @@ -140,7 +140,8 @@ Each of these solutions differ from one another, you may compare with the That being said, there are two main differences with other podcasting solutions: - Castopod puede ser auto-hospedado en tu propio servidor y es la única solución - que te permite mantener un control completo sobre los contenidos que produces. También, como es de código abierto, puedes incluso personalizarlo como desees + que te permite mantener un control completo sobre los contenidos que produces. + También, como es de código abierto, puedes incluso personalizarlo como desees y necesites. - Castopod es la única solución que hasta el momento integra tanto una red diff --git a/docs/src/content/docs/eu/getting-started/auth.mdx b/docs/src/content/docs/eu/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/eu/getting-started/auth.mdx +++ b/docs/src/content/docs/eu/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/eu/getting-started/docker.mdx b/docs/src/content/docs/eu/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/eu/getting-started/docker.mdx +++ b/docs/src/content/docs/eu/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/eu/getting-started/install.mdx b/docs/src/content/docs/eu/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/eu/getting-started/install.mdx +++ b/docs/src/content/docs/eu/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/eu/index.mdx b/docs/src/content/docs/eu/index.mdx index a2747fe8..dc77399c 100644 --- a/docs/src/content/docs/eu/index.mdx +++ b/docs/src/content/docs/eu/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/fa/getting-started/auth.mdx b/docs/src/content/docs/fa/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/fa/getting-started/auth.mdx +++ b/docs/src/content/docs/fa/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/fa/getting-started/docker.mdx b/docs/src/content/docs/fa/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/fa/getting-started/docker.mdx +++ b/docs/src/content/docs/fa/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/fa/getting-started/install.mdx b/docs/src/content/docs/fa/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/fa/getting-started/install.mdx +++ b/docs/src/content/docs/fa/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/fa/index.mdx b/docs/src/content/docs/fa/index.mdx index 2f23ff9b..4432a5d8 100644 --- a/docs/src/content/docs/fa/index.mdx +++ b/docs/src/content/docs/fa/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/fr-ca/getting-started/auth.mdx b/docs/src/content/docs/fr-ca/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/fr-ca/getting-started/auth.mdx +++ b/docs/src/content/docs/fr-ca/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/fr-ca/getting-started/docker.mdx b/docs/src/content/docs/fr-ca/getting-started/docker.mdx index 08b0e02b..1f39e0ae 100644 --- a/docs/src/content/docs/fr-ca/getting-started/docker.mdx +++ b/docs/src/content/docs/fr-ca/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/fr-ca/getting-started/install.mdx b/docs/src/content/docs/fr-ca/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/fr-ca/getting-started/install.mdx +++ b/docs/src/content/docs/fr-ca/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/fr-ca/index.mdx b/docs/src/content/docs/fr-ca/index.mdx index d6c18b5d..2efbd0a3 100644 --- a/docs/src/content/docs/fr-ca/index.mdx +++ b/docs/src/content/docs/fr-ca/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/fr/getting-started/auth.mdx b/docs/src/content/docs/fr/getting-started/auth.mdx index 627e61d7..2109a120 100644 --- a/docs/src/content/docs/fr/getting-started/auth.mdx +++ b/docs/src/content/docs/fr/getting-started/auth.mdx @@ -13,24 +13,25 @@ autorisations sont définis sur deux niveaux : ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| -------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super administrat·rice·eur | A un contrôle complet sur Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Gestionnaire | Gère le contenu de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Utilisateurs généraux de Castopod. | admin.access | +| role | description | permissions | +| -------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super administrat·rice·eur | A un contrôle complet sur Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Gestionnaire | Gère le contenu de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcast·rice·eur | Utilisateurs généraux de Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Autorisations dans l'instance -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | -------------------------------------------------------------------- | | admin.access | Peut accéder à la zone d'administration Castopod. | | admin.settings | Peut accéder aux paramètres de Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Peut gérer les utilisateurs de Castopod. | | persons.manage | Permet de gérer les personnes. | | pages.manage | Permet de gérer les pages. | @@ -39,13 +40,13 @@ autorisations sont définis sur deux niveaux : | podcasts.import | Peut importer des podcasts. | | fediverse.manage-blocks | Peut empêcher des act·rice·eur·s/domaines d'interagir avec Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Rôles et autorisations par podcast ### Rôles par podcast -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ---------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -54,11 +55,11 @@ autorisations sont définis sur deux niveaux : | Auteur / Autrice | Gère le contenu du podcast #\{id\} , mais ne peut pas le publier. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Invité | Contributeur général du podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Permissions par podcast -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | @@ -82,4 +83,4 @@ autorisations sont définis sur deux niveaux : | episodes.manage-publications | Peut publier/dépublier des épisodes et des messages de podcast #\{id\}. | | episodes.manage-comments | Peut créer/supprimer les commentaires de l'épisode du podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/fr/getting-started/docker.mdx b/docs/src/content/docs/fr/getting-started/docker.mdx index fdaaf71c..22270b6a 100644 --- a/docs/src/content/docs/fr/getting-started/docker.mdx +++ b/docs/src/content/docs/fr/getting-started/docker.mdx @@ -92,8 +92,8 @@ de données Redis peut être ajoutée en tant que gestionnaire de cache. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/fr/getting-started/install.mdx b/docs/src/content/docs/fr/getting-started/install.mdx index 6adf243b..9bfe50ff 100644 --- a/docs/src/content/docs/fr/getting-started/install.mdx +++ b/docs/src/content/docs/fr/getting-started/install.mdx @@ -10,15 +10,15 @@ serveurs web compatibles avec PHP-MySQL. ## Prérequis -- PHP v8.1 ou supérieure -- MySQL version 5.7 ou supérieure ou MariaDB version 10.2 ou supérieure +- PHP v8.5 ou supérieure +- MySQL version 8.4 ou supérieure ou MariaDB version 11.4 ou supérieure - Prise en charge HTTPS - Une horloge [synchronisée ntp](https://wiki.debian.org/NTP) pour valider les requêtes fédérés entrantes -### PHP v8.1 ou supérieure +### PHP v8.5 ou supérieure -PHP version 8.1 ou supérieure est requise, avec les extensions suivantes +PHP version 8.5 ou supérieure est requise, avec les extensions suivantes installées : - [intl](https://www.php.net/manual/fr/intl.requirements.php) diff --git a/docs/src/content/docs/fr/getting-started/update.mdx b/docs/src/content/docs/fr/getting-started/update.mdx index d8cea00c..096081ca 100644 --- a/docs/src/content/docs/fr/getting-started/update.mdx +++ b/docs/src/content/docs/fr/getting-started/update.mdx @@ -35,8 +35,7 @@ corrections de bugs 🐛 et des améliorations de performance ⚡. diff --git a/docs/src/content/docs/fr/index.mdx b/docs/src/content/docs/fr/index.mdx index 990cde22..27e42f44 100644 --- a/docs/src/content/docs/fr/index.mdx +++ b/docs/src/content/docs/fr/index.mdx @@ -16,11 +16,10 @@ petite taille. ## Fonctionnalités - 🌱  Libre, gratuit & open-source (Licence AGPL v3) -- 🔐  focalisé sur la souveraineté des données : votre contenu, votre - audience et vos statiistiques vous appartiennent, et à vous seulement -- 🪄 Fonctionnalités du Podcasting 2.0 : GUID, verrouillage, - transcriptions, financement, chapitres, géo-localisation, intervenants, - extraits sonores, … +- 🔐  focalisé sur la souveraineté des données : votre contenu, votre audience + et vos statiistiques vous appartiennent, et à vous seulement +- 🪄 Fonctionnalités du Podcasting 2.0 : GUID, verrouillage, transcriptions, + financement, chapitres, géo-localisation, intervenants, extraits sonores, … - 💬  Réseau social intégré : - 🚀  Castopod fait partie du Fédivers, un réseau social décentralisé - ❤️  Créer des publications, partager, mettre en favori et commenter des @@ -42,15 +41,13 @@ petite taille. - 🤝  value4value / WebMonetization - 💎  Podcasts premium - 📡 Publiez vos épisodes partout avec RSS : - - 📱  Sur tous les index et toutes les applications : Podcast Index, - Podcasts Apple, Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend… + - 📱  Sur tous les index et toutes les applications : Podcast Index, Podcasts + Apple, Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend… - ⚡ Diffusez instantanément vos épisodes avec WebSub -- 📥  Importation de Podcast : déménagez votre podcast existant vers - Castopod +- 📥  Importation de Podcast : déménagez votre podcast existant vers Castopod - 📤  Déplacez votre podcast chez un autre hébergeur - 🔀  Multi-locataire : hébergez autant de podcasts que vous le souhaitez -- 👥  Multi-utilisateur : ajoutez des contributeurs et définissez leurs - rôles +- 👥  Multi-utilisateur : ajoutez des contributeurs et définissez leurs rôles - 🌎  support de i18n : traduit en anglais, français, polonais, allemand, portugais, brésilien & espagnol… et [plus à l'avenir](https://translate.castopod.org)! @@ -74,26 +71,27 @@ Castopod a été créé dans le but de fournir une alternative ouverte et durabl pour l'hébergement de vos podcasts, de promouvoir la décentralisation et ainsi de s'assurer que la créativité des podcasteurs puisse rester libre. -Ce projet est poussé par la communauté open-source, et plus particulièrement par les mouvements -[Fediverse](https://fediverse.party/en/fediverse/) et +Ce projet est poussé par la communauté open-source, et plus particulièrement par +les mouvements [Fediverse](https://fediverse.party/en/fediverse/) et [Podcasting 2.0](https://podcastindex.org/). ## Comparaison avec d'autres solutions -Nous pensons qu'une solution n'est pas nécessairement adaptée à tous, tout dépend vos besoins. Voici donc un comparatif avec d'autres outils qui vous aidera à juger si -Castopod est fait pour vous. +Nous pensons qu'une solution n'est pas nécessairement adaptée à tous, tout +dépend vos besoins. Voici donc un comparatif avec d'autres outils qui vous +aidera à juger si Castopod est fait pour vous. ### Castopod vs Wordpress -Castopod est souvent appelé "le Wordpress pour les podcasts" en raison des points -communs entre les deux. D'une certaine manière, c'est vrai. Castopod a +Castopod est souvent appelé "le Wordpress pour les podcasts" en raison des +points communs entre les deux. D'une certaine manière, c'est vrai. Castopod a d'ailleurs été inspiré par l'écosystème de Wordpress, en raison de la facilité grâce à laquelle sa communauté l'a adopté et du nombre de sites Web qui l'utilisent. -Tout comme Wordpress, Castopod est libre & open source, utilise PHP et une -base de données MySQL et est fourni en paquet facilement installable sur la -plupart des serveurs web. +Tout comme Wordpress, Castopod est libre & open source, utilise PHP et une base +de données MySQL et est fourni en paquet facilement installable sur la plupart +des serveurs web. Wordpress est un excellent outil pour créer votre site web et de l'étendre avec des plugins pour faire ce que vous souhaitez. C'est un CMS à part entière qui @@ -156,18 +154,24 @@ documentation suivante pour démarrer. Castopod a mis en place un Code de Conduite destiné aux personnes souhaitant participer au projet. Merci de lire le -[manuel de code de conduite](https://code.castopod.org/adaures/castopod/-/blob/develop/CODE_OF_CONDUCT.md) pour comprendre les actions qui seront tolérées et celles qui ne le seront pas. +[manuel de code de conduite](https://code.castopod.org/adaures/castopod/-/blob/develop/CODE_OF_CONDUCT.md) +pour comprendre les actions qui seront tolérées et celles qui ne le seront pas. ### Guide de contribution -Lisez notre [guide de contribution](https://code.castopod.org/adaures/castopod/-/blob/develop/CONTRIBUTING.md) pour comprendre notre processus de développement, comment proposer des corrections de bug et améliorations, et comment coder et tester vos changements sur Castopod. +Lisez notre +[guide de contribution](https://code.castopod.org/adaures/castopod/-/blob/develop/CONTRIBUTING.md) +pour comprendre notre processus de développement, comment proposer des +corrections de bug et améliorations, et comment coder et tester vos changements +sur Castopod. ## Contact -Vous pouvez nous joindre pour obtenir de l'aide ou poser toute question que vous avez sur : +Vous pouvez nous joindre pour obtenir de l'aide ou poser toute question que vous +avez sur : -- [Discord](https://castopod.org/discord) (pour l'interaction directe avec les développeurs - et la communauté) +- [Discord](https://castopod.org/discord) (pour l'interaction directe avec les + développeurs et la communauté) - [Suivi de problèmes](https://code.castopod.org/adaures/castopod/-/issues) (pour toute demande de nouvelle fonctionnalité ou rapport de bug) @@ -181,7 +185,8 @@ informations à propos de Castopod : ## Partenaires -Le développement de Castopod est rendu possible avec le soutien de ses donateurs. Si vous souhaitez aider, pensez à +Le développement de Castopod est rendu possible avec le soutien de ses +donateurs. Si vous souhaitez aider, pensez à [sponsoriser le développement de Castopod](https://opencollective.com/castopod/contribute). [![Ad Aures Logo](../../../assets/images/sponsors/adaures.svg)](https://adaures.com/) diff --git a/docs/src/content/docs/fr2/getting-started/auth.mdx b/docs/src/content/docs/fr2/getting-started/auth.mdx index 70eda255..75e300b4 100644 --- a/docs/src/content/docs/fr2/getting-started/auth.mdx +++ b/docs/src/content/docs/fr2/getting-started/auth.mdx @@ -13,24 +13,25 @@ autorisations sont définis sur deux niveaux : ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Autorisations dans l'instance -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -39,13 +40,13 @@ autorisations sont définis sur deux niveaux : | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Rôles et autorisations par podcast ### Rôles par podcast -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -54,11 +55,11 @@ autorisations sont définis sur deux niveaux : | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Permissions par podcast -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -82,4 +83,4 @@ autorisations sont définis sur deux niveaux : | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/fr2/getting-started/docker.mdx b/docs/src/content/docs/fr2/getting-started/docker.mdx index fdaaf71c..22270b6a 100644 --- a/docs/src/content/docs/fr2/getting-started/docker.mdx +++ b/docs/src/content/docs/fr2/getting-started/docker.mdx @@ -92,8 +92,8 @@ de données Redis peut être ajoutée en tant que gestionnaire de cache. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/fr2/getting-started/install.mdx b/docs/src/content/docs/fr2/getting-started/install.mdx index 540dffe4..e5ec96c0 100644 --- a/docs/src/content/docs/fr2/getting-started/install.mdx +++ b/docs/src/content/docs/fr2/getting-started/install.mdx @@ -10,15 +10,15 @@ serveurs web compatibles avec PHP-MySQL. ## Prérequis -- PHP v8.1 or higher -- MySQL version 5.7 ou supérieure ou MariaDB version 10.2 ou supérieure +- PHP v8.5 or higher +- MySQL version 8.4 ou supérieure ou MariaDB version 11.4 ou supérieure - Prise en charge HTTPS - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://www.php.net/manual/fr/intl.requirements.php) - [libcurl](https://www.php.net/manual/fr/curl.requirements.php) diff --git a/docs/src/content/docs/fr2/getting-started/update.mdx b/docs/src/content/docs/fr2/getting-started/update.mdx index b2325f44..257593fe 100644 --- a/docs/src/content/docs/fr2/getting-started/update.mdx +++ b/docs/src/content/docs/fr2/getting-started/update.mdx @@ -34,8 +34,7 @@ corrections de bugs 🐛 et des améliorations de performance ⚡. diff --git a/docs/src/content/docs/fr2/index.mdx b/docs/src/content/docs/fr2/index.mdx index c3dc55e9..e66a8456 100644 --- a/docs/src/content/docs/fr2/index.mdx +++ b/docs/src/content/docs/fr2/index.mdx @@ -16,11 +16,10 @@ petite taille. ## Fonctionnalités - 🌱  Libre, gratuit & open-source (Licence AGPL v3) -- 🔐  focalisé sur la souveraineté des données : votre contenu, votre - audience et vos statiistiques vous appartiennent, et à vous seulement -- 🪄 Fonctionnalités du Podcasting 2.0 : GUID, verrouillage, - transcriptions, financement, chapitres, géo-localisation, intervenants, - extraits sonores, … +- 🔐  focalisé sur la souveraineté des données : votre contenu, votre audience + et vos statiistiques vous appartiennent, et à vous seulement +- 🪄 Fonctionnalités du Podcasting 2.0 : GUID, verrouillage, transcriptions, + financement, chapitres, géo-localisation, intervenants, extraits sonores, … - 💬  Réseau social intégré : - 🚀  Castopod fait partie du Fédivers, un réseau social décentralisé - ❤️  Créer des publications, partager, mettre en favori et commenter des @@ -42,15 +41,13 @@ petite taille. - 🤝  value4value / WebMonetization - 💎  Podcasts premium - 📡 Publiez vos épisodes partout avec RSS : - - 📱  Sur tous les index et toutes les applications : Podcast Index, - Podcasts Apple, Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend… + - 📱  Sur tous les index et toutes les applications : Podcast Index, Podcasts + Apple, Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend… - ⚡ Diffusez instantanément vos épisodes avec WebSub -- 📥  Importation de Podcast : déménagez votre podcast existant vers - Castopod +- 📥  Importation de Podcast : déménagez votre podcast existant vers Castopod - 📤  Déplacez votre podcast chez un autre hébergeur - 🔀  Multi-locataire : hébergez autant de podcasts que vous le souhaitez -- 👥  Multi-utilisateur : ajoutez des contributeurs et définissez leurs - rôles +- 👥  Multi-utilisateur : ajoutez des contributeurs et définissez leurs rôles - 🌎  support de i18n : traduit en anglais, français, polonais, allemand, portugais, brésilien & espagnol… et [plus à l'avenir](https://translate.castopod.org)! diff --git a/docs/src/content/docs/gd/getting-started/auth.mdx b/docs/src/content/docs/gd/getting-started/auth.mdx index 2c2a15d7..597c408f 100644 --- a/docs/src/content/docs/gd/getting-started/auth.mdx +++ b/docs/src/content/docs/gd/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| --------------- | --------------------------------------- | ------------------------------------------------------------------------------------------ | -| Sàr-rianaire | Smachd gu lèir air Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manaidsear | Stiùireadh susbaint Chastopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Pod-chraoladair | Luchd-cleachdaidh coitcheann Chastopod. | admin.access | +| role | description | permissions | +| --------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Sàr-rianaire | Smachd gu lèir air Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manaidsear | Stiùireadh susbaint Chastopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Pod-chraoladair | Luchd-cleachdaidh coitcheann Chastopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | --------------------------------------------------------------------------------------------- | | admin.access | ’S urrainn dhaibh raon rianachd Chastopod inntrigeadh. | | admin.settings | ’S urrainn dhaibh roghainnean Chastopod inntrigeadh. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | ’S urrainn dhaibh luchdc-leachdaidh Chastopod a stiùireadh. | | persons.manage | ’S urrainn dhaibh daoine a stiùireadh. | | pages.manage | ’S urrainn dhaibh duilleagan a stiùireadh. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | ’S urrainn dhaibh pod-chraolaidhean ion-phortadh. | | fediverse.manage-blocks | ’S urrainn dhaibh actairean/àrainnean a cho-shaoghail a bhacadh o eadar-ghabhail le Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | --------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Ùghdar | A’ stiùireadh susbaint a’ phod-chraolaidh #\{id\} ach gun chomas foillseachaidh. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Aoigh | Neach-cuideachaidh a’ phod-chraolaidh #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | ’S urrainn dhaibh eapasodan is postaichean a’ phod-chraolaidh #\{id\} fhoillseachadh/neo-fhoillseachadh. | | episodes.manage-comments | ’S urrainn dhaibh beachdan air eapasod a’ phod-chraolaidh #\{id\} a chruthachadh/a thoirt air falbh. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/gd/getting-started/docker.mdx b/docs/src/content/docs/gd/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/gd/getting-started/docker.mdx +++ b/docs/src/content/docs/gd/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/gd/getting-started/install.mdx b/docs/src/content/docs/gd/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/gd/getting-started/install.mdx +++ b/docs/src/content/docs/gd/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/gd/index.mdx b/docs/src/content/docs/gd/index.mdx index 2f23ff9b..4432a5d8 100644 --- a/docs/src/content/docs/gd/index.mdx +++ b/docs/src/content/docs/gd/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/gl/getting-started/auth.mdx b/docs/src/content/docs/gl/getting-started/auth.mdx index d5b3011d..1ae56fa0 100644 --- a/docs/src/content/docs/gl/getting-started/auth.mdx +++ b/docs/src/content/docs/gl/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ------------------------------------ | ------------------------------------------------------------------------------------------ | -| Super Admin | Ten control completo sobre Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Xestora | Quen xestiona o contido de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Usuaria común de Castopod. | admin.access | +| role | description | permissions | +| ----------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------ | +| Super Admin | Ten control completo sobre Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Xestora | Quen xestiona o contido de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Usuaria común de Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------------------ | | admin.access | Pode acceder á área de administración. | | admin.settings | Pode acceder aos axustes de Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Pode xestionar as usuarias de Castopod. | | persons.manage | Pode xestionar persoas. | | pages.manage | Pode xestionar páxinas. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Pode importar podcasts. | | fediverse.manage-blocks | Pode bloquear actores/dominios do fediverso evitando interactuar con Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | --------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Autora | Persoa que xestiona o contido do podcast #\{id\} pero non pode publicalo. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Convidada | Contribuínte básico ao podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | ----------------------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Pode publicar/retirar episodios e publicacións do podcast #\{id\}. | | episodes.manage-comments | Pode crear/eliminar comentarios dos episodios do podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/gl/getting-started/docker.mdx b/docs/src/content/docs/gl/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/gl/getting-started/docker.mdx +++ b/docs/src/content/docs/gl/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/gl/getting-started/install.mdx b/docs/src/content/docs/gl/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/gl/getting-started/install.mdx +++ b/docs/src/content/docs/gl/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/gl/index.mdx b/docs/src/content/docs/gl/index.mdx index 2f23ff9b..4432a5d8 100644 --- a/docs/src/content/docs/gl/index.mdx +++ b/docs/src/content/docs/gl/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/id/getting-started/auth.mdx b/docs/src/content/docs/id/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/id/getting-started/auth.mdx +++ b/docs/src/content/docs/id/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/id/getting-started/docker.mdx b/docs/src/content/docs/id/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/id/getting-started/docker.mdx +++ b/docs/src/content/docs/id/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/id/getting-started/install.mdx b/docs/src/content/docs/id/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/id/getting-started/install.mdx +++ b/docs/src/content/docs/id/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/id/index.mdx b/docs/src/content/docs/id/index.mdx index 2f8e181f..1fee5963 100644 --- a/docs/src/content/docs/id/index.mdx +++ b/docs/src/content/docs/id/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Fitur-fitur - 🌱  Gratis & sumber terbuka (Lisensi AGPL v3) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Jaringan sosial bawaan: - 🚀  Castopod termasuk bagian dari Fediverse, jaringan sosial terdesentralisasi @@ -41,15 +41,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Terbitkan episode-episode Anda dimana pun dengan RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivasi diff --git a/docs/src/content/docs/it/getting-started/auth.mdx b/docs/src/content/docs/it/getting-started/auth.mdx index 75922532..bec98a0a 100644 --- a/docs/src/content/docs/it/getting-started/auth.mdx +++ b/docs/src/content/docs/it/getting-started/auth.mdx @@ -13,24 +13,25 @@ definiti a due livelli: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Permessi istanza -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -39,13 +40,13 @@ definiti a due livelli: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per i ruoli e le autorizzazioni del podcast ### Per i ruoli del podcast -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -54,11 +55,11 @@ definiti a due livelli: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Autorizzazioni per podcast -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -82,4 +83,4 @@ definiti a due livelli: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/it/getting-started/docker.mdx b/docs/src/content/docs/it/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/it/getting-started/docker.mdx +++ b/docs/src/content/docs/it/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/it/getting-started/install.mdx b/docs/src/content/docs/it/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/it/getting-started/install.mdx +++ b/docs/src/content/docs/it/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/it/index.mdx b/docs/src/content/docs/it/index.mdx index 18f84407..55bd5e89 100644 --- a/docs/src/content/docs/it/index.mdx +++ b/docs/src/content/docs/it/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/ja/getting-started/auth.mdx b/docs/src/content/docs/ja/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/ja/getting-started/auth.mdx +++ b/docs/src/content/docs/ja/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/ja/getting-started/docker.mdx b/docs/src/content/docs/ja/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/ja/getting-started/docker.mdx +++ b/docs/src/content/docs/ja/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/ja/getting-started/install.mdx b/docs/src/content/docs/ja/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/ja/getting-started/install.mdx +++ b/docs/src/content/docs/ja/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/ja/index.mdx b/docs/src/content/docs/ja/index.mdx index 18f84407..55bd5e89 100644 --- a/docs/src/content/docs/ja/index.mdx +++ b/docs/src/content/docs/ja/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/kk/getting-started/auth.mdx b/docs/src/content/docs/kk/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/kk/getting-started/auth.mdx +++ b/docs/src/content/docs/kk/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/kk/getting-started/docker.mdx b/docs/src/content/docs/kk/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/kk/getting-started/docker.mdx +++ b/docs/src/content/docs/kk/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/kk/getting-started/install.mdx b/docs/src/content/docs/kk/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/kk/getting-started/install.mdx +++ b/docs/src/content/docs/kk/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/kk/index.mdx b/docs/src/content/docs/kk/index.mdx index 2f23ff9b..4432a5d8 100644 --- a/docs/src/content/docs/kk/index.mdx +++ b/docs/src/content/docs/kk/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/ko/getting-started/auth.mdx b/docs/src/content/docs/ko/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/ko/getting-started/auth.mdx +++ b/docs/src/content/docs/ko/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/ko/getting-started/docker.mdx b/docs/src/content/docs/ko/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/ko/getting-started/docker.mdx +++ b/docs/src/content/docs/ko/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/ko/getting-started/install.mdx b/docs/src/content/docs/ko/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/ko/getting-started/install.mdx +++ b/docs/src/content/docs/ko/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/ko/index.mdx b/docs/src/content/docs/ko/index.mdx index 18f84407..55bd5e89 100644 --- a/docs/src/content/docs/ko/index.mdx +++ b/docs/src/content/docs/ko/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/nl/getting-started/auth.mdx b/docs/src/content/docs/nl/getting-started/auth.mdx index 3f70d363..dfe4af8c 100644 --- a/docs/src/content/docs/nl/getting-started/auth.mdx +++ b/docs/src/content/docs/nl/getting-started/auth.mdx @@ -13,24 +13,25 @@ niveaus: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| --------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------ | -| Super beheerder | Heeft de volledige controle over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Beheerder | Beheert de inhoud van Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Algemene gebruikers van Castopod. | admin.access | +| role | description | permissions | +| --------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------ | +| Super beheerder | Heeft de volledige controle over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Beheerder | Beheert de inhoud van Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Algemene gebruikers van Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | -------------------------------------------------------------------- | | admin.access | Kan toegang krijgen tot de beheeromgeving van Castopod. | | admin.settings | Kan toegang krijgen tot de instellingen van Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Kan Castopod-gebruikers beheren. | | persons.manage | Kan personen beheren. | | pages.manage | Kan pagina's beheren. | @@ -39,13 +40,13 @@ niveaus: | podcasts.import | Kan podcasts importeren. | | fediverse.manage-blocks | Kan fediverse actors/domains blokkeren voor interactie met Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast rollen en permissies ### Per podcast rollen -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | --------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -54,11 +55,11 @@ niveaus: | Auteur | Beheert de inhoud van podcast #\{id\} maar kan deze niet publiceren. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Gast | Algemene bijdrager van podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | ---------------------------------------------------------------------------------------- | @@ -82,4 +83,4 @@ niveaus: | episodes.manage-publications | Kan afleveringen en berichten van podcast #\{id\} publiceren/depubliceren. | | episodes.manage-comments | Kan opmerkingen van aflevering van podcast van #\{id\} maken of verwijderen. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/nl/getting-started/docker.mdx b/docs/src/content/docs/nl/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/nl/getting-started/docker.mdx +++ b/docs/src/content/docs/nl/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/nl/getting-started/install.mdx b/docs/src/content/docs/nl/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/nl/getting-started/install.mdx +++ b/docs/src/content/docs/nl/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/nl/index.mdx b/docs/src/content/docs/nl/index.mdx index 18f84407..55bd5e89 100644 --- a/docs/src/content/docs/nl/index.mdx +++ b/docs/src/content/docs/nl/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/nn-no/getting-started/auth.mdx b/docs/src/content/docs/nn-no/getting-started/auth.mdx index 50bb8f4d..71f81937 100644 --- a/docs/src/content/docs/nn-no/getting-started/auth.mdx +++ b/docs/src/content/docs/nn-no/getting-started/auth.mdx @@ -2,7 +2,8 @@ title: Godkjenning & Autorisasjon --- -Denne teksten blir sett inn i ein \-knagg. Roller og tilgangar blir laga på to nivå: +Denne teksten blir sett inn i ein \-knagg. Roller +og tilgangar blir laga på to nivå: 1. [for heile nettstaden](#1-instance-wide-roles-and-permissions) 2. [for kvar podkast](#2-per-podcast-roles-and-permissions) @@ -11,24 +12,25 @@ Denne teksten blir sett inn i ein \-knagg. Roller ### Roller på nettstaden -{/_ AUTH-INSTANCE-ROLES-LIST:START - Ikkje slett eller endre denne delen _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Ikkje slett eller endre denne delen */} -| rolle | skildring | tilgangar | -| ----------- | -------------------------------- | ------------------------------------------------------------------------------------------ | -| Superstyrar | Har full kontroll over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Leiar | Styrer innhaldet på Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podkastar | Vanlege Castopod-brukarar. | admin.access | +| role | description | permissions | +| ----------- | -------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Superstyrar | Har full kontroll over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Leiar | Styrer innhaldet på Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podkastar | Vanlege Castopod-brukarar. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Løyve på nettstaden -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ---------------------------------------------------------------------- | | admin.access | Kan bruka styringspanelet for Castopod. | | admin.settings | Kan få tilgang til innstillingane for Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Kan handtera Castopod-brukarar. | | persons.manage | Kan handtera folk. | | pages.manage | Kan handtera sider. | @@ -37,13 +39,13 @@ Denne teksten blir sett inn i ein \-knagg. Roller | podcasts.import | Kan importera podkastar. | | fediverse.manage-blocks | Kan blokkera folk og domene på allheimen frå å samhandla med Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Roller pr. podkast -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -52,11 +54,11 @@ Denne teksten blir sett inn i ein \-knagg. Roller | Skapar | Styrer innhald for podkasten #\{id\}, men kan ikkje publisera dei. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Gjest | Vanleg bidragsytar til podkasten #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Løyve pr. podkast -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -80,4 +82,4 @@ Denne teksten blir sett inn i ein \-knagg. Roller | episodes.manage-publications | Kan publisera og avpublisera episodar og innlegg for podkasten #\{id\}. | | episodes.manage-comments | Kan skriva og sletta kommentarar til episodane av podkasten #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/nn-no/getting-started/docker.mdx b/docs/src/content/docs/nn-no/getting-started/docker.mdx index e557943e..01174941 100644 --- a/docs/src/content/docs/nn-no/getting-started/docker.mdx +++ b/docs/src/content/docs/nn-no/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Set opp ein revers-mellomlagertenar for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/nn-no/getting-started/install.mdx b/docs/src/content/docs/nn-no/getting-started/install.mdx index ab4b1f24..82bc2dba 100644 --- a/docs/src/content/docs/nn-no/getting-started/install.mdx +++ b/docs/src/content/docs/nn-no/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/nn-no/index.mdx b/docs/src/content/docs/nn-no/index.mdx index eb13e2d1..d46c9abf 100644 --- a/docs/src/content/docs/nn-no/index.mdx +++ b/docs/src/content/docs/nn-no/index.mdx @@ -17,13 +17,13 @@ systemkrav. ## Funksjonar - 🌱  Fri og open (AGPL v3-lisens) -- 🔐  Legg vekt på at du eig dine eigne data: innhaldet, publikum og - analysedata høyrer deg til, og berre deg -- 🪄  Podkasting 2.0-funksjonar: GUID, låsing, transkripsjonar, - finansiering, kapittel, plassering, personar, lydbetar, … +- 🔐  Legg vekt på at du eig dine eigne data: innhaldet, publikum og analysedata + høyrer deg til, og berre deg +- 🪄  Podkasting 2.0-funksjonar: GUID, låsing, transkripsjonar, finansiering, + kapittel, plassering, personar, lydbetar, … - 💬  Innebygd sosialt nettverk: - - 🚀  Castopod er ein del av fødiverset, som er eit desentralisert - sosialt nettverk + - 🚀  Castopod er ein del av fødiverset, som er eit desentralisert sosialt + nettverk - ❤️  Skriv innlegg, del dei, favorittmerk dei, og kommenter episodane - 📈  Innebygde analyseverkty: - ⚖️  Fylgjer GDPR / CCPA / LGPD @@ -35,8 +35,7 @@ systemkrav. - 🎨  Bruk eigne fargar - 🎬  Lag og del filmklypp frå episodane - 🔉  Lag lydbetar - - ▶️  Innbyggbar spelar, så du kan spela episodane dine på ein kvar - nettstad + - ▶️  Innbyggbar spelar, så du kan spela episodane dine på ein kvar nettstad - 💸  Ten pengar: - 🔗  Donasjonslenker - 📲  lytt-for-å-klikka-annonsar @@ -50,8 +49,8 @@ systemkrav. - 📤  Flytt podkasten din bort frå Castopod - 🔀  For fleire: Ver vertskap for så mange podkastar du vil - 👥  Fleirbrukar: legg til bidragsytarar og lag roller for dei -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Kvifor Castopod? diff --git a/docs/src/content/docs/oc/getting-started/auth.mdx b/docs/src/content/docs/oc/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/oc/getting-started/auth.mdx +++ b/docs/src/content/docs/oc/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/oc/getting-started/docker.mdx b/docs/src/content/docs/oc/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/oc/getting-started/docker.mdx +++ b/docs/src/content/docs/oc/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/oc/getting-started/install.mdx b/docs/src/content/docs/oc/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/oc/getting-started/install.mdx +++ b/docs/src/content/docs/oc/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/oc/index.mdx b/docs/src/content/docs/oc/index.mdx index a2747fe8..dc77399c 100644 --- a/docs/src/content/docs/oc/index.mdx +++ b/docs/src/content/docs/oc/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/pl/getting-started/auth.mdx b/docs/src/content/docs/pl/getting-started/auth.mdx index 180ea476..831596ba 100644 --- a/docs/src/content/docs/pl/getting-started/auth.mdx +++ b/docs/src/content/docs/pl/getting-started/auth.mdx @@ -12,73 +12,74 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Superadministrator | Ma pełną kontrolę nad Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Zarządza zawartością Castopoda. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Zwykli użytkownicy Castopoda. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} -| permission | description | -| ----------------------- | ------------------------------------------------------------------ | -| admin.access | Can access the Castopod admin area. | -| admin.settings | Can access the Castopod settings. | -| users.manage | Can manage Castopod users. | -| persons.manage | Can manage persons. | -| pages.manage | Can manage pages. | -| podcasts.view | Can view all podcasts. | -| podcasts.create | Can create new podcasts. | -| podcasts.import | Can import podcasts. | -| fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | +| permission | description | +| ----------------------- | --------------------------------------------------------------------------- | +| admin.access | Ma dostęp do panelu administracyjnego Castopoda. | +| admin.settings | Ma dostęp do ustawień Castopoda. | +| plugins.manage | Auth.instance_permissions.plugins.manage | +| users.manage | Może zarządzać użytkownikami Castopoda. | +| persons.manage | Może zarządzać osobami. | +| pages.manage | Może zarządzać stronami. | +| podcasts.view | Może wyświetlać wszystkie podcasty. | +| podcasts.create | Może tworzyć nowe podcasty. | +| podcasts.import | Może importować podcasty. | +| fediverse.manage-blocks | Można blokować aktorów/domeny z fediwersum przed interakcjami z Castopodem. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Admin | Has complete control of podcast #\{id\}. | \* | -| Editor | Manages content and publications of podcast #\{id\}. | view, edit, manage-import, manage-persons, manage-platforms, manage-publications, manage-notifications, interact-as, episodes.view, episodes.create, episodes.edit, episodes.delete, episodes.manage-persons, episodes.manage-clips, episodes.manage-publications, episodes.manage-comments | -| Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | -| Guest | General contributor of the podcast #\{id\}. | view, episodes.view | +| role | description | permissions | +| ------ | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Admin | Ma pełną kontrolę nad podcastem #\{id\}. | \* | +| Edytor | Zarządza treścią i publikacjami podcastu #\{id\}. | view, edit, manage-import, manage-persons, manage-platforms, manage-publications, manage-notifications, interact-as, episodes.view, episodes.create, episodes.edit, episodes.delete, episodes.manage-persons, episodes.manage-clips, episodes.manage-publications, episodes.manage-comments | +| Autor | Zarządza zawartością podcastu #\{id\}, ale nie może jej opublikować. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | +| Gość | Główny współtwórca podcastu #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} -| permission | description | -| ---------------------------- | -------------------------------------------------------------------------- | -| view | Can view dashboard and analytics of podcast #\{id\}. | -| edit | Can edit podcast #\{id\}. | -| delete | Can delete podcast #\{id\}. | -| manage-import | Can synchronize imported podcast #\{id\}. | -| manage-persons | Can manage subscriptions of podcast #\{id\}. | -| manage-subscriptions | Can manage subscriptions of podcast #\{id\}. | -| manage-contributors | Can manage contributors of podcast #\{id\}. | -| manage-platforms | Can set/remove platform links of podcast #\{id\}. | -| manage-publications | Can publish podcast #\{id\}. | -| manage-notifications | Can view and mark notifications as read for podcast #\{id\}. | -| interact-as | Can interact as the podcast #\{id\} to favourite, share or reply to posts. | -| episodes.view | Can view dashboards and analytics of podcast #\{id\}'s episodes. | -| episodes.create | Can create episodes for podcast #\{id\}. | -| episodes.edit | Can edit episodes of podcast #\{id\}. | -| episodes.delete | Can delete episodes of podcast #\{id\}. | -| episodes.manage-persons | Can manage episode persons of podcast #\{id\}. | -| episodes.manage-clips | Can manage video clips or soundbites of podcast #\{id\}. | -| episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | -| episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | +| permission | description | +| ---------------------------- | ------------------------------------------------------------------------------------- | +| view | Może wyświetlań panel zarządzania oraz analitykę podcastu #\{id\}. | +| edit | Może edytować podcast #\{id\}. | +| delete | Może usunąć podcast #\{id\}. | +| manage-import | Może synchronizować importowany podcast #\{id\}. | +| manage-persons | Może zarządzać subskrypcjami podcastu #\{id\}. | +| manage-subscriptions | Może zarządzać subskrypcjami podcastu #\{id\}. | +| manage-contributors | Może zarządzać współtwórcami podcastu #\{id\}. | +| manage-platforms | Można ustawić/usunąć linki platformy podcastu #\{id\}. | +| manage-publications | Może publikować podcast #\{id\}. | +| manage-notifications | Może wyświetlać i oznaczać powiadomienia jako przeczytane dla podcastu #\{id\}. | +| interact-as | Może jako podcast #\{id\} dodawać do ulubionych, udostępniać lub odpowiadać na posty. | +| episodes.view | Może wyświetlać panel zarządzania oraz analitykę odcinków podcastu #\{id\}. | +| episodes.create | Może tworzyć odcinki dla podcastu #\{id\}. | +| episodes.edit | Może edytować odcinki podcastu #\{id\}. | +| episodes.delete | Można usuwać odcinki podcastu #\{id\}. | +| episodes.manage-persons | Może zarządzać osobami w odcinkach podcastu #\{id\}. | +| episodes.manage-clips | Może zarządzać klipami wideo lub zajawkami podcastu #\{id\}. | +| episodes.manage-publications | Może publikować/cofać publikowanie odcinków i postów podcastu #\{id\}. | +| episodes.manage-comments | Może tworzyć/usuwać komentarze odcinka podcastu #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/pl/getting-started/docker.mdx b/docs/src/content/docs/pl/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/pl/getting-started/docker.mdx +++ b/docs/src/content/docs/pl/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/pl/getting-started/install.mdx b/docs/src/content/docs/pl/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/pl/getting-started/install.mdx +++ b/docs/src/content/docs/pl/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/pl/index.mdx b/docs/src/content/docs/pl/index.mdx index 18f84407..55bd5e89 100644 --- a/docs/src/content/docs/pl/index.mdx +++ b/docs/src/content/docs/pl/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/pt-br/getting-started/auth.mdx b/docs/src/content/docs/pt-br/getting-started/auth.mdx index a3b06900..ddb244c5 100644 --- a/docs/src/content/docs/pt-br/getting-started/auth.mdx +++ b/docs/src/content/docs/pt-br/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------ | -| Super administrador | Tem controle completo sobre Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Gerente | Gerencia o conteúdo de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Usuários gerais do Castopod. | admin.access | +| role | description | permissions | +| ------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super administrador | Tem controle completo sobre Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Gerente | Gerencia o conteúdo de Castopod. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Usuários gerais do Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ---------------------------------------------------------------- | | admin.access | Pode acessar a área de administração do Castopod. | | admin.settings | Pode acessar as configurações de Castopod. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Pode gerenciar usuários do Castopod. | | persons.manage | Pode gerenciar pessoas. | | pages.manage | Pode gerenciar páginas. | @@ -38,26 +39,26 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Pode importar podcasts. | | fediverse.manage-blocks | Pode bloquear ator/domínios distintos de interagir com Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Administrador | Tem controle completo do podcast #\{id\}. | \* | | Editor | Gerencia o conteúdo e as publicações do podcast #\{id\}. | view, edit, manage-import, manage-persons, manage-platforms, manage-publications, manage-notifications, interact-as, episodes.view, episodes.create, episodes.edit, episodes.delete, episodes.manage-persons, episodes.manage-clips, episodes.manage-publications, episodes.manage-comments | | Autor | Gerencia o conteúdo do podcast #\{id\} mas não pode publicá-los. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | -| Guest | Contribuidor geral do podcast #\{id\}. | view, episodes.view | +| Convidado | Contribuidor geral do podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | --------------------------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Pode publicar/remover a publicação de episódios e postagens de podcast #\{id\}. | | episodes.manage-comments | Pode criar/remover comentários de episódio do podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/pt-br/getting-started/docker.mdx b/docs/src/content/docs/pt-br/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/pt-br/getting-started/docker.mdx +++ b/docs/src/content/docs/pt-br/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/pt-br/getting-started/install.mdx b/docs/src/content/docs/pt-br/getting-started/install.mdx index 3b655c42..d9b3c8ce 100644 --- a/docs/src/content/docs/pt-br/getting-started/install.mdx +++ b/docs/src/content/docs/pt-br/getting-started/install.mdx @@ -10,15 +10,15 @@ com PHP-MySQL. ## Requisitos -- PHP v8.1 or higher -- MySQL versão 5.7 ou superior ou MariaDB versão 10.2 ou superior +- PHP v8.5 or higher +- MySQL versão 8.4 ou superior ou MariaDB versão 11.4 ou superior - Suporte a HTTPS - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/pt-br/index.mdx b/docs/src/content/docs/pt-br/index.mdx index dd789a8c..441f0831 100644 --- a/docs/src/content/docs/pt-br/index.mdx +++ b/docs/src/content/docs/pt-br/index.mdx @@ -16,10 +16,10 @@ pegada muito pequena. ## Funcionalidades - 🌱  Livre & de código aberto (Licença AGPL v3) -- 🔐  Focado na soberania de dados: seu conteúdo, público e análises - pertence a você, e somente você -- 🪄  Recursos do podcasting 2.0: GUID, bloqueado, transcrições, - financiamento, capítulos, localização, pessoas, soundbites, … +- 🔐  Focado na soberania de dados: seu conteúdo, público e análises pertence a + você, e somente você +- 🪄  Recursos do podcasting 2.0: GUID, bloqueado, transcrições, financiamento, + capítulos, localização, pessoas, soundbites, … - 💬  Rede social integrada: - 🚀  Castopod é parte do Fediverso, uma rede social descentralizada - ❤️  Crie publicações, compartilhe, favorite e comente em episódios @@ -31,8 +31,7 @@ pegada muito pequena. - ✅  Pronto para SEO (meta-tags de open-graph, JSON-LD, …) - 📱  PWA: instalar como um aplicativo autônomo - 🎨  Cores de tema personalizáveis - - 🎬  Gere clipes de vídeo prontos para compartilhar a partir dos - episódios + - 🎬  Gere clipes de vídeo prontos para compartilhar a partir dos episódios - 🔉  Gere clipes de áudio - ▶️  Player incorporável, incorpore seus episódios em qualquer site - 💸  Monetização: @@ -41,16 +40,15 @@ pegada muito pequena. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publique seus episódios em qualquer lugar com RSS: - - 📱  Em todos os agregadores e aplicativos: Podcast Index, Apple - Podcasts, Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  Em todos os agregadores e aplicativos: Podcast Index, Apple Podcasts, + Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Transmita seus episódios instantaneamente com WebSub - 📥  Importação de Podcast: mova seu podcast existente para o Castopod - 📤  Mova seu podcast para fora do Castopod - 🔀  Multi-inquilino: hospede quantos podcasts você quiser - 👥  Multi-usuário: adicione contribuidores e defina cargos -- 🌎  Suporte i18n: traduzido em inglês, francês, polonês, alemão, - português brasileiro e espanhol… com - [mais por vir](https://translate.castopod.org)! +- 🌎  Suporte i18n: traduzido em inglês, francês, polonês, alemão, português + brasileiro e espanhol… com [mais por vir](https://translate.castopod.org)! ## Motivação diff --git a/docs/src/content/docs/pt/getting-started/auth.mdx b/docs/src/content/docs/pt/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/pt/getting-started/auth.mdx +++ b/docs/src/content/docs/pt/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/pt/getting-started/docker.mdx b/docs/src/content/docs/pt/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/pt/getting-started/docker.mdx +++ b/docs/src/content/docs/pt/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/pt/getting-started/install.mdx b/docs/src/content/docs/pt/getting-started/install.mdx index 260a0ca4..2673ffbf 100644 --- a/docs/src/content/docs/pt/getting-started/install.mdx +++ b/docs/src/content/docs/pt/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/pt/index.mdx b/docs/src/content/docs/pt/index.mdx index 18f84407..55bd5e89 100644 --- a/docs/src/content/docs/pt/index.mdx +++ b/docs/src/content/docs/pt/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/ro/getting-started/auth.mdx b/docs/src/content/docs/ro/getting-started/auth.mdx index 47c1e349..904c8e56 100644 --- a/docs/src/content/docs/ro/getting-started/auth.mdx +++ b/docs/src/content/docs/ro/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Rolurile instanței -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Permisiuni instanță -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Roluri per podcast -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Permisiuni per podcast -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/ro/getting-started/docker.mdx b/docs/src/content/docs/ro/getting-started/docker.mdx index 23037068..167419bf 100644 --- a/docs/src/content/docs/ro/getting-started/docker.mdx +++ b/docs/src/content/docs/ro/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/ro/getting-started/install.mdx b/docs/src/content/docs/ro/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/ro/getting-started/install.mdx +++ b/docs/src/content/docs/ro/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/ro/index.mdx b/docs/src/content/docs/ro/index.mdx index a2747fe8..dc77399c 100644 --- a/docs/src/content/docs/ro/index.mdx +++ b/docs/src/content/docs/ro/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/ru/getting-started/auth.mdx b/docs/src/content/docs/ru/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/ru/getting-started/auth.mdx +++ b/docs/src/content/docs/ru/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/ru/getting-started/docker.mdx b/docs/src/content/docs/ru/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/ru/getting-started/docker.mdx +++ b/docs/src/content/docs/ru/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/ru/getting-started/install.mdx b/docs/src/content/docs/ru/getting-started/install.mdx index 260a0ca4..2673ffbf 100644 --- a/docs/src/content/docs/ru/getting-started/install.mdx +++ b/docs/src/content/docs/ru/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/ru/index.mdx b/docs/src/content/docs/ru/index.mdx index 18f84407..55bd5e89 100644 --- a/docs/src/content/docs/ru/index.mdx +++ b/docs/src/content/docs/ru/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/sk/getting-started/auth.mdx b/docs/src/content/docs/sk/getting-started/auth.mdx index fa888b39..8282b972 100644 --- a/docs/src/content/docs/sk/getting-started/auth.mdx +++ b/docs/src/content/docs/sk/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/sk/getting-started/docker.mdx b/docs/src/content/docs/sk/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/sk/getting-started/docker.mdx +++ b/docs/src/content/docs/sk/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/sk/getting-started/install.mdx b/docs/src/content/docs/sk/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/sk/getting-started/install.mdx +++ b/docs/src/content/docs/sk/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/sk/index.mdx b/docs/src/content/docs/sk/index.mdx index a2747fe8..dc77399c 100644 --- a/docs/src/content/docs/sk/index.mdx +++ b/docs/src/content/docs/sk/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/sr-latn/getting-started/auth.mdx b/docs/src/content/docs/sr-latn/getting-started/auth.mdx index 8bf0a544..af1fdcea 100644 --- a/docs/src/content/docs/sr-latn/getting-started/auth.mdx +++ b/docs/src/content/docs/sr-latn/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Uloge po nalogu -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------ | -| Super administrator | Ima kompletnu kontrolu nad Castopod-om. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Menadžer | Upravlja sadržajem na Castopod-u. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podkaster | Opšti korisnici Castopod-a. | admin.access | +| role | description | permissions | +| ------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super administrator | Ima kompletnu kontrolu nad Castopod-om. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Menadžer | Upravlja sadržajem na Castopod-u. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podkaster | Opšti korisnici Castopod-a. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Dozvole po nalogu -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | -------------------------------------------------------------- | | admin.access | Može pristupiti administratorskom delu Castopod-a. | | admin.settings | Može pristupiti podešavanjima Castopod-a. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Može upravljati korisnicima Castopod-a. | | persons.manage | Može upravljati osobama. | | pages.manage | Može upravljati stranicama. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Može uvesti nove podkaste. | | fediverse.manage-blocks | Može blokirati interakciju Castopoda i fedivers naloga/domena. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Uloge po podkastu -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Autor | Upravlja sadržajem podkasta #\{id\} ali ne može da ga objavi. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Gost | Saradnik na podkastu #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Dozvole po podkastu -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | ---------------------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Može da objavi/poništi objavljivanje epizoda i postova podkasta #\{id\}. | | episodes.manage-comments | Može dodati/obrisati komentar na epizodi podkasta #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/sr-latn/getting-started/docker.mdx b/docs/src/content/docs/sr-latn/getting-started/docker.mdx index af1d0c6d..3a96b56f 100644 --- a/docs/src/content/docs/sr-latn/getting-started/docker.mdx +++ b/docs/src/content/docs/sr-latn/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Podesite obrnuti proksi za TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/sr-latn/getting-started/install.mdx b/docs/src/content/docs/sr-latn/getting-started/install.mdx index 2ee80acc..41790fdb 100644 --- a/docs/src/content/docs/sr-latn/getting-started/install.mdx +++ b/docs/src/content/docs/sr-latn/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/sr-latn/index.mdx b/docs/src/content/docs/sr-latn/index.mdx index 75532de1..d4e9db42 100644 --- a/docs/src/content/docs/sr-latn/index.mdx +++ b/docs/src/content/docs/sr-latn/index.mdx @@ -18,12 +18,11 @@ otiskom (footprint). - 🌱 Besplatan i otvorenog koda (AGPL v3 License) - 🔐 Fokusiran an suverenitet podataka: vaš sadržaj, publika i analitika pripada vama i samo vama -- 🪄  Podkasting 2.0 funkcionalnosti: GUID, zaključan, transkripti, - podrška, poglavlja, lokacija, posobe, zvučni isečci, … +- 🪄  Podkasting 2.0 funkcionalnosti: GUID, zaključan, transkripti, podrška, + poglavlja, lokacija, posobe, zvučni isečci, … - 💬  Ugrađena društvena mreža: - 🚀  Castopod je deo Fediversa, decentralizovane društvene mreže - - ❤️  Napravite objave, delite, dodajte u omiljene i komentarišite - epizode + - ❤️  Napravite objave, delite, dodajte u omiljene i komentarišite epizode - 📈  Ugrađena analitika: - ⚖️  U skladu sa GDPR / CCPA / LGPD - 🪙  Merenje publike putem IABv2 standarda @@ -48,8 +47,8 @@ otiskom (footprint). - 📤  Prebacite svoj podkast sa Castopod-a - 🔀  Mreža: hostujte koliko god želite podkasta - 👥  Više korisnika: dodajte saradnike i odredite njihove uloge -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivacija diff --git a/docs/src/content/docs/sv/getting-started/auth.mdx b/docs/src/content/docs/sv/getting-started/auth.mdx index 0725c600..f452ca54 100644 --- a/docs/src/content/docs/sv/getting-started/auth.mdx +++ b/docs/src/content/docs/sv/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------ | -| Super administratör | Har fullständig kontroll över Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Hanterare | Hanterar Castopods innehåll. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Generella användare av Castopod. | admin.access | +| role | description | permissions | +| ------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super administratör | Har fullständig kontroll över Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Hanterare | Hanterar Castopods innehåll. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | Generella användare av Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ----------------------------------------------------------------------------- | | admin.access | Kan komma åt Castopod admin-området. | | admin.settings | Kan komma åt Castopod-inställningarna. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Kan hantera Castopod-användare. | | persons.manage | Kan hantera personer. | | pages.manage | Kan hantera sidor. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Kan importera podcasts. | | fediverse.manage-blocks | Kan blockera fediverse skådespelare/domäner från att interagera med Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ---------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Författare | Hanterar innehåll i podcast #\{id\} men kan inte publicera dem. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Gäst | Generell bidragsgivare till podcasten #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | ---------------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Kan publicera/avpublicera avsnitt och inlägg i podcast #\{id\}. | | episodes.manage-comments | Kan skapa/ta bort avsnitt kommentarer från podcasten #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/sv/getting-started/docker.mdx b/docs/src/content/docs/sv/getting-started/docker.mdx index 893fc29c..9cf90544 100644 --- a/docs/src/content/docs/sv/getting-started/docker.mdx +++ b/docs/src/content/docs/sv/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Ställ in en omvänd proxy för TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/sv/getting-started/install.mdx b/docs/src/content/docs/sv/getting-started/install.mdx index 37b72f3a..1f653648 100644 --- a/docs/src/content/docs/sv/getting-started/install.mdx +++ b/docs/src/content/docs/sv/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/sv/index.mdx b/docs/src/content/docs/sv/index.mdx index 3bf6d4f1..a494ef49 100644 --- a/docs/src/content/docs/sv/index.mdx +++ b/docs/src/content/docs/sv/index.mdx @@ -16,13 +16,12 @@ mycket litet fotavtryck. ## Funktioner - 🌱  Gratis & öppen källkod (AGPL v3-licens) -- 🔐  Fokuserad på datasuveränitet: ditt innehåll, målgrupp och analys - tillhör dig, och du bara -- 🪄  Podcasting 2.0 funktioner: GUID, låst, avskrifter, finansiering, - kapitel, plats, personer, ljud, … +- 🔐  Fokuserad på datasuveränitet: ditt innehåll, målgrupp och analys tillhör + dig, och du bara +- 🪄  Podcasting 2.0 funktioner: GUID, låst, avskrifter, finansiering, kapitel, + plats, personer, ljud, … - 💬  Inbyggt socialt nätverk: - - 🚀  Castopod är en del av Fediverse, ett decentraliserat socialt - nätverk + - 🚀  Castopod är en del av Fediverse, ett decentraliserat socialt nätverk - ❤️  Skapa inlägg, dela, favorit och kommentera avsnitt - 📈  Inbyggd analys: - ⚖️  GDPR / CCPA / LGPD kompatibel @@ -41,15 +40,15 @@ mycket litet fotavtryck. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publicera dina avsnitt överallt med RSS: - - 📱  På alla index och appar: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  På alla index och appar: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Sänd dina avsnitt direkt med WebSub - 📥  Podcast import: flytta din befintliga podcast till Castopod - 📤  Flytta ut din podcast från Castopod - 🔀  Flera hyresgäst: värd så många podcasts du vill - 👥  Flera användare: lägg till bidragslämnare och ange roller -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/uk/getting-started/auth.mdx b/docs/src/content/docs/uk/getting-started/auth.mdx index 3cfc9530..9f122f86 100644 --- a/docs/src/content/docs/uk/getting-started/auth.mdx +++ b/docs/src/content/docs/uk/getting-started/auth.mdx @@ -12,24 +12,25 @@ coupled with custom rules. Roles and permissions are defined at two levels: ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### Instance permissions -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +39,13 @@ coupled with custom rules. Roles and permissions are defined at two levels: | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. Per podcast roles and permissions ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +54,11 @@ coupled with custom rules. Roles and permissions are defined at two levels: | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### Per podcast permissions -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +82,4 @@ coupled with custom rules. Roles and permissions are defined at two levels: | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/uk/getting-started/docker.mdx b/docs/src/content/docs/uk/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/uk/getting-started/docker.mdx +++ b/docs/src/content/docs/uk/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/uk/getting-started/install.mdx b/docs/src/content/docs/uk/getting-started/install.mdx index 2ee80acc..41790fdb 100644 --- a/docs/src/content/docs/uk/getting-started/install.mdx +++ b/docs/src/content/docs/uk/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/uk/index.mdx b/docs/src/content/docs/uk/index.mdx index a2747fe8..dc77399c 100644 --- a/docs/src/content/docs/uk/index.mdx +++ b/docs/src/content/docs/uk/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,15 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese, Spanish, simplified Chinese… and +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese, Spanish, simplified Chinese… and [many more](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/content/docs/zh-hans/getting-started/auth.mdx b/docs/src/content/docs/zh-hans/getting-started/auth.mdx index 0c026ee6..ced7ab7e 100644 --- a/docs/src/content/docs/zh-hans/getting-started/auth.mdx +++ b/docs/src/content/docs/zh-hans/getting-started/auth.mdx @@ -2,8 +2,9 @@ title: 验证 & 授权 --- -Castopod 使用 `codeigniter/shield` 处理身份验证和授权 与自定义规则。 角色和权限 -在两个级别上定义: Roles and permissions are defined at two levels: +Castopod 使用 `codeigniter/shield` +处理身份验证和授权 与自定义规则。 角色和权限在两个级别上定义: Roles and +permissions are defined at two levels: 1. [实例范围](#1-instance-wide-roles-and-permissions) 2. [每个播客](#2-per-podcast-roles-and-permissions) @@ -12,24 +13,25 @@ Castopod 使用 `codeigniter/shield` 处理身份验证和授权 与自定义规 ### 实例角色 -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ---------- | ---------------------------- | ------------------------------------------------------------------------------------------ | -| 超级管理员 | 拥有对 Castopod 的完全控制。 | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| 管理 | 管理 Castopod 的内容。 | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | Castopod 的普通用户。 | admin.access | +| role | description | permissions | +| ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------ | +| 超级管理员 | 拥有对 Castopod 的完全控制。 | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| 管理 | 管理 Castopod 的内容。 | podcasts.create, podcasts.import, persons.manage, pages.manage | +| 播客 | Castopod 的普通用户。 | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### 实例权限 -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------- | | admin.access | 可以访问 Castopod 管理区域。 | | admin.settings | 可以访问 Castopod 设置。 | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | 可以管理 Castopod 用户。 | | persons.manage | 可以管理人员。 | | pages.manage | 可以管理页面。 | @@ -38,13 +40,13 @@ Castopod 使用 `codeigniter/shield` 处理身份验证和授权 与自定义规 | podcasts.import | 可以导入播客。 | | fediverse.manage-blocks | 可以阻止联邦宇宙参与者/域与 Castopod 交互。 | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. 每个播客角色与权限 ### 每个播客角色 -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +55,11 @@ Castopod 使用 `codeigniter/shield` 处理身份验证和授权 与自定义规 | 作者 | 管理播客 #\{id\} 的内容,但不能发布。 | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | 访客 | 播客 #\{id\} 的普通贡献者。 | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### 每个播客权限 -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | ----------------------------------------------------- | @@ -81,4 +83,4 @@ Castopod 使用 `codeigniter/shield` 处理身份验证和授权 与自定义规 | episodes.manage-publications | 可以发布/取消发布播客 #\{id\} 的剧集和帖子。 | | episodes.manage-comments | 可以创建/删除播客 #\{id\} 的剧集评论。 | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/zh-hans/getting-started/docker.mdx b/docs/src/content/docs/zh-hans/getting-started/docker.mdx index 80f44fc8..ecac3eaf 100644 --- a/docs/src/content/docs/zh-hans/getting-started/docker.mdx +++ b/docs/src/content/docs/zh-hans/getting-started/docker.mdx @@ -4,16 +4,12 @@ title: 官方 Docker 镜像 Castopod 在其自动构建期间会将 3 个 Docker 映像推送到 Docker Hub : -- [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod);一个使 - 用 nginx 单元的整合 Castopod 镜像 -- [**`castopod/app`**](https://hub.docker.com/r/castopod/app):应用程序包,包含 - 所有 Castopod 依赖关系 -- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server):Castopod - 的 Nginx 配置 +- [**`castopod/castopod`**](https://hub.docker.com/r/castopod/castopod);一个使用 nginx 单元的整合 Castopod 镜像 +- [**`castopod/app`**](https://hub.docker.com/r/castopod/app):应用程序包,包含所有 Castopod 依赖关系 +- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server):Castopod 的 Nginx 配置 -此外,Castopod 需要一个与 MySQL 兼容的数据库。 Redis 数据库 可以添加为缓存处理 -器。 A Redis database -can be added as a cache handler. +此外,Castopod 需要一个与 MySQL 兼容的数据库。 Redis 数据库 可以添加为缓存处理器。 A +Redis database can be added as a cache handler. ## 目前支持的标签 @@ -93,8 +89,8 @@ can be added as a cache handler. 3. 设置 TLS 反向代理 (SSL/HTTPS) TLS 是 ActivePub 工作的强制性要求。 此操作可用通过反向代理轻松解决,例如使用 - [Caddy](https://caddyserver.com/) 处理: This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + [Caddy](https://caddyserver.com/) 处理: This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/zh-hans/getting-started/install.mdx b/docs/src/content/docs/zh-hans/getting-started/install.mdx index 508111d7..ee31bc1b 100644 --- a/docs/src/content/docs/zh-hans/getting-started/install.mdx +++ b/docs/src/content/docs/zh-hans/getting-started/install.mdx @@ -9,14 +9,14 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## 要求 -- PHP v8.1 or higher -- MySQL 5.7 或更高版本与 MariaDB 10.2 或更高版本 +- PHP v8.5 or higher +- MySQL 8.4 或更高版本与 MariaDB 11.4 或更高版本 - HTTPS 支持 - 用于验证的 [NTP 同步时钟](https://wiki.debian.org/NTP) 传入请求 -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) @@ -35,9 +35,8 @@ PHP version 8.1 or higher is required, with the following extensions installed: > 我们建议使用 [MariaDB](https://mariadb.org)。 -你需要填写服务器主机名、数据库名称、用户名和密码才能完成安装过程。 如果没有这 -些,请与你的服务器管理员联系。 If you do not have these, please contact your -server administrator. +你需要填写服务器主机名、数据库名称、用户名和密码才能完成安装过程。 如果没有这些,请与你的服务器管理员联系。 If +you do not have these, please contact your server administrator. #### 权限 @@ -46,8 +45,8 @@ server administrator. ### (可选)FFmpeg v4.1.8 或更高版本,用于视频素材 -如果你需要视频素材,则需要 [FFFmpeg](https://www.ffmpeg.org/) 4.1.8 或更高版本。 -必须安装以下扩展: The following extensions must be installed: +如果你需要视频素材,则需要 [FFFmpeg](https://www.ffmpeg.org/) +4.1.8 或更高版本。必须安装以下扩展: The following extensions must be installed: - **FreeType 2** 来自库 [gd](https://www.php.net/manual/en/image.installation.php) @@ -63,11 +62,11 @@ server administrator. ### Pre-requisites 0. 需要一台已经实现 [环境要求](#requirements)的 Web 服务器 -1. 为 Castopod 创建一个 MySQL 数据库,其中用户具有访问和修改权限(有关详细信息, - 请参阅 [MySQL 兼容数据库](#mysql-compatible-database))。 +1. 为 Castopod 创建一个 MySQL 数据库,其中用户具有访问和修改权限(有关详细信息,请参阅 + [MySQL 兼容数据库](#mysql-compatible-database))。 2. 使用 _SSL 证书_ 在您的域激活 HTTPS。 -3. 下载最新的 [Castopod](https://castopod.org/) 到 web 服务器并解压(如果尚未下 - 载)。 +3. 下载最新的 [Castopod](https://castopod.org/) + 到 web 服务器并解压(如果尚未下载)。 - ⚠️ 将 web 服务器根目录设置为 `castopod` 文件夹中的 `public/` 子文件夹。 4. 在 Web 服务器上为各种后台进程添加 **cron 任务** (相应地替换路径): @@ -84,15 +83,16 @@ server administrator. ### (推荐) 安装向导 -1. 前往你最喜欢的浏览器并跳转至安装向导页面 - (`https://your_domain_name.com/cp-install`)运行 Castopod 安装脚本。 +1. 前往你最喜欢的浏览器并跳转至安装向导页面 (`https://your_domain_name.com/cp-install`)运行 Castopod 安装脚本。 2. 请按照屏幕上的说明进行操作。 3. 开始播客! @@ -145,9 +145,9 @@ email.SMTPPass="你的邮件密码" ### 媒体存储 -By default, files are saved to the `public/media` folder using the file system. -默认情况下,文件使用文件系统保存到 `公共/媒体` 文件夹中。 如果您需要将 `media` -文件夹重新定位到其他位置,您可以在您的 `.env` 文件中指定它,如下所示: +By default, files are saved to the `public/media` folder using the file +system. 默认情况下,文件使用文件系统保存到 `公共/媒体` 文件夹中。 如果您需要将 +`media` 文件夹重新定位到其他位置,您可以在您的 `.env` 文件中指定它,如下所示: ```ini # […] @@ -192,9 +192,9 @@ media.s3.region="your_s3_region" ### 使用 YunoHost 安装 -[YunoHost](https://yunohost.org/) 是一个基于 Debian GNU/Linux 的发行版,由免费和 -开源软件包组成。 它可以为你解决自托管的困难。 It manages the hardships of -self-hosting for you. +[YunoHost](https://yunohost.org/) 是一个基于 Debian +GNU/Linux 的发行版,由免费和开源软件包组成。 它可以为你解决自托管的困难。 It +manages the hardships of self-hosting for you.
diff --git a/docs/src/content/docs/zh-hans/getting-started/security.mdx b/docs/src/content/docs/zh-hans/getting-started/security.mdx index 78c367e6..653b1655 100644 --- a/docs/src/content/docs/zh-hans/getting-started/security.mdx +++ b/docs/src/content/docs/zh-hans/getting-started/security.mdx @@ -5,8 +5,7 @@ title: 安全问题 Castopod 构建于 [CodeIgniter4](https://codeigniter.com/), PHP 框架上,鼓励 [更好的安全实践](https://codeigniter.com/user_guide/concepts/security.html)。 -为了最大限度地提高你实例的安全性并防止任何恶意攻击。 我们 建议你在安装或更新后检 -查所有的 Castopod 文件权限(避免任何之前的权限错误): +为了最大限度地提高你实例的安全性并防止任何恶意攻击。 我们 建议你在安装或更新后检查所有的 Castopod 文件权限(避免任何之前的权限错误): - `writable/` 文件夹权限为 **可读** 和 **可写**。 - `public/media/` 文件夹权限为 **可读** 和 **可写**。 diff --git a/docs/src/content/docs/zh-hans/getting-started/update.mdx b/docs/src/content/docs/zh-hans/getting-started/update.mdx index c342daee..cd847189 100644 --- a/docs/src/content/docs/zh-hans/getting-started/update.mdx +++ b/docs/src/content/docs/zh-hans/getting-started/update.mdx @@ -4,21 +4,20 @@ title: 如何更新 Castopod ? import { Aside } from "@astrojs/starlight/components"; -安装 Castopod 后,你可能希望将实例更新到最新版本 版本以享受最新功能 ✨, 修复错误 -🐛 和性能提升 ⚡。 +安装 Castopod 后,你可能希望将实例更新到最新版本 版本以享受最新功能 ✨, 修复错误 🐛 和性能提升 ⚡。 ## 更新说明 0. ⚠️ 在更新之前,我们强烈建议你备份 Castopod 文件和数据库。 - 参看. [我应该在更新前进行备份吗?](#should-i-make-a-backup-before-updating) -1. 前往 [发布页面](https://code.castopod.org/adaures/castopod/-/releases) 和 查 - 看您的实例是否是最新的 Castopod 版本 +1. 前往 [发布页面](https://code.castopod.org/adaures/castopod/-/releases) + 和 查看您的实例是否是最新的 Castopod 版本 - 参看 [我在哪里可以找到我的 Castopod 版本?](#where-can-i-find-my-castopod-version) -2. 下载名为`Castopod Package`的最新发布包,你可以在 `zip` 或 `tar.gz` 压缩包之间 - 选择 +2. 下载名为`Castopod Package`的最新发布包,你可以在 `zip` 或 `tar.gz` + 压缩包之间选择 - ⚠️ 请确保你下载的是 Castopod 软件包而 **不是** 源代码 - 请注意,你还可以从 [castopod.org](https://castopod.org/) @@ -79,8 +78,9 @@ them sequentially, from the oldest to the newest. 1. 下载最新版本,覆盖您的文件,同时保留 `.env` 文件和 `public/media` 文件夹。 -2. 从 `v1.0.0-alpha.43` 开始,按顺序执行每个版本更新指令(从老版本到 最新版本), - 然后是 `v1.0.0-alpha.44`,`v1.0.0-alpha.45`,…,直到 `v1.0.0-beta.1`。 +2. 从 `v1.0.0-alpha.43` + 开始,按顺序执行每个版本更新指令(从老版本到 最新版本),然后是 + `v1.0.0-alpha.44`,`v1.0.0-alpha.45`,…,直到 `v1.0.0-beta.1`。 3. ✨ 享受你的新实例, 你已经更新完毕! diff --git a/docs/src/content/docs/zh-hans/index.mdx b/docs/src/content/docs/zh-hans/index.mdx index 93492a8f..0dc25fb1 100644 --- a/docs/src/content/docs/zh-hans/index.mdx +++ b/docs/src/content/docs/zh-hans/index.mdx @@ -4,11 +4,10 @@ title: 欢迎 👋 import { LinkCard } from "@astrojs/starlight/components"; -Castopod 是一个免费的开源播客托管平台,为那些想要和听众接触与互动的播客们制作 -的。 +Castopod 是一个免费的开源播客托管平台,为那些想要和听众接触与互动的播客们制作的。 -Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/) 构建, 这是一 -个强大的 PHP 框架,并且占用极小。 +Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/) +构建, 这是一个强大的 PHP 框架,并且占用极小。 @@ -16,8 +15,7 @@ Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/) 构 - 🌱  免费与开源(AGPL v3 许可证) - 🔐  专注于数据主权:你的内容、受众和分析属于你,而且只属于你 -- 🪄  播客 2.0 功能:GUID,锁定,报表,资金,章节,位置信息,人员,原声摘要 - ... +- 🪄  播客 2.0 功能:GUID,锁定,报表,资金,章节,位置信息,人员,原声摘要 ... - 💬  内置社交网络: - 🚀  Castopod 是联邦宇宙的一部分,联邦宇宙是一个去中心化的社交网络 - ❤️  创建帖子、分享、收藏和评论剧集。 @@ -38,35 +36,31 @@ Castopod 易于安装,并使用 [CodeIgniter4](https://codeigniter.com/) 构 - 🤝  value4value / 网络货币化 - 💎  高级版播客 - 📡  使用 RSS 将你的剧集发布到任何地方: - - 📱  支持众多索引和应用程序:Podcast Index,Apple - Podcasts,Spotify,Google Podcasts,Deezer,Podcast Addict,Podfriend... + - 📱  支持众多索引和应用程序:Podcast Index,Apple Podcasts,Spotify,Google + Podcasts,Deezer,Podcast Addict,Podfriend... - ⚡  使用 WebSub 即时广播你的剧集 - 📥  播客导入:将现有播客移至 Castopod - 📤  也支持将播客移出 Castopod - 🔀  多租户:根据需要托管任意数量的播客 - 👥  多用户:添加贡献者并设置角色 -- 🌎  i18n 支持:翻译成英语,法语,波兰语,德语,巴西葡萄牙语和西班牙语 - ...[还有更多](https://translate.castopod.org)! and +- 🌎 +  i18n 支持:翻译成英语,法语,波兰语,德语,巴西葡萄牙语和西班牙语 ...[还有更多](https://translate.castopod.org)! and [many more](https://translate.castopod.org)! ## 创作动机 -播客生态系统本质上是去中心化的:你可以创建自己的播客 RSS 文件,将其发布到网络上 -并在线共享。 +播客生态系统本质上是去中心化的:你可以创建自己的播客 RSS 文件,将其发布到网络上并在线共享。 事实上,它是唯一长期保持这种状态的媒体之一。 -随着习惯的发展,越来越多的人开始接触播客:允许创作者寻找新的方式来分享他们的想 -法,或是让听众获得更好的内容。 +随着习惯的发展,越来越多的人开始接触播客:允许创作者寻找新的方式来分享他们的想法,或是让听众获得更好的内容。 随着播客的使用越来越广泛,一些公司正试图控制播客与集中化。 -Castopod 的创建旨在提供一种开放且可持续的替代方案来托管你的播客,促进权力下放, -确保播客可以用创造力表达自己。 +Castopod 的创建旨在提供一种开放且可持续的替代方案来托管你的播客,促进权力下放,确保播客可以用创造力表达自己。 -此项目由开源社区推动的,特别是 -由[联邦宇宙](https://fediverse.party/en/fediverse/) 和 -[播客 2.0](https://podcastindex.org/) 推动。 +此项目由开源社区推动的,特别是由[联邦宇宙](https://fediverse.party/en/fediverse/) +和 [播客 2.0](https://podcastindex.org/) 推动。 ## 与其他解决方案的对比 @@ -76,51 +70,42 @@ gauge whether Castopod is the right fit for you. ### Castopod 对比 Wordpress -Castopod 经常被称为 “播客中的 Wordpress”,因为两者有很多相似之处。 在某些方面, -确实如此。 实际上,Castopod 受到 WordPress 生态的极大启发,看到了采用社区的易用 -性以及运行它的网站数量。 In some ways this is true. And actually, Castopod -was greatly inspired by the Wordpress ecosystem, seeing the ease of adoption -from the community and the number of websites running it. +Castopod 经常被称为 “播客中的 Wordpress”,因为两者有很多相似之处。 在某些方面,确实如此。 实际上,Castopod 受到 WordPress 生态的极大启发,看到了采用社区的易用性以及运行它的网站数量。 In +some ways this is true. And actually, Castopod was greatly inspired by the +Wordpress ecosystem, seeing the ease of adoption from the community and the +number of websites running it. -就像 Wordpress 一样,Castopod 是免费 & 开源的,PHP 构建并使用 MySQL 数据库,可以 -在大多数 Web 服务器上轻松安装。 +就像 Wordpress 一样,Castopod 是免费 & 开源的,PHP 构建并使用 MySQL 数据库,可以在大多数 Web 服务器上轻松安装。 -Wordpress 是创建你的网站,并使用插件扩展以获得想要内容的好办法。 这是一个成熟的 -CMS,可以帮助你在线访问任何类型的网站。 It is a full fledged CMS that helps you get any type of -website online. +Wordpress 是创建你的网站,并使用插件扩展以获得想要内容的好办法。 这是一个成熟的 CMS,可以帮助你在线访问任何类型的网站。 It +is a full fledged CMS that helps you get any type of website online. -另一方面,Castopod 旨在专门满足播客的需求,专注于播客,而不是其他。 你不需要任何 -插件即可轻松开始播客之旅。 You don't need any -plugin to get you started on your podcasting journey. +另一方面,Castopod 旨在专门满足播客的需求,专注于播客,而不是其他。 你不需要任何插件即可轻松开始播客之旅。 You +don't need any plugin to get you started on your podcasting journey. 还拥有对播客的独特优化:从播客的创建和新剧集的发布一直到广播,营销和分析。 -最后,根据你的需要,Wordpress 和 Castopod 甚至可以共存,因为他们有相同的配置环 -境! +最后,根据你的需要,Wordpress 和 Castopod 甚至可以共存,因为他们有相同的配置环境! ### Castopod 对比 Funkwhale -Funkwhale 是一个自托管、现代界面、免费开源的音乐服务器。 就像 Castopod 一 -样,Funkwhale 也位于联邦宇宙中,这是一个去中心化的社交网络,允许两者的互联。 Just as -Castopod, Funkwhale is on the fediverse, a decentralized social network allowing -interoperability between the two. +Funkwhale 是一个自托管、现代界面、免费开源的音乐服务器。 就像 Castopod 一样,Funkwhale 也位于联邦宇宙中,这是一个去中心化的社交网络,允许两者的互联。Just +as Castopod, Funkwhale is on the fediverse, a decentralized social network +allowing interoperability between the two. -Funkwhale 最初是围绕音乐制作的。 后来,随着项目的发展,引入了托管播客的能力。 And later on, as the project -evolved, the ability to host podcasts was introduced. +Funkwhale 最初是围绕音乐制作的。 后来,随着项目的发展,引入了托管播客的能力。And +later on, as the project evolved, the ability to host podcasts was introduced. -与 Funkwhale 不同,Castopod 是只围绕播客设计和构建的。 这样可以更简单地实现与播 -客相关的生态系统,例如播客 2.0 功能(报表、 章节、地点、人员…)。 This allows easier implementation for features related to the -podcasting ecosystem, such as the podcasting 2.0 features (transcripts, -chapters, locations, persons, …). +与 Funkwhale 不同,Castopod 是只围绕播客设计和构建的。 这样可以更简单地实现与播客相关的生态系统,例如播客 2.0 功能(报表、 章节、地点、人员…)。 This +allows easier implementation for features related to the podcasting ecosystem, +such as the podcasting 2.0 features (transcripts, chapters, locations, persons, +…). -因此,如果你想托管你的音乐库,你可能应该使用 Funkwhale,如果您想主持一个播客,请 -使用 Castopod。 +因此,如果你想托管你的音乐库,你可能应该使用 Funkwhale,如果您想主持一个播客,请使用 Castopod。 ### Castopod 与其他播客 -有许多非常棒地解决方案可供你托管播客,并 -且[很多](https://podcastindex.org/apps)正在搭上播客 2.0 的便车,就像 Castopod 一 -样! +有许多非常棒地解决方案可供你托管播客,并且[很多](https://podcastindex.org/apps)正在搭上播客 2.0 的便车,就像 Castopod 一样! 这些解决方案各不相同,你可以对比 [功能列表](#features)。 @@ -130,33 +115,30 @@ chapters, locations, persons, …). full control over what you produce. Also, as it is open-source, you can even customize it as you wish. -- Castopod 是目前唯一一个同时集成去中心化的,带有 ActivePub 的社交网络以及很多播 - 客 2.0 功能集成的解决方案,希望弥合两者之间的差距。 +- Castopod 是目前唯一一个同时集成去中心化的,带有 ActivePub 的社交网络以及很多播客 2.0 功能集成的解决方案,希望弥合两者之间的差距。 ## 贡献 -喜欢 Castopod 并且想帮忙吗? 请查看以下文档以帮助你入门。 请查看以下文档以帮助你 -入门。 +喜欢 Castopod 并且想帮忙吗? 请查看以下文档以帮助你入门。 请查看以下文档以帮助你入门。 ### 行为准则 Castopod has adopted a Code of Conduct that we expect project participants to -adhere to. Castopod 已经通过了一项行为准则,并希望所有的参与者都能够遵循本行为准则。 请阅 -读[行为准则](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md) +adhere to. +Castopod 已经通过了一项行为准则,并希望所有的参与者都能够遵循本行为准则。 请阅读[行为准则](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md) 以便了解哪些行为被允许,哪些行为不会被容忍。 ### 贡献指南 -阅读我们的 [贡献指南](../contributing/guidelines.md) ,了解我们的开发过程。 提出 -错 误修正和改进想法,以及如何构建和测试 Castopod 。 +阅读我们的 [贡献指南](../contributing/guidelines.md) +,了解我们的开发过程。 提出错 误修正和改进想法,以及如何构建和测试 Castopod 。 ## 联系 你可以联系我们寻求帮助或提出任何问题: - [Discord](https://castopod.org/discord) (用于与开发人员和社区直接互动) -- [问题跟踪器](https://code.castopod.org/adaures/castopod/-/issues)(用于功能请 - 求和错误报告) +- [问题跟踪器](https://code.castopod.org/adaures/castopod/-/issues)(用于功能请求和错误报告) 或者,你可以在社交媒体上关注我们,以获取有关 Castopod 的新闻: @@ -168,8 +150,8 @@ adhere to. Castopod 已经通过了一项行为准则,并希望所有的参与 ## 赞助商 The ongoing development of Castopod is made possible with the support of its -backers. Castopod 的发展离不开赞助商的支持。 如果你想要帮助我们,请考 -虑[赞助 Castopod 的开发](https://opencollective.com/castopod/contribute). +backers. +Castopod 的发展离不开赞助商的支持。 如果你想要帮助我们,请考虑[赞助 Castopod 的开发](https://opencollective.com/castopod/contribute). [![Ad Aures Logo](../../../assets/images/sponsors/adaures.svg)](https://adaures.com/) diff --git a/docs/src/content/docs/zh-hant/getting-started/auth.mdx b/docs/src/content/docs/zh-hant/getting-started/auth.mdx index 177d77a1..2b9f3f0a 100644 --- a/docs/src/content/docs/zh-hant/getting-started/auth.mdx +++ b/docs/src/content/docs/zh-hant/getting-started/auth.mdx @@ -2,8 +2,9 @@ title: 認證 & 授權 --- -Castopod 使用 `codeigniter/shield` 處理身分認證和授權 與自定義規則。 腳色和權限 -在定義為兩個層級: Roles and permissions are defined at two levels: +Castopod 使用 `codeigniter/shield` +處理身分認證和授權 與自定義規則。 腳色和權限在定義為兩個層級: Roles and +permissions are defined at two levels: 1. [實例範圍](#1-instance-wide-roles-and-permissions) 2. [每個播客](#2-per-podcast-roles-and-permissions) @@ -12,24 +13,25 @@ Castopod 使用 `codeigniter/shield` 處理身分認證和授權 與自定義規 ### Instance roles -{/_ AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section */} -| role | description | permissions | -| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | -| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | -| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | -| Podcaster | General users of Castopod. | admin.access | +| role | description | permissions | +| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Super admin | Has complete control over Castopod. | admin.\*, plugins.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks | +| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage | +| Podcaster | General users of Castopod. | admin.access | -{/_ AUTH-INSTANCE-ROLES-LIST:END _/} +{/* AUTH-INSTANCE-ROLES-LIST:END */} ### 實例權限 -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ----------------------- | ------------------------------------------------------------------ | | admin.access | Can access the Castopod admin area. | | admin.settings | Can access the Castopod settings. | +| plugins.manage | Auth.instance_permissions.plugins.manage | | users.manage | Can manage Castopod users. | | persons.manage | Can manage persons. | | pages.manage | Can manage pages. | @@ -38,13 +40,13 @@ Castopod 使用 `codeigniter/shield` 處理身分認證和授權 與自定義規 | podcasts.import | Can import podcasts. | | fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. | -{/_ AUTH-INSTANCE-PERMISSIONS-LIST:END _/} +{/* AUTH-INSTANCE-PERMISSIONS-LIST:END */} ## 2. 每個播客腳色與權限 ### Per podcast roles -{/_ AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section */} | role | description | permissions | | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -53,11 +55,11 @@ Castopod 使用 `codeigniter/shield` 處理身分認證和授權 與自定義規 | Author | Manages content of podcast #\{id\} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips | | Guest | General contributor of the podcast #\{id\}. | view, episodes.view | -{/_ AUTH-PODCAST-ROLES-LIST:END _/} +{/* AUTH-PODCAST-ROLES-LIST:END */} ### 每個播客權限 -{/_ AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section */} | permission | description | | ---------------------------- | -------------------------------------------------------------------------- | @@ -81,4 +83,4 @@ Castopod 使用 `codeigniter/shield` 處理身分認證和授權 與自定義規 | episodes.manage-publications | Can publish/unpublish episodes and posts of podcast #\{id\}. | | episodes.manage-comments | Can create/remove episode comments of podcast #\{id\}. | -{/_ AUTH-PODCAST-PERMISSIONS-LIST:END _/} +{/* AUTH-PODCAST-PERMISSIONS-LIST:END */} diff --git a/docs/src/content/docs/zh-hant/getting-started/docker.mdx b/docs/src/content/docs/zh-hant/getting-started/docker.mdx index 14e36d5e..2628ed8a 100644 --- a/docs/src/content/docs/zh-hant/getting-started/docker.mdx +++ b/docs/src/content/docs/zh-hant/getting-started/docker.mdx @@ -92,8 +92,8 @@ can be added as a cache handler. 3. Setup a reverse proxy for TLS (SSL/HTTPS) - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): + TLS is mandatory for ActivityPub to work. This job can easily be handled by a + reverse proxy, for example with [Caddy](https://caddyserver.com/): ``` #castopod diff --git a/docs/src/content/docs/zh-hant/getting-started/install.mdx b/docs/src/content/docs/zh-hant/getting-started/install.mdx index 4e8f1b2c..316cd588 100644 --- a/docs/src/content/docs/zh-hant/getting-started/install.mdx +++ b/docs/src/content/docs/zh-hant/getting-started/install.mdx @@ -9,15 +9,15 @@ shared hosting, you can install it on most PHP-MySQL compatible web servers. ## Requirements -- PHP v8.1 or higher -- MySQL version 5.7 or higher or MariaDB version 10.2 or higher +- PHP v8.5 or higher +- MySQL version 8.4 or higher or MariaDB version 11.4 or higher - HTTPS support - An [ntp-synced clock](https://wiki.debian.org/NTP) to validate federation's incoming requests -### PHP v8.1 or higher +### PHP v8.5 or higher -PHP version 8.1 or higher is required, with the following extensions installed: +PHP version 8.5 or higher is required, with the following extensions installed: - [intl](https://php.net/manual/en/intl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php) diff --git a/docs/src/content/docs/zh-hant/index.mdx b/docs/src/content/docs/zh-hant/index.mdx index 18f84407..55bd5e89 100644 --- a/docs/src/content/docs/zh-hant/index.mdx +++ b/docs/src/content/docs/zh-hant/index.mdx @@ -16,10 +16,10 @@ small footprint. ## Features - 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … +- 🔐  Focused on data sovereignty: your content, audience, and analytics belong + to you, and you only +- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, chapters, + location, persons, soundbites, … - 💬  Built-in social network: - 🚀  Castopod is part of the Fediverse, a decentralized social network - ❤️  Create posts, share, favourite, and comment on episodes @@ -40,16 +40,15 @@ small footprint. - 🤝  value4value / WebMonetization - 💎  Premium podcasts - 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … + - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, Google + Podcasts, Deezer, Podcast Addict, Podfriend, … - ⚡  Broadcast your episodes instantly with WebSub - 📥  Podcast import: move your existing podcast into Castopod - 📤  Move your podcast out of Castopod - 🔀  Multi-tenant: host as many podcasts as you want - 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! +- 🌎  i18n support: translated in English, French, Polish, German, Brazilian + Portuguese & Spanish… with [more to come](https://translate.castopod.org)! ## Motivation diff --git a/docs/src/pages/plugin-manifest.schema.json.ts b/docs/src/pages/plugin-manifest.schema.json.ts new file mode 100644 index 00000000..f92d95fb --- /dev/null +++ b/docs/src/pages/plugin-manifest.schema.json.ts @@ -0,0 +1,8 @@ +import type { APIRoute } from "astro"; +import manifestSchema from "../../../modules/Plugins/Manifest/manifest.schema.json"; + +export const GET: APIRoute = async () => { + return new Response(JSON.stringify(manifestSchema), { + headers: { "Content-Type": "application/json" }, + }); +}; diff --git a/ecs.php b/ecs.php index 8dfd4b97..8b1a94f5 100644 --- a/ecs.php +++ b/ecs.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer; use PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer; use PhpCsFixer\Fixer\StringNotation\SingleQuoteFixer; use PhpCsFixer\Fixer\Whitespace\IndentationTypeFixer; @@ -13,6 +14,7 @@ return ECSConfig::configure() ->withPaths([ __DIR__ . '/app', __DIR__ . '/modules', + __DIR__ . '/plugins', __DIR__ . '/themes', __DIR__ . '/tests', __DIR__ . '/public', @@ -49,4 +51,7 @@ return ECSConfig::configure() 'operators' => [ '=>' => 'align_single_space_minimal', ], + ]) + ->withConfiguredRule(TrailingCommaInMultilineFixer::class, [ + 'elements' => ['arguments', 'array_destructuring', 'arrays', 'match', 'parameters'], ]); diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php index 55b3645f..375b2943 100644 --- a/modules/Admin/Config/Routes.php +++ b/modules/Admin/Config/Routes.php @@ -29,41 +29,41 @@ $routes->group( 'as' => 'settings-general', 'filter' => 'permission:admin.settings', ]); - $routes->post('instance', 'SettingsController::attemptInstanceEdit', [ + $routes->post('instance', 'SettingsController::instanceEditAction', [ 'as' => 'settings-instance', 'filter' => 'permission:admin.settings', ]); - $routes->get('instance-delete-icon', 'SettingsController::deleteIcon', [ + $routes->get('instance-delete-icon', 'SettingsController::deleteIconAction', [ 'as' => 'settings-instance-delete-icon', 'filter' => 'permission:admin.settings', ]); - $routes->post('instance-images-regenerate', 'SettingsController::regenerateImages', [ + $routes->post('instance-images-regenerate', 'SettingsController::regenerateImagesAction', [ 'as' => 'settings-images-regenerate', 'filter' => 'permission:admin.settings', ]); - $routes->post('instance-housekeeping-run', 'SettingsController::runHousekeeping', [ + $routes->post('instance-housekeeping-run', 'SettingsController::housekeepingAction', [ 'as' => 'settings-housekeeping-run', 'filter' => 'permission:admin.settings', ]); - $routes->get('theme', 'SettingsController::theme', [ + $routes->get('theme', 'SettingsController::themeView', [ 'as' => 'settings-theme', 'filter' => 'permission:admin.settings', ]); - $routes->post('theme', 'SettingsController::attemptSetInstanceTheme', [ + $routes->post('theme', 'SettingsController::themeAction', [ 'as' => 'settings-theme', 'filter' => 'permission:admin.settings', ]); }); $routes->group('persons', static function ($routes): void { - $routes->get('/', 'PersonController', [ + $routes->get('/', 'PersonController::list', [ 'as' => 'person-list', 'filter' => 'permission:persons.manage', ]); - $routes->get('new', 'PersonController::create', [ + $routes->get('new', 'PersonController::createView', [ 'as' => 'person-create', 'filter' => 'permission:persons.manage', ]); - $routes->post('new', 'PersonController::attemptCreate', [ + $routes->post('new', 'PersonController::createAction', [ 'filter' => 'permission:persons.manage', ]); $routes->group('(:num)', static function ($routes): void { @@ -71,14 +71,14 @@ $routes->group( 'as' => 'person-view', 'filter' => 'permission:persons.manage', ]); - $routes->get('edit', 'PersonController::edit/$1', [ + $routes->get('edit', 'PersonController::editView/$1', [ 'as' => 'person-edit', 'filter' => 'permission:persons.manage', ]); - $routes->post('edit', 'PersonController::attemptEdit/$1', [ + $routes->post('edit', 'PersonController::editAction/$1', [ 'filter' => 'permission:persons.manage', ]); - $routes->add('delete', 'PersonController::delete/$1', [ + $routes->add('delete', 'PersonController::deleteAction/$1', [ 'as' => 'person-delete', 'filter' => 'permission:persons.manage', ]); @@ -89,11 +89,11 @@ $routes->group( $routes->get('/', 'PodcastController::list', [ 'as' => 'podcast-list', ]); - $routes->get('new', 'PodcastController::create', [ + $routes->get('new', 'PodcastController::createView', [ 'as' => 'podcast-create', 'filter' => 'permission:podcasts.create', ]); - $routes->post('new', 'PodcastController::attemptCreate', [ + $routes->post('new', 'PodcastController::createAction', [ 'filter' => 'permission:podcasts.create', ]); // Podcast @@ -101,144 +101,136 @@ $routes->group( $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'PodcastController::view/$1', [ 'as' => 'podcast-view', - 'filter' => 'permission:podcast#.view', + 'filter' => 'permission:podcast$1.view', ]); - $routes->get('edit', 'PodcastController::edit/$1', [ + $routes->get('edit', 'PodcastController::editView/$1', [ 'as' => 'podcast-edit', - 'filter' => 'permission:podcast#.edit', + 'filter' => 'permission:podcast$1.edit', ]); - $routes->post('edit', 'PodcastController::attemptEdit/$1', [ - 'filter' => 'permission:podcast#.edit', + $routes->post('edit', 'PodcastController::editAction/$1', [ + 'filter' => 'permission:podcast$1.edit', ]); $routes->get( 'publish', - 'PodcastController::publish/$1', + 'PodcastController::publishView/$1', [ 'as' => 'podcast-publish', - 'filter' => 'permission:podcast#.manage-publications', + 'filter' => 'permission:podcast$1.manage-publications', ], ); $routes->post( 'publish', - 'PodcastController::attemptPublish/$1', + 'PodcastController::publishAction/$1', [ - 'filter' => 'permission:podcast#.manage-publications', + 'filter' => 'permission:podcast$1.manage-publications', ], ); $routes->get( 'publish-edit', - 'PodcastController::publishEdit/$1', + 'PodcastController::publishEditView/$1', [ 'as' => 'podcast-publish_edit', - 'filter' => 'permission:podcast#.manage-publications', + 'filter' => 'permission:podcast$1.manage-publications', ], ); $routes->post( 'publish-edit', - 'PodcastController::attemptPublishEdit/$1', + 'PodcastController::publishEditAction/$1', [ - 'filter' => 'permission:podcast#.manage-publications', + 'filter' => 'permission:podcast$1.manage-publications', ], ); $routes->get( 'publish-cancel', - 'PodcastController::publishCancel/$1', + 'PodcastController::publishCancelAction/$1', [ 'as' => 'podcast-publish-cancel', - 'filter' => 'permission:podcast#.manage-publications', + 'filter' => 'permission:podcast$1.manage-publications', ], ); - $routes->get('edit/delete-banner', 'PodcastController::deleteBanner/$1', [ + $routes->get('edit/delete-banner', 'PodcastController::deleteBannerAction/$1', [ 'as' => 'podcast-banner-delete', - 'filter' => 'permission:podcast#.edit', + 'filter' => 'permission:podcast$1.edit', ]); - $routes->get('delete', 'PodcastController::delete/$1', [ + $routes->get('delete', 'PodcastController::deleteView/$1', [ 'as' => 'podcast-delete', - 'filter' => 'permission:podcast#.delete', + 'filter' => 'permission:podcast$1.delete', ]); - $routes->post('delete', 'PodcastController::attemptDelete/$1', [ - 'filter' => 'permission:podcast#.delete', + $routes->post('delete', 'PodcastController::deleteAction/$1', [ + 'filter' => 'permission:podcast$1.delete', ]); $routes->group('persons', static function ($routes): void { $routes->get('/', 'PodcastPersonController::index/$1', [ 'as' => 'podcast-persons-manage', - 'filter' => 'permission:podcast#.manage-persons', + 'filter' => 'permission:podcast$1.manage-persons', ]); $routes->post( '/', - 'PodcastPersonController::attemptCreate/$1', + 'PodcastPersonController::createAction/$1', [ - 'filter' => 'permission:podcast#.manage-persons', + 'filter' => 'permission:podcast$1.manage-persons', ], ); $routes->get( '(:num)/remove', - 'PodcastPersonController::remove/$1/$2', + 'PodcastPersonController::deleteAction/$1/$2', [ 'as' => 'podcast-person-remove', - 'filter' => 'permission:podcast#.manage-persons', + 'filter' => 'permission:podcast$1.manage-persons', ], ); }); - $routes->get('monetization-other', 'PodcastController::monetizationOther/$1', [ - 'as' => 'podcast-monetization-other', - 'filter' => 'permission:podcast#.edit', - ]); - $routes->post('monetization-other', 'PodcastController::monetizationOtherAction/$1', [ - 'as' => 'podcast-monetization-other', - 'filter' => 'permission:podcast#.edit', - ]); $routes->group('analytics', static function ($routes): void { - $routes->get('/', 'PodcastController::viewAnalytics/$1', [ + $routes->get('/', 'PodcastController::analyticsView/$1', [ 'as' => 'podcast-analytics', - 'filter' => 'permission:podcast#.view', + 'filter' => 'permission:podcast$1.view', ]); $routes->get( 'webpages', - 'PodcastController::viewAnalyticsWebpages/$1', + 'PodcastController::analyticsWebpagesView/$1', [ 'as' => 'podcast-analytics-webpages', - 'filter' => 'permission:podcast#.view', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'locations', - 'PodcastController::viewAnalyticsLocations/$1', + 'PodcastController::analyticsLocationsView/$1', [ 'as' => 'podcast-analytics-locations', - 'filter' => 'permission:podcast#.view', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'unique-listeners', - 'PodcastController::viewAnalyticsUniqueListeners/$1', + 'PodcastController::analyticsUniqueListenersView/$1', [ 'as' => 'podcast-analytics-unique-listeners', - 'filter' => 'permission:podcast#.view', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'listening-time', - 'PodcastController::viewAnalyticsListeningTime/$1', + 'PodcastController::analyticsListeningTimeView/$1', [ 'as' => 'podcast-analytics-listening-time', - 'filter' => 'permission:podcast#.view', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'time-periods', - 'PodcastController::viewAnalyticsTimePeriods/$1', + 'PodcastController::analyticsTimePeriodsView/$1', [ 'as' => 'podcast-analytics-time-periods', - 'filter' => 'permission:podcast#.view', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'players', - 'PodcastController::viewAnalyticsPlayers/$1', + 'PodcastController::analyticsPlayersView/$1', [ 'as' => 'podcast-analytics-players', - 'filter' => 'permission:podcast#.view', + 'filter' => 'permission:podcast$1.view', ], ); }); @@ -246,117 +238,117 @@ $routes->group( $routes->group('episodes', static function ($routes): void { $routes->get('/', 'EpisodeController::list/$1', [ 'as' => 'episode-list', - 'filter' => 'permission:podcast#.episodes.view', + 'filter' => 'permission:podcast$1.episodes.view', ]); - $routes->get('new', 'EpisodeController::create/$1', [ + $routes->get('new', 'EpisodeController::createView/$1', [ 'as' => 'episode-create', - 'filter' => 'permission:podcast#.episodes.create', + 'filter' => 'permission:podcast$1.episodes.create', ]); $routes->post( 'new', - 'EpisodeController::attemptCreate/$1', + 'EpisodeController::createAction/$1', [ - 'filter' => 'permission:podcast#.episodes.create', + 'filter' => 'permission:podcast$1.episodes.create', ], ); // Episode $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'EpisodeController::view/$1/$2', [ 'as' => 'episode-view', - 'filter' => 'permission:podcast#.episodes.view', + 'filter' => 'permission:podcast$1.episodes.view', ]); - $routes->get('edit', 'EpisodeController::edit/$1/$2', [ + $routes->get('edit', 'EpisodeController::editView/$1/$2', [ 'as' => 'episode-edit', - 'filter' => 'permission:podcast#.episodes.edit', + 'filter' => 'permission:podcast$1.episodes.edit', ]); $routes->post( 'edit', - 'EpisodeController::attemptEdit/$1/$2', + 'EpisodeController::editAction/$1/$2', [ - 'filter' => 'permission:podcast#.episodes.edit', + 'filter' => 'permission:podcast$1.episodes.edit', ], ); $routes->get( 'publish', - 'EpisodeController::publish/$1/$2', + 'EpisodeController::publishView/$1/$2', [ 'as' => 'episode-publish', - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->post( 'publish', - 'EpisodeController::attemptPublish/$1/$2', + 'EpisodeController::publishAction/$1/$2', [ - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'publish-edit', - 'EpisodeController::publishEdit/$1/$2', + 'EpisodeController::publishEditView/$1/$2', [ 'as' => 'episode-publish_edit', - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->post( 'publish-edit', - 'EpisodeController::attemptPublishEdit/$1/$2', + 'EpisodeController::publishEditAction/$1/$2', [ - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'publish-cancel', - 'EpisodeController::publishCancel/$1/$2', + 'EpisodeController::publishCancelAction/$1/$2', [ 'as' => 'episode-publish-cancel', - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'publish-date-edit', - 'EpisodeController::publishDateEdit/$1/$2', + 'EpisodeController::publishDateEditView/$1/$2', [ 'as' => 'episode-publish_date_edit', - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->post( 'publish-date-edit', - 'EpisodeController::attemptPublishDateEdit/$1/$2', + 'EpisodeController::publishDateEditAction/$1/$2', [ - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'unpublish', - 'EpisodeController::unpublish/$1/$2', + 'EpisodeController::unpublishView/$1/$2', [ 'as' => 'episode-unpublish', - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->post( 'unpublish', - 'EpisodeController::attemptUnpublish/$1/$2', + 'EpisodeController::unpublishAction/$1/$2', [ - 'filter' => 'permission:podcast#.episodes.manage-publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'delete', - 'EpisodeController::delete/$1/$2', + 'EpisodeController::deleteView/$1/$2', [ 'as' => 'episode-delete', - 'filter' => 'permission:podcast#.episodes.delete', + 'filter' => 'permission:podcast$1.episodes.delete', ], ); $routes->post( 'delete', - 'EpisodeController::attemptDelete/$1/$2', + 'EpisodeController::deleteAction/$1/$2', [ - 'filter' => 'permission:podcast#.episodes.delete', + 'filter' => 'permission:podcast$1.episodes.delete', ], ); $routes->get( @@ -364,7 +356,7 @@ $routes->group( 'EpisodeController::transcriptDelete/$1/$2', [ 'as' => 'transcript-delete', - 'filter' => 'permission:podcast#.episodes.edit', + 'filter' => 'permission:podcast$1.episodes.edit', ], ); $routes->get( @@ -372,7 +364,7 @@ $routes->group( 'EpisodeController::chaptersDelete/$1/$2', [ 'as' => 'chapters-delete', - 'filter' => 'permission:podcast#.episodes.edit', + 'filter' => 'permission:podcast$1.episodes.edit', ], ); $routes->get( @@ -380,31 +372,31 @@ $routes->group( 'SoundbiteController::list/$1/$2', [ 'as' => 'soundbites-list', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'soundbites/new', - 'SoundbiteController::create/$1/$2', + 'SoundbiteController::createView/$1/$2', [ 'as' => 'soundbites-create', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->post( 'soundbites/new', - 'SoundbiteController::attemptCreate/$1/$2', + 'SoundbiteController::createAction/$1/$2', [ 'as' => 'soundbites-create', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'soundbites/(:num)/delete', - 'SoundbiteController::delete/$1/$2/$3', + 'SoundbiteController::deleteAction/$1/$2/$3', [ 'as' => 'soundbites-delete', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( @@ -412,23 +404,23 @@ $routes->group( 'VideoClipsController::list/$1/$2', [ 'as' => 'video-clips-list', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'video-clips/new', - 'VideoClipsController::create/$1/$2', + 'VideoClipsController::createView/$1/$2', [ 'as' => 'video-clips-create', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->post( 'video-clips/new', - 'VideoClipsController::attemptCreate/$1/$2', + 'VideoClipsController::createAction/$1/$2', [ 'as' => 'video-clips-create', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( @@ -436,78 +428,78 @@ $routes->group( 'VideoClipsController::view/$1/$2/$3', [ 'as' => 'video-clip', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'video-clips/(:num)/retry', - 'VideoClipsController::retry/$1/$2/$3', + 'VideoClipsController::retryAction/$1/$2/$3', [ 'as' => 'video-clip-retry', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'video-clips/(:num)/delete', - 'VideoClipsController::delete/$1/$2/$3', + 'VideoClipsController::deleteAction/$1/$2/$3', [ 'as' => 'video-clip-delete', - 'filter' => 'permission:podcast#.episodes.manage-clips', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'embed', - 'EpisodeController::embed/$1/$2', + 'EpisodeController::embedView/$1/$2', [ 'as' => 'embed-add', - 'filter' => 'permission:podcast#.episodes.edit', + 'filter' => 'permission:podcast$1.episodes.edit', ], ); $routes->group('persons', static function ($routes): void { $routes->get('/', 'EpisodePersonController::index/$1/$2', [ 'as' => 'episode-persons-manage', - 'filter' => 'permission:podcast#.episodes.manage-persons', + 'filter' => 'permission:podcast$1.episodes.manage-persons', ]); $routes->post( '/', - 'EpisodePersonController::attemptCreate/$1/$2', + 'EpisodePersonController::createAction/$1/$2', [ - 'filter' => 'permission:podcast#.episodes.manage-persons', + 'filter' => 'permission:podcast$1.episodes.manage-persons', ], ); $routes->get( '(:num)/remove', - 'EpisodePersonController::remove/$1/$2/$3', + 'EpisodePersonController::deleteAction/$1/$2/$3', [ 'as' => 'episode-person-remove', - 'filter' => 'permission:podcast#.episodes.manage-persons', + 'filter' => 'permission:podcast$1.episodes.manage-persons', ], ); }); $routes->group('comments', static function ($routes): void { $routes->post( 'new', - 'EpisodeController::attemptCommentCreate/$1/$2', + 'EpisodeController::commentCreateAction/$1/$2', [ 'as' => 'comment-attempt-create', - 'filter' => 'permission:podcast#.episodes.manage-comments', - ] + 'filter' => 'permission:podcast$1.episodes.manage-comments', + ], ); $routes->post( '(:uuid)/reply', - 'EpisodeController::attemptCommentReply/$1/$2/$3', + 'EpisodeController::commentReplyAction/$1/$2/$3', [ 'as' => 'comment-attempt-reply', - 'filter' => 'permission:podcast#.episodes.manage-comments', - ] + 'filter' => 'permission:podcast$1.episodes.manage-comments', + ], ); $routes->post( 'delete', - 'EpisodeController::attemptCommentDelete/$1/$2', + 'EpisodeController::commentDeleteAction/$1/$2', [ 'as' => 'comment-attempt-delete', - 'filter' => 'permission:podcast#.episodes.manage-comments', - ] + 'filter' => 'permission:podcast$1.episodes.manage-comments', + ], ); }); }); @@ -516,15 +508,15 @@ $routes->group( $routes->group('notifications', static function ($routes): void { $routes->get('/', 'NotificationController::list/$1', [ 'as' => 'notification-list', - 'filter' => 'permission:podcast#.manage-notifications', + 'filter' => 'permission:podcast$1.manage-notifications', ]); - $routes->get('(:num)/mark-as-read', 'NotificationController::markAsRead/$1/$2', [ + $routes->get('(:num)/mark-as-read', 'NotificationController::markAsReadAction/$1/$2', [ 'as' => 'notification-mark-as-read', - 'filter' => 'permission:podcast#.manage-notifications', + 'filter' => 'permission:podcast$1.manage-notifications', ]); - $routes->get('mark-all-as-read', 'NotificationController::markAllAsRead/$1', [ + $routes->get('mark-all-as-read', 'NotificationController::markAllAsReadAction/$1', [ 'as' => 'notification-mark-all-as-read', - 'filter' => 'permission:podcast#.manage-notifications', + 'filter' => 'permission:podcast$1.manage-notifications', ]); }); }); @@ -536,7 +528,7 @@ $routes->group( ]); $routes->get( 'blocked-actors', - 'FediverseController::blockedActors', + 'FediverseController::blockedActorsView', [ 'as' => 'fediverse-blocked-actors', 'filter' => 'permission:fediverse.manage-blocks', @@ -544,7 +536,7 @@ $routes->group( ); $routes->get( 'blocked-domains', - 'FediverseController::blockedDomains', + 'FediverseController::blockedDomainsView', [ 'as' => 'fediverse-blocked-domains', 'filter' => 'permission:fediverse.manage-blocks', @@ -557,25 +549,25 @@ $routes->group( 'as' => 'page-list', 'filter' => 'permission:pages.manage', ]); - $routes->get('new', 'PageController::create', [ + $routes->get('new', 'PageController::createView', [ 'as' => 'page-create', 'filter' => 'permission:pages.manage', ]); - $routes->post('new', 'PageController::attemptCreate', [ + $routes->post('new', 'PageController::createAction', [ 'filter' => 'permission:pages.manage', ]); $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'PageController::view/$1', [ 'as' => 'page-view', ]); - $routes->get('edit', 'PageController::edit/$1', [ + $routes->get('edit', 'PageController::editView/$1', [ 'as' => 'page-edit', 'filter' => 'permission:pages.manage', ]); - $routes->post('edit', 'PageController::attemptEdit/$1', [ + $routes->post('edit', 'PageController::editAction/$1', [ 'filter' => 'permission:pages.manage', ]); - $routes->get('delete', 'PageController::delete/$1', [ + $routes->get('delete', 'PageController::deleteAction/$1', [ 'as' => 'page-delete', 'filter' => 'permission:pages.manage', ]); diff --git a/modules/Admin/Controllers/AboutController.php b/modules/Admin/Controllers/AboutController.php index a11ffc89..b61abf29 100644 --- a/modules/Admin/Controllers/AboutController.php +++ b/modules/Admin/Controllers/AboutController.php @@ -24,6 +24,7 @@ class AboutController extends BaseController 'languages' => implode(', ', config('App')->supportedLocales), ]; + $this->setHtmlHead(lang('AboutCastopod.title')); return view('settings/about', [ 'info' => $instanceInfo, ]); diff --git a/modules/Admin/Controllers/BaseController.php b/modules/Admin/Controllers/BaseController.php index a97b8bdc..1dc5d3bc 100644 --- a/modules/Admin/Controllers/BaseController.php +++ b/modules/Admin/Controllers/BaseController.php @@ -4,18 +4,25 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; +use App\Libraries\HtmlHead; use CodeIgniter\Controller; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use Override; use Psr\Log\LoggerInterface; use ViewThemes\Theme; /** * BaseController provides a convenient place for loading components and performing functions that are needed by all - * your controllers. Extend this class in any new controllers: class Home extends BaseController + * your controllers. * - * For security be sure to declare any new methods as protected or private. + * Extend this class in any new controllers: + * ``` + * class Home extends BaseController + * ``` + * + * For security, be sure to declare any new methods as protected or private. */ abstract class BaseController extends Controller @@ -27,11 +34,14 @@ abstract class BaseController extends Controller */ protected $request; + #[Override] public function initController( RequestInterface $request, ResponseInterface $response, - LoggerInterface $logger + LoggerInterface $logger, ): void { + // Load here all helpers you want to be available in your controllers that extend BaseController. + // Caution: Do not put the this below the parent::initController() call below. $this->helpers = [...$this->helpers, 'auth', 'breadcrumb', 'svg', 'components', 'misc']; // Do Not Edit This Line @@ -39,4 +49,16 @@ abstract class BaseController extends Controller Theme::setTheme('admin'); } + + protected function setHtmlHead(string $title): void + { + /** @var HtmlHead $head */ + $head = service('html_head'); + + $head + ->title($title . ' | Castopod Admin') + ->description( + 'Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.', + ); + } } diff --git a/modules/Admin/Controllers/DashboardController.php b/modules/Admin/Controllers/DashboardController.php index 7d6f4821..725412a3 100644 --- a/modules/Admin/Controllers/DashboardController.php +++ b/modules/Admin/Controllers/DashboardController.php @@ -20,32 +20,37 @@ class DashboardController extends BaseController public function index(): string { $podcastsData = []; - $podcastsCount = (new PodcastModel())->builder() + $podcastsCount = new PodcastModel() + ->builder() ->countAll(); - $podcastsLastPublishedAt = (new PodcastModel())->builder() + $podcastsLastPublishedAt = new PodcastModel() + ->builder() ->selectMax('published_at', 'last_published_at') ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->get() ->getResultArray()[0]['last_published_at']; $podcastsData['number_of_podcasts'] = (int) $podcastsCount; $podcastsData['last_published_at'] = $podcastsLastPublishedAt === null ? null : new Time( - $podcastsLastPublishedAt + $podcastsLastPublishedAt, ); $episodesData = []; - $episodesCount = (new EpisodeModel())->builder() + $episodesCount = new EpisodeModel() + ->builder() ->countAll(); - $episodesLastPublishedAt = (new EpisodeModel())->builder() + $episodesLastPublishedAt = new EpisodeModel() + ->builder() ->selectMax('published_at', 'last_published_at') ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->get() ->getResultArray()[0]['last_published_at']; $episodesData['number_of_episodes'] = (int) $episodesCount; $episodesData['last_published_at'] = $episodesLastPublishedAt === null ? null : new Time( - $episodesLastPublishedAt + $episodesLastPublishedAt, ); - $totalUploaded = (new MediaModel())->builder() + $totalUploaded = new MediaModel() + ->builder() ->selectSum('file_size') ->get() ->getResultArray()[0]; @@ -66,7 +71,8 @@ class DashboardController extends BaseController $onlyPodcastId = null; if ($podcastsData['number_of_podcasts'] === 1) { - $onlyPodcastId = (new PodcastModel())->first() + $onlyPodcastId = new PodcastModel() + ->first() ->id; } @@ -81,6 +87,7 @@ class DashboardController extends BaseController 'onlyPodcastId' => $onlyPodcastId, ]; + $this->setHtmlHead(lang('Dashboard.home')); return view('dashboard', $data); } } diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php index f3b3bbc6..7319afd6 100644 --- a/modules/Admin/Controllers/EpisodeController.php +++ b/modules/Admin/Controllers/EpisodeController.php @@ -35,36 +35,31 @@ class EpisodeController extends BaseController public function _remap(string $method, string ...$params): mixed { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + + if (count($params) === 1) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + return $this->{$method}($podcast); + } + if ( - ! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast + ! ($episode = new EpisodeModel()->getEpisodeById((int) $params[1])) instanceof Episode ) { throw PageNotFoundException::forPageNotFound(); } - $this->podcast = $podcast; + unset($params[0]); + unset($params[1]); - if (count($params) > 1) { - if ( - ! ($episode = (new EpisodeModel()) - ->where([ - 'id' => $params[1], - 'podcast_id' => $params[0], - ]) - ->first()) instanceof Episode - ) { - throw PageNotFoundException::forPageNotFound(); - } - - $this->episode = $episode; - - unset($params[1]); - unset($params[0]); - } - - return $this->{$method}(...$params); + return $this->{$method}($episode, ...$params); } - public function list(): string + public function list(Podcast $podcast): string { /** @var ?string $query */ $query = $this->request->getGet('q'); @@ -75,7 +70,7 @@ class EpisodeController extends BaseController // Use LIKE operator as a fallback. if (strlen($query) < 4) { $episodes = $episodeModel - ->where('podcast_id', $this->podcast->id) + ->where('podcast_id', $podcast->id) ->like('title', $episodeModel->db->escapeLikeString($query)) ->orLike('description_markdown', $episodeModel->db->escapeLikeString($query)) ->orLike('slug', $episodeModel->db->escapeLikeString($query)) @@ -84,68 +79,71 @@ class EpisodeController extends BaseController ->orderBy('created_at', 'desc'); } else { $episodes = $episodeModel - ->where('podcast_id', $this->podcast->id) + ->where('podcast_id', $podcast->id) ->where( "MATCH (title, description_markdown, slug, location_name) AGAINST ('{$episodeModel->db->escapeString( - $query - )}')" + $query, + )}')", ); } } else { $episodes = $episodeModel - ->where('podcast_id', $this->podcast->id) + ->where('podcast_id', $podcast->id) ->orderBy('-`published_at`', '', false) ->orderBy('created_at', 'desc'); } helper('form'); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, 'episodes' => $episodes->paginate(10), 'pager' => $episodes->pager, 'query' => $query, ]; + $this->setHtmlHead(lang('Episode.all_podcast_episodes')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('episode/list', $data); } - public function view(): string + public function view(Episode $episode): string { $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead($episode->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/view', $data); } - public function create(): string + public function createView(Podcast $podcast): string { helper(['form']); - $currentSeasonNumber = (new EpisodeModel())->getCurrentSeasonNumber($this->podcast->id); + $currentSeasonNumber = new EpisodeModel() + ->getCurrentSeasonNumber($podcast->id); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, 'currentSeasonNumber' => $currentSeasonNumber, - 'nextEpisodeNumber' => (new EpisodeModel())->getNextEpisodeNumber( - $this->podcast->id, - $currentSeasonNumber - ), + 'nextEpisodeNumber' => new EpisodeModel() + ->getNextEpisodeNumber($podcast->id, $currentSeasonNumber), ]; + + $this->setHtmlHead(lang('Episode.create')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('episode/create', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(Podcast $podcast): RedirectResponse { $rules = [ 'title' => 'required', @@ -156,7 +154,7 @@ class EpisodeController extends BaseController 'chapters_file' => 'ext_in[chapters_file,json]|is_json[chapters_file]', ]; - if ($this->podcast->type === 'serial' && $this->request->getPost('type') === 'full') { + if ($podcast->type === 'serial' && $this->request->getPost('type') === 'full') { $rules['episode_number'] = 'required'; } @@ -169,10 +167,10 @@ class EpisodeController extends BaseController $validData = $this->validator->getValidated(); - if ((new EpisodeModel()) + if (new EpisodeModel() ->where([ 'slug' => $validData['slug'], - 'podcast_id' => $this->podcast->id, + 'podcast_id' => $podcast->id, ]) ->first() instanceof Episode) { return redirect() @@ -181,13 +179,10 @@ class EpisodeController extends BaseController ->with('error', lang('Episode.messages.sameSlugError')); } - $db = db_connect(); - $db->transStart(); - $newEpisode = new Episode([ 'created_by' => user_id(), 'updated_by' => user_id(), - 'podcast_id' => $this->podcast->id, + 'podcast_id' => $podcast->id, 'title' => $this->request->getPost('title'), 'slug' => $this->request->getPost('slug'), 'guid' => null, @@ -195,7 +190,7 @@ class EpisodeController extends BaseController 'cover' => $this->request->getFile('cover'), 'description_markdown' => $this->request->getPost('description'), 'location' => $this->request->getPost('location_name') === '' ? null : new Location( - $this->request->getPost('location_name') + $this->request->getPost('location_name'), ), 'transcript' => $this->request->getFile('transcript'), 'chapters' => $this->request->getFile('chapters'), @@ -208,11 +203,10 @@ class EpisodeController extends BaseController 'season_number' => $this->request->getPost('season_number') ? (int) $this->request->getPost('season_number') : null, - 'type' => $this->request->getPost('type'), - 'is_blocked' => $this->request->getPost('block') === 'yes', - 'custom_rss_string' => $this->request->getPost('custom_rss'), - 'is_premium' => $this->request->getPost('premium') === 'yes', - 'published_at' => null, + 'type' => $this->request->getPost('type'), + 'is_blocked' => $this->request->getPost('block') === 'yes', + 'is_premium' => $this->request->getPost('premium') === 'yes', + 'published_at' => null, ]); $transcriptChoice = $this->request->getPost('transcript-choice'); @@ -220,7 +214,7 @@ class EpisodeController extends BaseController $newEpisode->setTranscript($this->request->getFile('transcript_file')); } elseif ($transcriptChoice === 'remote-url') { $newEpisode->transcript_remote_url = $this->request->getPost( - 'transcript_remote_url' + 'transcript_remote_url', ) === '' ? null : $this->request->getPost('transcript_remote_url'); } @@ -229,61 +223,42 @@ class EpisodeController extends BaseController $newEpisode->setChapters($this->request->getFile('chapters_file')); } elseif ($chaptersChoice === 'remote-url') { $newEpisode->chapters_remote_url = $this->request->getPost( - 'chapters_remote_url' + 'chapters_remote_url', ) === '' ? null : $this->request->getPost('chapters_remote_url'); } $episodeModel = new EpisodeModel(); if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) { - $db->transRollback(); return redirect() ->back() ->withInput() ->with('errors', $episodeModel->errors()); } - // update podcast's episode_description_footer_markdown if changed - $this->podcast->episode_description_footer_markdown = $this->request->getPost( - 'description_footer' - ) === '' ? null : $this->request->getPost('description_footer'); - - if ($this->podcast->hasChanged('episode_description_footer_markdown')) { - $podcastModel = new PodcastModel(); - - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { - $db->transRollback(); - return redirect() - ->back() - ->withInput() - ->with('errors', $podcastModel->errors()); - } - } - - $db->transComplete(); - - return redirect()->route('episode-view', [$this->podcast->id, $newEpisodeId])->with( + return redirect()->route('episode-view', [$podcast->id, $newEpisodeId])->with( 'message', - lang('Episode.messages.createSuccess') + lang('Episode.messages.createSuccess'), ); } - public function edit(): string + public function editView(Episode $episode): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.edit')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(Episode $episode): RedirectResponse { $rules = [ 'title' => 'required', @@ -294,7 +269,7 @@ class EpisodeController extends BaseController 'chapters_file' => 'ext_in[chapters_file,json]|is_json[chapters_file]', ]; - if ($this->podcast->type === 'serial' && $this->request->getPost('type') === 'full') { + if ($episode->podcast->type === 'serial' && $this->request->getPost('type') === 'full') { $rules['episode_number'] = 'required'; } @@ -307,113 +282,89 @@ class EpisodeController extends BaseController $validData = $this->validator->getValidated(); - $this->episode->title = $this->request->getPost('title'); - $this->episode->slug = $validData['slug']; - $this->episode->description_markdown = $this->request->getPost('description'); - $this->episode->location = $this->request->getPost('location_name') === '' ? null : new Location( - $this->request->getPost('location_name') + $episode->title = $this->request->getPost('title'); + $episode->slug = $validData['slug']; + $episode->description_markdown = $this->request->getPost('description'); + $episode->location = $this->request->getPost('location_name') === '' ? null : new Location( + $this->request->getPost('location_name'), ); - $this->episode->parental_advisory = + $episode->parental_advisory = $this->request->getPost('parental_advisory') !== 'undefined' ? $this->request->getPost('parental_advisory') : null; - $this->episode->number = $this->request->getPost('episode_number') ?: null; - $this->episode->season_number = $this->request->getPost('season_number') ?: null; - $this->episode->type = $this->request->getPost('type'); - $this->episode->is_blocked = $this->request->getPost('block') === 'yes'; - $this->episode->custom_rss_string = $this->request->getPost('custom_rss'); - $this->episode->is_premium = $this->request->getPost('premium') === 'yes'; + $episode->number = $this->request->getPost('episode_number') ?: null; + $episode->season_number = $this->request->getPost('season_number') ?: null; + $episode->type = $this->request->getPost('type'); + $episode->is_blocked = $this->request->getPost('block') === 'yes'; + $episode->is_premium = $this->request->getPost('premium') === 'yes'; - $this->episode->updated_by = (int) user_id(); - $this->episode->setAudio($this->request->getFile('audio_file')); - $this->episode->setCover($this->request->getFile('cover')); + $episode->updated_by = (int) user_id(); + $episode->setAudio($this->request->getFile('audio_file')); + $episode->setCover($this->request->getFile('cover')); // republish on websub hubs upon edit - $this->episode->is_published_on_hubs = false; + $episode->is_published_on_hubs = false; $transcriptChoice = $this->request->getPost('transcript-choice'); if ($transcriptChoice === 'upload-file') { $transcriptFile = $this->request->getFile('transcript_file'); if ($transcriptFile instanceof UploadedFile && $transcriptFile->isValid()) { - $this->episode->setTranscript($transcriptFile); - $this->episode->transcript_remote_url = null; + $episode->setTranscript($transcriptFile); + $episode->transcript_remote_url = null; } } elseif ($transcriptChoice === 'remote-url') { if ( ($transcriptRemoteUrl = $this->request->getPost('transcript_remote_url')) && - (($transcriptFile = $this->episode->transcript_id) !== null) + (($transcriptFile = $episode->transcript_id) !== null) ) { - (new MediaModel())->deleteMedia($this->episode->transcript); + new MediaModel() + ->deleteMedia($episode->transcript); } - $this->episode->transcript_remote_url = $transcriptRemoteUrl === '' ? null : $transcriptRemoteUrl; + $episode->transcript_remote_url = $transcriptRemoteUrl === '' ? null : $transcriptRemoteUrl; } $chaptersChoice = $this->request->getPost('chapters-choice'); if ($chaptersChoice === 'upload-file') { $chaptersFile = $this->request->getFile('chapters_file'); if ($chaptersFile instanceof UploadedFile && $chaptersFile->isValid()) { - $this->episode->setChapters($chaptersFile); - $this->episode->chapters_remote_url = null; + $episode->setChapters($chaptersFile); + $episode->chapters_remote_url = null; } } elseif ($chaptersChoice === 'remote-url') { if ( ($chaptersRemoteUrl = $this->request->getPost('chapters_remote_url')) && - (($chaptersFile = $this->episode->chapters) instanceof Chapters) + (($chaptersFile = $episode->chapters) instanceof Chapters) ) { - (new MediaModel())->deleteMedia($this->episode->chapters); + new MediaModel() + ->deleteMedia($episode->chapters); } - $this->episode->chapters_remote_url = $chaptersRemoteUrl === '' ? null : $chaptersRemoteUrl; + $episode->chapters_remote_url = $chaptersRemoteUrl === '' ? null : $chaptersRemoteUrl; } - $db = db_connect(); - $db->transStart(); - $episodeModel = new EpisodeModel(); - - if (! $episodeModel->update($this->episode->id, $this->episode)) { - $db->transRollback(); - + if (! $episodeModel->update($episode->id, $episode)) { return redirect() ->back() ->withInput() ->with('errors', $episodeModel->errors()); } - // update podcast's episode_description_footer_markdown if changed - $this->podcast->episode_description_footer_markdown = $this->request->getPost( - 'description_footer' - ) === '' ? null : $this->request->getPost('description_footer'); - - if ($this->podcast->hasChanged('episode_description_footer_markdown')) { - $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { - $db->transRollback(); - - return redirect() - ->back() - ->withInput() - ->with('errors', $podcastModel->errors()); - } - } - - $db->transComplete(); - - return redirect()->route('episode-edit', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-edit', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Episode.messages.editSuccess') + lang('Episode.messages.editSuccess'), ); } - public function transcriptDelete(): RedirectResponse + public function transcriptDelete(Episode $episode): RedirectResponse { - if (! $this->episode->transcript instanceof Transcript) { + if (! $episode->transcript instanceof Transcript) { return redirect()->back(); } $mediaModel = new MediaModel(); - if (! $mediaModel->deleteMedia($this->episode->transcript)) { + if (! $mediaModel->deleteMedia($episode->transcript)) { return redirect() ->back() ->withInput() @@ -423,14 +374,14 @@ class EpisodeController extends BaseController return redirect()->back(); } - public function chaptersDelete(): RedirectResponse + public function chaptersDelete(Episode $episode): RedirectResponse { - if (! $this->episode->chapters instanceof Chapters) { + if (! $episode->chapters instanceof Chapters) { return redirect()->back(); } $mediaModel = new MediaModel(); - if (! $mediaModel->deleteMedia($this->episode->chapters)) { + if (! $mediaModel->deleteMedia($episode->chapters)) { return redirect() ->back() ->withInput() @@ -440,32 +391,33 @@ class EpisodeController extends BaseController return redirect()->back(); } - public function publish(): string | RedirectResponse + public function publishView(Episode $episode): string | RedirectResponse { - if ($this->episode->publication_status === 'not_published') { + if ($episode->publication_status === 'not_published') { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.publish')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/publish', $data); } - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'error', - lang('Episode.publish_error') + lang('Episode.publish_error'), ); } - public function attemptPublish(): RedirectResponse + public function publishAction(Episode $episode): RedirectResponse { - if ($this->podcast->publication_status === 'published') { + if ($episode->podcast->publication_status === 'published') { $rules = [ 'publication_method' => 'required', 'scheduled_publication_date' => 'valid_date[Y-m-d H:i]|permit_empty', @@ -483,18 +435,18 @@ class EpisodeController extends BaseController $db->transStart(); $newPost = new Post([ - 'actor_id' => $this->podcast->actor_id, - 'episode_id' => $this->episode->id, + 'actor_id' => $episode->podcast->actor_id, + 'episode_id' => $episode->id, 'message' => $this->request->getPost('message'), 'created_by' => user_id(), ]); - if ($this->podcast->publication_status === 'published') { + if ($episode->podcast->publication_status === 'published') { $publishMethod = $this->request->getPost('publication_method'); if ($publishMethod === 'schedule') { $scheduledPublicationDate = $this->request->getPost('scheduled_publication_date'); if ($scheduledPublicationDate) { - $this->episode->published_at = Time::createFromFormat( + $episode->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, $this->request->getPost('client_timezone'), @@ -507,16 +459,16 @@ class EpisodeController extends BaseController ->with('error', lang('Episode.messages.scheduleDateError')); } } else { - $this->episode->published_at = Time::now(); + $episode->published_at = Time::now(); } - } elseif ($this->podcast->publication_status === 'scheduled') { + } elseif ($episode->podcast->publication_status === 'scheduled') { // podcast publication date has already been set - $this->episode->published_at = $this->podcast->published_at->addSeconds(1); + $episode->published_at = $episode->podcast->published_at->addSeconds(1); } else { - $this->episode->published_at = Time::now(); + $episode->published_at = Time::now(); } - $newPost->published_at = $this->episode->published_at; + $newPost->published_at = $episode->published_at; $postModel = new PostModel(); if (! $postModel->addPost($newPost)) { @@ -528,7 +480,7 @@ class EpisodeController extends BaseController } $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { $db->transRollback(); return redirect() ->back() @@ -538,46 +490,47 @@ class EpisodeController extends BaseController $db->transComplete(); - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'message', lang('Episode.messages.publishSuccess', [ - 'publication_status' => $this->episode->publication_status, - ]) + 'publication_status' => $episode->publication_status, + ]), ); } - public function publishEdit(): string | RedirectResponse + public function publishEditView(Episode $episode): string | RedirectResponse { - if (in_array($this->episode->publication_status, ['scheduled', 'with_podcast'], true)) { + if (in_array($episode->publication_status, ['scheduled', 'with_podcast'], true)) { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, - 'post' => (new PostModel()) + 'podcast' => $episode->podcast, + 'episode' => $episode, + 'post' => new PostModel() ->where([ - 'actor_id' => $this->podcast->actor_id, - 'episode_id' => $this->episode->id, + 'actor_id' => $episode->podcast->actor_id, + 'episode_id' => $episode->id, ]) ->first(), ]; + $this->setHtmlHead(lang('Episode.publish_edit')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/publish_edit', $data); } - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'error', - lang('Episode.publish_edit_error') + lang('Episode.publish_edit_error'), ); } - public function attemptPublishEdit(): RedirectResponse + public function publishEditAction(Episode $episode): RedirectResponse { - if ($this->podcast->publication_status === 'published') { + if ($episode->podcast->publication_status === 'published') { $rules = [ 'post_id' => 'required', 'publication_method' => 'required', @@ -595,12 +548,12 @@ class EpisodeController extends BaseController $db = db_connect(); $db->transStart(); - if ($this->podcast->publication_status === 'published') { + if ($episode->podcast->publication_status === 'published') { $publishMethod = $this->request->getPost('publication_method'); if ($publishMethod === 'schedule') { $scheduledPublicationDate = $this->request->getPost('scheduled_publication_date'); if ($scheduledPublicationDate) { - $this->episode->published_at = Time::createFromFormat( + $episode->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, $this->request->getPost('client_timezone'), @@ -613,20 +566,21 @@ class EpisodeController extends BaseController ->with('error', lang('Episode.messages.scheduleDateError')); } } else { - $this->episode->published_at = Time::now(); + $episode->published_at = Time::now(); } - } elseif ($this->podcast->publication_status === 'scheduled') { + } elseif ($episode->podcast->publication_status === 'scheduled') { // podcast publication date has already been set - $this->episode->published_at = $this->podcast->published_at->addSeconds(1); + $episode->published_at = $episode->podcast->published_at->addSeconds(1); } else { - $this->episode->published_at = Time::now(); + $episode->published_at = Time::now(); } - $post = (new PostModel())->getPostById($this->request->getPost('post_id')); + $post = new PostModel() + ->getPostById($this->request->getPost('post_id')); if ($post instanceof Post) { $post->message = $this->request->getPost('message'); - $post->published_at = $this->episode->published_at; + $post->published_at = $episode->published_at; $postModel = new PostModel(); if (! $postModel->editPost($post)) { @@ -639,7 +593,7 @@ class EpisodeController extends BaseController } $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { $db->transRollback(); return redirect() ->back() @@ -649,33 +603,33 @@ class EpisodeController extends BaseController $db->transComplete(); - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'message', lang('Episode.messages.publishSuccess', [ - 'publication_status' => $this->episode->publication_status, - ]) + 'publication_status' => $episode->publication_status, + ]), ); } - public function publishCancel(): RedirectResponse + public function publishCancelAction(Episode $episode): RedirectResponse { - if (in_array($this->episode->publication_status, ['scheduled', 'with_podcast'], true)) { + if (in_array($episode->publication_status, ['scheduled', 'with_podcast'], true)) { $db = db_connect(); $db->transStart(); $postModel = new PostModel(); $post = $postModel ->where([ - 'actor_id' => $this->podcast->actor_id, - 'episode_id' => $this->episode->id, + 'actor_id' => $episode->podcast->actor_id, + 'episode_id' => $episode->id, ]) ->first(); $postModel->removePost($post); - $this->episode->published_at = null; + $episode->published_at = null; $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { $db->transRollback(); return redirect() ->back() @@ -685,37 +639,37 @@ class EpisodeController extends BaseController $db->transComplete(); - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Episode.messages.publishCancelSuccess') + lang('Episode.messages.publishCancelSuccess'), ); } - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]); + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id]); } - public function publishDateEdit(): string|RedirectResponse + public function publishDateEditView(Episode $episode): string|RedirectResponse { // only accessible if episode is already published - if ($this->episode->publication_status !== 'published') { - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + if ($episode->publication_status !== 'published') { + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'error', - lang('Episode.publish_date_edit_error') + lang('Episode.publish_date_edit_error'), ); } helper('form'); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.publish_date_edit')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->title, + 1 => $episode->title, ]); - return view('episode/publish_date_edit', $data); } @@ -725,7 +679,7 @@ class EpisodeController extends BaseController * Prevents setting a future date as it does not make sense to set a future published date to an already published * episode. This also prevents any side-effects from occurring. */ - public function attemptPublishDateEdit(): RedirectResponse + public function publishDateEditAction(Episode $episode): RedirectResponse { $rules = [ 'new_publication_date' => 'valid_date[Y-m-d H:i]', @@ -755,46 +709,47 @@ class EpisodeController extends BaseController ->with('error', lang('Episode.publish_date_edit_future_error')); } - $this->episode->published_at = $newPublicationDate; + $episode->published_at = $newPublicationDate; $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { return redirect() ->back() ->withInput() ->with('errors', $episodeModel->errors()); } - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Episode.publish_date_edit_success') + lang('Episode.publish_date_edit_success'), ); } - public function unpublish(): string | RedirectResponse + public function unpublishView(Episode $episode): string | RedirectResponse { - if ($this->episode->publication_status !== 'published') { - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + if ($episode->publication_status !== 'published') { + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'error', - lang('Episode.unpublish_error') + lang('Episode.unpublish_error'), ); } helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.unpublish')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->title, + 1 => $episode->title, ]); return view('episode/unpublish', $data); } - public function attemptUnpublish(): RedirectResponse + public function unpublishAction(Episode $episode): RedirectResponse { $rules = [ 'understand' => 'required', @@ -811,32 +766,34 @@ class EpisodeController extends BaseController $db->transStart(); - $allPostsLinkedToEpisode = (new PostModel()) + $allPostsLinkedToEpisode = new PostModel() ->where([ - 'episode_id' => $this->episode->id, + 'episode_id' => $episode->id, 'in_reply_to_id' => null, 'reblog_of_id' => null, ]) ->findAll(); foreach ($allPostsLinkedToEpisode as $post) { - (new PostModel())->removePost($post); + new PostModel() + ->removePost($post); } - $allCommentsLinkedToEpisode = (new EpisodeCommentModel()) + $allCommentsLinkedToEpisode = new EpisodeCommentModel() ->where([ - 'episode_id' => $this->episode->id, + 'episode_id' => $episode->id, 'in_reply_to_id' => null, ]) ->findAll(); foreach ($allCommentsLinkedToEpisode as $comment) { - (new EpisodeCommentModel())->removeComment($comment); + new EpisodeCommentModel() + ->removeComment($comment); } // set episode published_at to null to unpublish - $this->episode->published_at = null; + $episode->published_at = null; $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { $db->transRollback(); return redirect() ->back() @@ -845,32 +802,34 @@ class EpisodeController extends BaseController } // set podcast is_published_on_hubs to false to trigger websub push - (new PodcastModel())->update($this->episode->podcast->id, [ - 'is_published_on_hubs' => 0, - ]); + new PodcastModel() + ->update($episode->podcast_id, [ + 'is_published_on_hubs' => 0, + ]); $db->transComplete(); - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]); + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id]); } - public function delete(): string + public function deleteView(Episode $episode): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.delete')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/delete', $data); } - public function attemptDelete(): RedirectResponse + public function deleteAction(Episode $episode): RedirectResponse { $rules = [ 'understand' => 'required', @@ -883,7 +842,7 @@ class EpisodeController extends BaseController ->with('errors', $this->validator->getErrors()); } - if ($this->episode->published_at instanceof Time) { + if ($episode->published_at instanceof Time) { return redirect() ->back() ->withInput() @@ -896,7 +855,7 @@ class EpisodeController extends BaseController $episodeModel = new EpisodeModel(); - if (! $episodeModel->delete($this->episode->id)) { + if (! $episodeModel->delete($episode->id)) { $db->transRollback(); return redirect() ->back() @@ -904,11 +863,11 @@ class EpisodeController extends BaseController ->with('errors', $episodeModel->errors()); } - $episodeMediaList = [$this->episode->transcript, $this->episode->chapters, $this->episode->audio]; + $episodeMediaList = [$episode->transcript, $episode->chapters, $episode->audio]; //only delete episode cover if different from podcast's - if ($this->episode->cover_id !== null) { - $episodeMediaList[] = $this->episode->cover; + if ($episode->cover_id !== null) { + $episodeMediaList[] = $episode->cover; } $mediaModel = new MediaModel(); @@ -948,35 +907,36 @@ class EpisodeController extends BaseController if ($warnings !== []) { return redirect() - ->route('episode-list', [$this->podcast->id]) + ->route('episode-list', [$episode->podcast_id]) ->with('message', lang('Episode.messages.deleteSuccess')) ->with('warnings', $warnings); } - return redirect()->route('episode-list', [$this->podcast->id])->with( + return redirect()->route('episode-list', [$episode->podcast_id])->with( 'message', - lang('Episode.messages.deleteSuccess') + lang('Episode.messages.deleteSuccess'), ); } - public function embed(): string + public function embedView(Episode $episode): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, 'themes' => EpisodeModel::$themes, ]; + $this->setHtmlHead(lang('Episode.embed.title')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/embed', $data); } - public function attemptCommentCreate(): RedirectResponse + public function commentCreateAction(Episode $episode): RedirectResponse { $rules = [ 'message' => 'required|max_length[500]', @@ -993,7 +953,7 @@ class EpisodeController extends BaseController $newComment = new EpisodeComment([ 'actor_id' => interact_as_actor_id(), - 'episode_id' => $this->episode->id, + 'episode_id' => $episode->id, 'message' => $validData['message'], 'created_at' => new Time('now'), 'created_by' => user_id(), @@ -1013,7 +973,7 @@ class EpisodeController extends BaseController return redirect()->back(); } - public function attemptCommentReply(string $commentId): RedirectResponse + public function commentReplyAction(Episode $episode, string $commentId): RedirectResponse { $rules = [ 'message' => 'required|max_length[500]', @@ -1030,7 +990,7 @@ class EpisodeController extends BaseController $newReply = new EpisodeComment([ 'actor_id' => interact_as_actor_id(), - 'episode_id' => $this->episode->id, + 'episode_id' => $episode->id, 'message' => $validData['message'], 'in_reply_to_id' => $commentId, 'created_at' => new Time('now'), diff --git a/modules/Admin/Controllers/EpisodePersonController.php b/modules/Admin/Controllers/EpisodePersonController.php index 8a6bcae5..dd8c8e4a 100644 --- a/modules/Admin/Controllers/EpisodePersonController.php +++ b/modules/Admin/Controllers/EpisodePersonController.php @@ -20,52 +20,54 @@ use CodeIgniter\HTTP\RedirectResponse; class EpisodePersonController extends BaseController { - protected Podcast $podcast; - - protected Episode $episode; - public function _remap(string $method, string ...$params): mixed { - if (count($params) < 2) { + if ($params === []) { throw PageNotFoundException::forPageNotFound(); } - if ( - ($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) && - ($this->episode = (new EpisodeModel()) - ->where([ - 'id' => $params[1], - 'podcast_id' => $params[0], - ]) - ->first()) - ) { - unset($params[1]); - unset($params[0]); + if (count($params) === 1) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } - return $this->{$method}(...$params); + return $this->{$method}($podcast); } - throw PageNotFoundException::forPageNotFound(); + if ( + ! ($episode = new EpisodeModel()->getEpisodeById((int) $params[1])) instanceof Episode + ) { + throw PageNotFoundException::forPageNotFound(); + } + + unset($params[0]); + unset($params[1]); + + return $this->{$method}($episode, ...$params); } - public function index(): string + public function index(Episode $episode): string { helper('form'); $data = [ - 'episode' => $this->episode, - 'podcast' => $this->podcast, - 'personOptions' => (new PersonModel())->getPersonOptions(), - 'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(), + 'episode' => $episode, + 'podcast' => $episode->podcast, + 'personOptions' => new PersonModel() + ->getPersonOptions(), + 'taxonomyOptions' => new PersonModel() + ->getTaxonomyOptions(), ]; + + $this->setHtmlHead(lang('Person.episode_form.title')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/persons', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(Episode $episode): RedirectResponse { $rules = [ 'persons' => 'required', @@ -80,19 +82,21 @@ class EpisodePersonController extends BaseController $validData = $this->validator->getValidated(); - (new PersonModel())->addEpisodePersons( - $this->podcast->id, - $this->episode->id, - $validData['persons'], - $this->request->getPost('roles') ?? [], - ); + new PersonModel() + ->addEpisodePersons( + $episode->podcast_id, + $episode->id, + $validData['persons'], + $this->request->getPost('roles') ?? [], + ); return redirect()->back(); } - public function remove(string $personId): RedirectResponse + public function deleteAction(Episode $episode, string $personId): RedirectResponse { - (new PersonModel())->removePersonFromEpisode($this->podcast->id, $this->episode->id, (int) $personId); + new PersonModel() + ->removePersonFromEpisode($episode->podcast_id, $episode->id, (int) $personId); return redirect()->back(); } diff --git a/modules/Admin/Controllers/FediverseController.php b/modules/Admin/Controllers/FediverseController.php index 889c9958..55b9ffcd 100644 --- a/modules/Admin/Controllers/FediverseController.php +++ b/modules/Admin/Controllers/FediverseController.php @@ -19,25 +19,27 @@ class FediverseController extends BaseController return redirect()->route('fediverse-blocked-actors'); } - public function blockedActors(): string + public function blockedActorsView(): string { helper(['form']); $blockedActors = model('ActorModel', false) ->getBlockedActors(); + $this->setHtmlHead(lang('Fediverse.blocked_actors')); return view('fediverse/blocked_actors', [ 'blockedActors' => $blockedActors, ]); } - public function blockedDomains(): string + public function blockedDomainsView(): string { helper(['form']); $blockedDomains = model('BlockedDomainModel', false) ->getBlockedDomains(); + $this->setHtmlHead(lang('Fediverse.blocked_domains')); return view('fediverse/blocked_domains', [ 'blockedDomains' => $blockedDomains, ]); diff --git a/modules/Admin/Controllers/NotificationController.php b/modules/Admin/Controllers/NotificationController.php index 36091773..06f0cea1 100644 --- a/modules/Admin/Controllers/NotificationController.php +++ b/modules/Admin/Controllers/NotificationController.php @@ -27,78 +27,79 @@ class NotificationController extends BaseController public function _remap(string $method, string ...$params): mixed { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + if ( - ! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast + ! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast ) { throw PageNotFoundException::forPageNotFound(); } - $this->podcast = $podcast; + $params[0] = $podcast; if (count($params) > 1) { if ( - ! ($notification = (new NotificationModel()) - ->where([ - 'id' => $params[1], - ]) - ->first()) instanceof Notification + ! ($notification = new NotificationModel()->find($params[1])) instanceof Notification ) { throw PageNotFoundException::forPageNotFound(); } - $this->notification = $notification; - - unset($params[1]); - unset($params[0]); + $params[1] = $notification; } return $this->{$method}(...$params); } - public function list(): string + public function list(Podcast $podcast): string { - $notifications = (new NotificationModel())->where('target_actor_id', $this->podcast->actor_id) + $notifications = new NotificationModel() + ->where('target_actor_id', $podcast->actor_id) ->orderBy('created_at', 'desc'); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, 'notifications' => $notifications->paginate(10), 'pager' => $notifications->pager, ]; + $this->setHtmlHead(lang('Notifications.title')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); - return view('podcast/notifications', $data); } - public function markAsRead(): RedirectResponse + public function markAllAsReadAction(Podcast $podcast): RedirectResponse { - $this->notification->read_at = new Time('now'); - $notificationModel = new NotificationModel(); - $notificationModel->update($this->notification->id, $this->notification); - - if ($this->notification->post_id === null) { - return redirect()->route('podcast-activity', [esc($this->podcast->handle)]); - } - - $post = (new PostModel())->getPostById($this->notification->post_id); - - return redirect()->route('post', [$this->podcast->handle, $post->id]); - } - - public function markAllAsRead(): RedirectResponse - { - $notifications = (new NotificationModel())->where('target_actor_id', $this->podcast->actor_id) + $notifications = new NotificationModel() + ->where('target_actor_id', $podcast->actor_id) ->where('read_at') ->findAll(); foreach ($notifications as $notification) { $notification->read_at = new Time('now'); - (new NotificationModel())->update($notification->id, $notification); + new NotificationModel() + ->update($notification->id, $notification); } return redirect()->back(); } + + public function markAsReadAction(Podcast $podcast, Notification $notification): RedirectResponse + { + $notification->read_at = new Time('now'); + $notificationModel = new NotificationModel(); + $notificationModel->update($notification->id, $notification); + + if ($notification->post_id === null) { + return redirect()->route('podcast-activity', [esc($podcast->handle)]); + } + + $post = new PostModel() + ->getPostById($notification->post_id); + + return redirect()->route('post', [$podcast->handle, $post->id]); + } } diff --git a/modules/Admin/Controllers/PageController.php b/modules/Admin/Controllers/PageController.php index f23bf242..8c406463 100644 --- a/modules/Admin/Controllers/PageController.php +++ b/modules/Admin/Controllers/PageController.php @@ -17,16 +17,14 @@ use CodeIgniter\HTTP\RedirectResponse; class PageController extends BaseController { - protected ?Page $page = null; - public function _remap(string $method, string ...$params): mixed { if ($params === []) { return $this->{$method}(); } - if (($this->page = (new PageModel())->find($params[0])) instanceof Page) { - return $this->{$method}(); + if (($page = new PageModel()->find($params[0])) instanceof Page) { + return $this->{$method}($page); } throw PageNotFoundException::forPageNotFound(); @@ -34,28 +32,32 @@ class PageController extends BaseController public function list(): string { + $this->setHtmlHead(lang('Page.all_pages')); $data = [ - 'pages' => (new PageModel())->findAll(), + 'pages' => new PageModel() + ->findAll(), ]; return view('page/list', $data); } - public function view(): string + public function view(Page $page): string { + $this->setHtmlHead($page->title); return view('page/view', [ - 'page' => $this->page, + 'page' => $page, ]); } - public function create(): string + public function createView(): string { helper('form'); + $this->setHtmlHead(lang('Page.create')); return view('page/create'); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { $page = new Page([ 'title' => $this->request->getPost('title'), @@ -79,39 +81,41 @@ class PageController extends BaseController ])); } - public function edit(): string + public function editView(Page $page): string { helper('form'); + $this->setHtmlHead(lang('Page.edit')); replace_breadcrumb_params([ - 0 => $this->page->title, + 0 => $page->title, ]); return view('page/edit', [ - 'page' => $this->page, + 'page' => $page, ]); } - public function attemptEdit(): RedirectResponse + public function editAction(Page $page): RedirectResponse { - $this->page->title = $this->request->getPost('title'); - $this->page->slug = $this->request->getPost('slug'); - $this->page->content_markdown = $this->request->getPost('content'); + $page->title = $this->request->getPost('title'); + $page->slug = $this->request->getPost('slug'); + $page->content_markdown = $this->request->getPost('content'); $pageModel = new PageModel(); - if (! $pageModel->update($this->page->id, $this->page)) { + if (! $pageModel->update($page->id, $page)) { return redirect() ->back() ->withInput() ->with('errors', $pageModel->errors()); } - return redirect()->route('page-edit', [$this->page->id])->with('message', lang('Page.messages.editSuccess')); + return redirect()->route('page-edit', [$page->id])->with('message', lang('Page.messages.editSuccess')); } - public function delete(): RedirectResponse + public function deleteAction(Page $page): RedirectResponse { - (new PageModel())->delete($this->page->id); + new PageModel() + ->delete($page->id); return redirect()->route('page-list'); } diff --git a/modules/Admin/Controllers/PersonController.php b/modules/Admin/Controllers/PersonController.php index 85b466c8..95bc4a28 100644 --- a/modules/Admin/Controllers/PersonController.php +++ b/modules/Admin/Controllers/PersonController.php @@ -18,8 +18,6 @@ use Modules\Media\Models\MediaModel; class PersonController extends BaseController { - protected ?Person $person = null; - public function _remap(string $method, string ...$params): mixed { if ($params === []) { @@ -27,44 +25,48 @@ class PersonController extends BaseController } if ( - ($this->person = (new PersonModel())->getPersonById((int) $params[0])) instanceof Person + ($person = new PersonModel()->getPersonById((int) $params[0])) instanceof Person ) { - return $this->{$method}(); + return $this->{$method}($person); } throw PageNotFoundException::forPageNotFound(); } - public function index(): string + public function list(): string { $data = [ - 'persons' => (new PersonModel())->orderBy('full_name') + 'persons' => new PersonModel() + ->orderBy('full_name') ->findAll(), ]; + $this->setHtmlHead(lang('Person.all_persons')); return view('person/list', $data); } - public function view(): string + public function view(Person $person): string { $data = [ - 'person' => $this->person, + 'person' => $person, ]; + $this->setHtmlHead($person->full_name); replace_breadcrumb_params([ - 0 => $this->person->full_name, + 0 => $person->full_name, ]); return view('person/view', $data); } - public function create(): string + public function createView(): string { helper(['form']); + $this->setHtmlHead(lang('Person.create')); return view('person/create'); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { $rules = [ 'avatar' => 'is_image[avatar]|ext_in[avatar,jpg,jpeg,png]|min_dims[avatar,400,400]|is_image_ratio[avatar,1,1]', @@ -104,21 +106,22 @@ class PersonController extends BaseController ->with('message', lang('Person.messages.createSuccess')); } - public function edit(): string + public function editView(Person $person): string { helper('form'); $data = [ - 'person' => $this->person, + 'person' => $person, ]; + $this->setHtmlHead(lang('Person.edit')); replace_breadcrumb_params([ - 0 => $this->person->full_name, + 0 => $person->full_name, ]); return view('person/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(Person $person): RedirectResponse { $rules = [ 'avatar' => 'is_image[avatar]|ext_in[avatar,jpg,jpeg,png]|min_dims[avatar,400,400]|is_image_ratio[avatar,1,1]', @@ -131,34 +134,36 @@ class PersonController extends BaseController ->with('errors', $this->validator->getErrors()); } - $this->person->updated_by = user_id(); - $this->person->full_name = $this->request->getPost('full_name'); - $this->person->unique_name = $this->request->getPost('unique_name'); - $this->person->information_url = $this->request->getPost('information_url'); - $this->person->setAvatar($this->request->getFile('avatar')); + $person->updated_by = user_id(); + $person->full_name = $this->request->getPost('full_name'); + $person->unique_name = $this->request->getPost('unique_name'); + $person->information_url = $this->request->getPost('information_url'); + $person->setAvatar($this->request->getFile('avatar')); $personModel = new PersonModel(); - if (! $personModel->update($this->person->id, $this->person)) { + if (! $personModel->update($person->id, $person)) { return redirect() ->back() ->withInput() ->with('errors', $personModel->errors()); } - return redirect()->route('person-edit', [$this->person->id])->with( + return redirect()->route('person-edit', [$person->id])->with( 'message', - lang('Person.messages.editSuccess') + lang('Person.messages.editSuccess'), ); } - public function delete(): RedirectResponse + public function deleteAction(Person $person): RedirectResponse { - if ($this->person->avatar_id !== null) { + if ($person->avatar_id !== null) { // delete avatar to prevent collision if recreating person - (new MediaModel())->deleteMedia($this->person->avatar); + new MediaModel() + ->deleteMedia($person->avatar); } - (new PersonModel())->delete($this->person->id); + new PersonModel() + ->delete($person->id); return redirect()->route('person-list') ->with('message', lang('Person.messages.deleteSuccess')); diff --git a/modules/Admin/Controllers/PodcastController.php b/modules/Admin/Controllers/PodcastController.php index f4cf4d10..96204b66 100644 --- a/modules/Admin/Controllers/PodcastController.php +++ b/modules/Admin/Controllers/PodcastController.php @@ -38,8 +38,6 @@ use Modules\Media\Models\MediaModel; class PodcastController extends BaseController { - protected Podcast $podcast; - public function _remap(string $method, string ...$params): mixed { if ($params === []) { @@ -47,10 +45,9 @@ class PodcastController extends BaseController } if ( - ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast + ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast ) { - $this->podcast = $podcast; - return $this->{$method}(); + return $this->{$method}($podcast); } throw PageNotFoundException::forPageNotFound(); @@ -60,7 +57,8 @@ class PodcastController extends BaseController { if (auth()->user()->can('podcasts.view')) { $data = [ - 'podcasts' => (new PodcastModel())->findAll(), + 'podcasts' => new PodcastModel() + ->findAll(), ]; } else { $data = [ @@ -68,111 +66,122 @@ class PodcastController extends BaseController ]; } + $this->setHtmlHead(lang('Podcast.all_podcasts')); return view('podcast/list', $data); } - public function view(): string + public function view(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/view', $data); } - public function viewAnalytics(): string + public function analyticsView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/index', $data); } - public function viewAnalyticsWebpages(): string + public function analyticsWebpagesView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/webpages', $data); } - public function viewAnalyticsLocations(): string + public function analyticsLocationsView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/locations', $data); } - public function viewAnalyticsUniqueListeners(): string + public function analyticsUniqueListenersView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/unique_listeners', $data); } - public function viewAnalyticsListeningTime(): string + public function analyticsListeningTimeView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/listening_time', $data); } - public function viewAnalyticsTimePeriods(): string + public function analyticsTimePeriodsView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/time_periods', $data); } - public function viewAnalyticsPlayers(): string + public function analyticsPlayersView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/players', $data); } - public function create(): string + public function createView(): string { helper(['form', 'misc']); - $languageOptions = (new LanguageModel())->getLanguageOptions(); - $categoryOptions = (new CategoryModel())->getCategoryOptions(); + $languageOptions = new LanguageModel() + ->getLanguageOptions(); + $categoryOptions = new CategoryModel() + ->getCategoryOptions(); $data = [ 'languageOptions' => $languageOptions, @@ -180,10 +189,11 @@ class PodcastController extends BaseController 'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')), ]; + $this->setHtmlHead(lang('Podcast.create')); return view('podcast/create', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { $rules = [ 'cover' => 'uploaded[cover]|is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', @@ -213,18 +223,14 @@ class PodcastController extends BaseController 'parental_advisory' => $this->request->getPost('parental_advisory') !== 'undefined' ? $this->request->getPost('parental_advisory') : null, - 'owner_name' => $this->request->getPost('owner_name'), - 'owner_email' => $this->request->getPost('owner_email'), - 'is_owner_email_removed_from_feed' => $this->request->getPost('is_owner_email_removed_from_feed') === 'yes', - 'publisher' => $this->request->getPost('publisher'), - 'type' => $this->request->getPost('type'), - 'medium' => $this->request->getPost('medium'), - 'copyright' => $this->request->getPost('copyright'), - 'location' => $this->request->getPost('location_name') === '' ? null : new Location( - $this->request->getPost('location_name') + 'owner_name' => $this->request->getPost('owner_name'), + 'owner_email' => $this->request->getPost('owner_email'), + 'publisher' => $this->request->getPost('publisher'), + 'type' => $this->request->getPost('type'), + 'copyright' => $this->request->getPost('copyright'), + 'location' => $this->request->getPost('location_name') === '' ? null : new Location( + $this->request->getPost('location_name'), ), - 'verify_txt' => $this->request->getPost('verify_txt'), - 'custom_rss_string' => $this->request->getPost('custom_rss'), 'is_blocked' => $this->request->getPost('block') === 'yes', 'is_completed' => $this->request->getPost('complete') === 'yes', 'is_locked' => $this->request->getPost('lock') === 'yes', @@ -248,43 +254,40 @@ class PodcastController extends BaseController add_podcast_group(auth()->user(), (int) $newPodcastId, setting('AuthGroups.mostPowerfulPodcastGroup')); // set Podcast categories - (new CategoryModel())->setPodcastCategories( - (int) $newPodcastId, - $this->request->getPost('other_categories') ?? [], - ); - - // OP3 - service('settings') - ->set('Analytics.enableOP3', $this->request->getPost('enable_op3') === 'yes', 'podcast:' . $newPodcastId); + new CategoryModel() + ->setPodcastCategories((int) $newPodcastId, $this->request->getPost('other_categories') ?? []); $db->transComplete(); return redirect()->route('podcast-view', [$newPodcastId])->with( 'message', - lang('Podcast.messages.createSuccess') + lang('Podcast.messages.createSuccess'), ); } - public function edit(): string + public function editView(Podcast $podcast): string { helper('form'); - $languageOptions = (new LanguageModel())->getLanguageOptions(); - $categoryOptions = (new CategoryModel())->getCategoryOptions(); + $languageOptions = new LanguageModel() + ->getLanguageOptions(); + $categoryOptions = new CategoryModel() + ->getCategoryOptions(); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, 'languageOptions' => $languageOptions, 'categoryOptions' => $categoryOptions, ]; + $this->setHtmlHead(lang('Podcast.edit')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(Podcast $podcast): RedirectResponse { $rules = [ 'cover' => 'is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', @@ -298,54 +301,46 @@ class PodcastController extends BaseController ->with('errors', $this->validator->getErrors()); } - $this->podcast->updated_by = (int) user_id(); + $podcast->updated_by = (int) user_id(); - $this->podcast->title = $this->request->getPost('title'); - $this->podcast->description_markdown = $this->request->getPost('description'); - $this->podcast->setCover($this->request->getFile('cover')); - $this->podcast->setBanner($this->request->getFile('banner')); + $podcast->title = $this->request->getPost('title'); + $podcast->description_markdown = $this->request->getPost('description'); + $podcast->setCover($this->request->getFile('cover')); + $podcast->setBanner($this->request->getFile('banner')); - $this->podcast->language_code = $this->request->getPost('language'); - $this->podcast->category_id = $this->request->getPost('category'); - $this->podcast->parental_advisory = + $podcast->language_code = $this->request->getPost('language'); + $podcast->category_id = $this->request->getPost('category'); + $podcast->parental_advisory = $this->request->getPost('parental_advisory') !== 'undefined' ? $this->request->getPost('parental_advisory') : null; - $this->podcast->publisher = $this->request->getPost('publisher'); - $this->podcast->owner_name = $this->request->getPost('owner_name'); - $this->podcast->owner_email = $this->request->getPost('owner_email'); - $this->podcast->is_owner_email_removed_from_feed = $this->request->getPost( - 'is_owner_email_removed_from_feed' - ) === 'yes'; - $this->podcast->type = $this->request->getPost('type'); - $this->podcast->medium = $this->request->getPost('medium'); - $this->podcast->copyright = $this->request->getPost('copyright'); - $this->podcast->location = $this->request->getPost('location_name') === '' ? null : new Location( - $this->request->getPost('location_name') + $podcast->publisher = $this->request->getPost('publisher'); + $podcast->owner_name = $this->request->getPost('owner_name'); + $podcast->owner_email = $this->request->getPost('owner_email'); + $podcast->type = $this->request->getPost('type'); + $podcast->copyright = $this->request->getPost('copyright'); + $podcast->location = $this->request->getPost('location_name') === '' ? null : new Location( + $this->request->getPost('location_name'), ); - $this->podcast->verify_txt = $this->request->getPost('verify_txt') === '' ? null : $this->request->getPost( - 'verify_txt' - ); - $this->podcast->custom_rss_string = $this->request->getPost('custom_rss'); - $this->podcast->new_feed_url = $this->request->getPost('new_feed_url') === '' ? null : $this->request->getPost( - 'new_feed_url' + $podcast->new_feed_url = $this->request->getPost('new_feed_url') === '' ? null : $this->request->getPost( + 'new_feed_url', ); - $this->podcast->is_blocked = $this->request->getPost('block') === 'yes'; - $this->podcast->is_completed = + $podcast->is_blocked = $this->request->getPost('block') === 'yes'; + $podcast->is_completed = $this->request->getPost('complete') === 'yes'; - $this->podcast->is_locked = $this->request->getPost('lock') === 'yes'; - $this->podcast->is_premium_by_default = $this->request->getPost('premium_by_default') === 'yes'; + $podcast->is_locked = $this->request->getPost('lock') === 'yes'; + $podcast->is_premium_by_default = $this->request->getPost('premium_by_default') === 'yes'; // republish on websub hubs upon edit - $this->podcast->is_published_on_hubs = false; + $podcast->is_published_on_hubs = false; $db = db_connect(); $db->transStart(); $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + if (! $podcastModel->update($podcast->id, $podcast)) { $db->transRollback(); return redirect() ->back() @@ -354,85 +349,28 @@ class PodcastController extends BaseController } // set Podcast categories - (new CategoryModel())->setPodcastCategories( - $this->podcast->id, - $this->request->getPost('other_categories') ?? [], - ); - - // enable/disable OP3? - service('settings') - ->set( - 'Analytics.enableOP3', - $this->request->getPost('enable_op3') === 'yes', - 'podcast:' . $this->podcast->id - ); + new CategoryModel() + ->setPodcastCategories($podcast->id, $this->request->getPost('other_categories') ?? []); // New feed url redirect service('settings') ->set( 'Podcast.redirect_to_new_feed', $this->request->getPost('redirect_to_new_feed') === 'yes', - 'podcast:' . $this->podcast->id + 'podcast:' . $podcast->id, ); $db->transComplete(); - return redirect()->route('podcast-edit', [$this->podcast->id])->with( + return redirect()->route('podcast-edit', [$podcast->id])->with( 'message', - lang('Podcast.messages.editSuccess') + lang('Podcast.messages.editSuccess'), ); } - public function monetizationOther(): string + public function deleteBannerAction(Podcast $podcast): RedirectResponse { - helper('form'); - - $data = [ - 'podcast' => $this->podcast, - ]; - - replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - ]); - return view('podcast/monetization_other', $data); - } - - public function monetizationOtherAction(): RedirectResponse - { - if ( - ($partnerId = $this->request->getPost('partner_id')) === '' || - ($partnerLinkUrl = $this->request->getPost('partner_link_url')) === '' || - ($partnerImageUrl = $this->request->getPost('partner_image_url')) === '') { - $partnerId = null; - $partnerLinkUrl = null; - $partnerImageUrl = null; - } - - $this->podcast->payment_pointer = $this->request->getPost( - 'payment_pointer' - ) === '' ? null : $this->request->getPost('payment_pointer'); - - $this->podcast->partner_id = $partnerId; - $this->podcast->partner_link_url = $partnerLinkUrl; - $this->podcast->partner_image_url = $partnerImageUrl; - - $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $podcastModel->errors()); - } - - return redirect()->route('podcast-monetization-other', [$this->podcast->id])->with( - 'message', - lang('Podcast.messages.editSuccess') - ); - } - - public function deleteBanner(): RedirectResponse - { - if (! $this->podcast->banner instanceof Image) { + if (! $podcast->banner instanceof Image) { return redirect()->back(); } @@ -441,25 +379,28 @@ class PodcastController extends BaseController $db->transStart(); $mediaModel = new MediaModel(); - if (! $mediaModel->deleteMedia($this->podcast->banner)) { + if (! $mediaModel->deleteMedia($podcast->banner)) { return redirect() ->back() ->withInput() ->with('errors', $mediaModel->errors()); } - (new PodcastModel())->clearCache([ - 'id' => $this->podcast->id, - ]); + new PodcastModel() + ->clearCache([ + 'id' => $podcast->id, + ]); // remove banner url from actor - $actor = (new ActorModel())->getActorById($this->podcast->actor_id); + $actor = new ActorModel() + ->getActorById($podcast->actor_id); if ($actor instanceof Actor) { $actor->cover_image_url = null; $actor->cover_image_mimetype = null; - (new ActorModel())->update($actor->id, $actor); + new ActorModel() + ->update($actor->id, $actor); } $db->transComplete(); @@ -467,9 +408,9 @@ class PodcastController extends BaseController return redirect()->back(); } - public function latestEpisodes(int $limit, int $podcastId): string + public function latestEpisodesView(int $limit, int $podcastId): string { - $episodes = (new EpisodeModel()) + $episodes = new EpisodeModel() ->where('podcast_id', $podcastId) ->orderBy('-`published_at`', '', false) ->orderBy('created_at', 'desc') @@ -477,25 +418,27 @@ class PodcastController extends BaseController return view('podcast/latest_episodes', [ 'episodes' => $episodes, - 'podcast' => (new PodcastModel())->getPodcastById($podcastId), + 'podcast' => new PodcastModel() + ->getPodcastById($podcastId), ]); } - public function delete(): string + public function deleteView(Podcast $podcast): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead(lang('Podcast.delete')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/delete', $data); } - public function attemptDelete(): RedirectResponse + public function deleteAction(Podcast $podcast): RedirectResponse { $rules = [ 'understand' => 'required', @@ -513,7 +456,8 @@ class PodcastController extends BaseController $db->transStart(); //delete podcast episodes - $podcastEpisodes = (new EpisodeModel())->where('podcast_id', $this->podcast->id) + $podcastEpisodes = new EpisodeModel() + ->where('podcast_id', $podcast->id) ->findAll(); foreach ($podcastEpisodes as $podcastEpisode) { @@ -553,7 +497,7 @@ class PodcastController extends BaseController //delete podcast $podcastModel = new PodcastModel(); - if (! $podcastModel->delete($this->podcast->id)) { + if (! $podcastModel->delete($podcast->id)) { $db->transRollback(); return redirect() ->back() @@ -565,15 +509,15 @@ class PodcastController extends BaseController $podcastMediaList = [ [ 'type' => 'cover', - 'file' => $this->podcast->cover, + 'file' => $podcast->cover, ], ]; - if ($this->podcast->banner_id !== null) { + if ($podcast->banner_id !== null) { $podcastMediaList[] = [ 'type' => 'banner', - 'file' => $this->podcast->banner, + 'file' => $podcast->banner, ]; } @@ -594,7 +538,7 @@ class PodcastController extends BaseController //delete podcast actor $actorModel = new ActorModel(); - if (! $actorModel->delete($this->podcast->actor_id)) { + if (! $actorModel->delete($podcast->actor_id)) { $db->transRollback(); return redirect() ->back() @@ -616,7 +560,7 @@ class PodcastController extends BaseController ]; foreach ($analyticsModels as $analyticsModel) { if (! $analyticsModel->where([ - 'podcast_id' => $this->podcast->id, + 'podcast_id' => $podcast->id, ])->delete()) { $db->transRollback(); return redirect() @@ -632,11 +576,11 @@ class PodcastController extends BaseController $fileManager = service('file_manager'); //delete podcast media files and folder - $folder = 'podcasts/' . $this->podcast->handle; + $folder = 'podcasts/' . $podcast->handle; if (! $fileManager->deleteAll($folder)) { return redirect()->route('podcast-list') ->with('message', lang('Podcast.messages.deleteSuccess', [ - 'podcast_handle' => $this->podcast->handle, + 'podcast_handle' => $podcast->handle, ])) ->with('warning', lang('Podcast.messages.deletePodcastMediaFolderError', [ 'folder_path' => $folder, @@ -645,31 +589,31 @@ class PodcastController extends BaseController return redirect()->route('podcast-list') ->with('message', lang('Podcast.messages.deleteSuccess', [ - 'podcast_handle' => $this->podcast->handle, + 'podcast_handle' => $podcast->handle, ])); } - public function publish(): string | RedirectResponse + public function publishView(Podcast $podcast): string | RedirectResponse { helper(['form']); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead(lang('Podcast.publish')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); - return view('podcast/publish', $data); } - public function attemptPublish(): RedirectResponse + public function publishAction(Podcast $podcast): RedirectResponse { - if ($this->podcast->publication_status !== 'not_published') { - return redirect()->route('podcast-view', [$this->podcast->id])->with( + if ($podcast->publication_status !== 'not_published') { + return redirect()->route('podcast-view', [$podcast->id])->with( 'error', - lang('Podcast.messages.publishError') + lang('Podcast.messages.publishError'), ); } @@ -694,7 +638,7 @@ class PodcastController extends BaseController if ($publishMethod === 'schedule') { $scheduledPublicationDate = $validData['scheduled_publication_date']; if ($scheduledPublicationDate) { - $this->podcast->published_at = Time::createFromFormat( + $podcast->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, $this->request->getPost('client_timezone'), @@ -707,19 +651,19 @@ class PodcastController extends BaseController ->with('error', lang('Podcast.messages.scheduleDateError')); } } else { - $this->podcast->published_at = Time::now(); + $podcast->published_at = Time::now(); } $message = $this->request->getPost('message'); // only create post if message is not empty if ($message !== '') { $newPost = new Post([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'message' => $message, 'created_by' => user_id(), ]); - $newPost->published_at = $this->podcast->published_at; + $newPost->published_at = $podcast->published_at; $postModel = new PostModel(); if (! $postModel->addPost($newPost)) { @@ -731,13 +675,13 @@ class PodcastController extends BaseController } } - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) + $episodes = new EpisodeModel() + ->where('podcast_id', $podcast->id) ->where('published_at !=') ->findAll(); foreach ($episodes as $episode) { - $episode->published_at = $this->podcast->published_at->addSeconds(1); + $episode->published_at = $podcast->published_at->addSeconds(1); $episodeModel = new EpisodeModel(); if (! $episodeModel->update($episode->id, $episode)) { @@ -748,7 +692,8 @@ class PodcastController extends BaseController ->with('errors', $episodeModel->errors()); } - $post = (new PostModel())->where('episode_id', $episode->id) + $post = new PostModel() + ->where('episode_id', $episode->id) ->first(); if ($post instanceof Post) { @@ -765,7 +710,7 @@ class PodcastController extends BaseController } $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + if (! $podcastModel->update($podcast->id, $podcast)) { $db->transRollback(); return redirect() ->back() @@ -775,36 +720,36 @@ class PodcastController extends BaseController $db->transComplete(); - return redirect()->route('podcast-view', [$this->podcast->id]); + return redirect()->route('podcast-view', [$podcast->id]); } - public function publishEdit(): string | RedirectResponse + public function publishEditView(Podcast $podcast): string | RedirectResponse { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'post' => (new PostModel()) + 'podcast' => $podcast, + 'post' => new PostModel() ->where([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'episode_id' => null, ]) ->first(), ]; + $this->setHtmlHead(lang('Podcast.publish_edit')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); - return view('podcast/publish_edit', $data); } - public function attemptPublishEdit(): RedirectResponse + public function publishEditAction(Podcast $podcast): RedirectResponse { - if ($this->podcast->publication_status !== 'scheduled') { - return redirect()->route('podcast-view', [$this->podcast->id])->with( + if ($podcast->publication_status !== 'scheduled') { + return redirect()->route('podcast-view', [$podcast->id])->with( 'error', - lang('Podcast.messages.publishEditError') + lang('Podcast.messages.publishEditError'), ); } @@ -829,7 +774,7 @@ class PodcastController extends BaseController if ($publishMethod === 'schedule') { $scheduledPublicationDate = $validData['scheduled_publication_date']; if ($scheduledPublicationDate) { - $this->podcast->published_at = Time::createFromFormat( + $podcast->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, $this->request->getPost('client_timezone'), @@ -842,12 +787,12 @@ class PodcastController extends BaseController ->with('error', lang('Podcast.messages.scheduleDateError')); } } else { - $this->podcast->published_at = Time::now(); + $podcast->published_at = Time::now(); } - $post = (new PostModel()) + $post = new PostModel() ->where([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'episode_id' => null, ]) ->first(); @@ -858,7 +803,7 @@ class PodcastController extends BaseController if ($newPostMessage !== '') { // edit post if post exists and message is not empty $post->message = $newPostMessage; - $post->published_at = $this->podcast->published_at; + $post->published_at = $podcast->published_at; $postModel = new PostModel(); if (! $postModel->editPost($post)) { @@ -873,7 +818,7 @@ class PodcastController extends BaseController $postModel = new PostModel(); $post = $postModel ->where([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'episode_id' => null, ]) ->first(); @@ -882,12 +827,12 @@ class PodcastController extends BaseController } elseif ($newPostMessage !== '') { // create post if there is no post and message is not empty $newPost = new Post([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'message' => $newPostMessage, 'created_by' => user_id(), ]); - $newPost->published_at = $this->podcast->published_at; + $newPost->published_at = $podcast->published_at; $postModel = new PostModel(); if (! $postModel->addPost($newPost)) { @@ -899,13 +844,13 @@ class PodcastController extends BaseController } } - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) + $episodes = new EpisodeModel() + ->where('podcast_id', $podcast->id) ->where('published_at !=') ->findAll(); foreach ($episodes as $episode) { - $episode->published_at = $this->podcast->published_at->addSeconds(1); + $episode->published_at = $podcast->published_at->addSeconds(1); $episodeModel = new EpisodeModel(); if (! $episodeModel->update($episode->id, $episode)) { @@ -916,7 +861,8 @@ class PodcastController extends BaseController ->with('errors', $episodeModel->errors()); } - $post = (new PostModel())->where('episode_id', $episode->id) + $post = new PostModel() + ->where('episode_id', $episode->id) ->first(); if ($post instanceof Post) { @@ -933,7 +879,7 @@ class PodcastController extends BaseController } $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + if (! $podcastModel->update($podcast->id, $podcast)) { $db->transRollback(); return redirect() ->back() @@ -943,13 +889,13 @@ class PodcastController extends BaseController $db->transComplete(); - return redirect()->route('podcast-view', [$this->podcast->id]); + return redirect()->route('podcast-view', [$podcast->id]); } - public function publishCancel(): RedirectResponse + public function publishCancelAction(Podcast $podcast): RedirectResponse { - if ($this->podcast->publication_status !== 'scheduled') { - return redirect()->route('podcast-view', [$this->podcast->id]); + if ($podcast->publication_status !== 'scheduled') { + return redirect()->route('podcast-view', [$podcast->id]); } $db = db_connect(); @@ -958,7 +904,7 @@ class PodcastController extends BaseController $postModel = new PostModel(); $post = $postModel ->where([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'episode_id' => null, ]) ->first(); @@ -966,8 +912,8 @@ class PodcastController extends BaseController $postModel->removePost($post); } - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) + $episodes = new EpisodeModel() + ->where('podcast_id', $podcast->id) ->where('published_at !=') ->findAll(); @@ -989,10 +935,10 @@ class PodcastController extends BaseController $postModel->removePost($post); } - $this->podcast->published_at = null; + $podcast->published_at = null; $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + if (! $podcastModel->update($podcast->id, $podcast)) { $db->transRollback(); return redirect() ->back() @@ -1002,9 +948,9 @@ class PodcastController extends BaseController $db->transComplete(); - return redirect()->route('podcast-view', [$this->podcast->id])->with( + return redirect()->route('podcast-view', [$podcast->id])->with( 'message', - lang('Podcast.messages.publishCancelSuccess') + lang('Podcast.messages.publishCancelSuccess'), ); } } diff --git a/modules/Admin/Controllers/PodcastPersonController.php b/modules/Admin/Controllers/PodcastPersonController.php index ec02c393..7713bbfc 100644 --- a/modules/Admin/Controllers/PodcastPersonController.php +++ b/modules/Admin/Controllers/PodcastPersonController.php @@ -27,32 +27,37 @@ class PodcastPersonController extends BaseController } if ( - ($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast + ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast ) { unset($params[0]); - return $this->{$method}(...$params); + return $this->{$method}($podcast, ...$params); } throw PageNotFoundException::forPageNotFound(); } - public function index(): string + public function index(Podcast $podcast): string { helper('form'); $data = [ - 'podcast' => $this->podcast, - 'podcastPersons' => (new PersonModel())->getPodcastPersons($this->podcast->id), - 'personOptions' => (new PersonModel())->getPersonOptions(), - 'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(), + 'podcast' => $podcast, + 'podcastPersons' => new PersonModel() + ->getPodcastPersons($podcast->id), + 'personOptions' => new PersonModel() + ->getPersonOptions(), + 'taxonomyOptions' => new PersonModel() + ->getTaxonomyOptions(), ]; + + $this->setHtmlHead(lang('Person.podcast_form.title')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, + 0 => $podcast->at_handle, ]); return view('podcast/persons', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(Podcast $podcast): RedirectResponse { $rules = [ 'persons' => 'required', @@ -67,18 +72,16 @@ class PodcastPersonController extends BaseController $validData = $this->validator->getValidated(); - (new PersonModel())->addPodcastPersons( - $this->podcast->id, - $validData['persons'], - $this->request->getPost('roles') ?? [], - ); + new PersonModel() + ->addPodcastPersons($podcast->id, $validData['persons'], $this->request->getPost('roles') ?? []); return redirect()->back(); } - public function remove(string $personId): RedirectResponse + public function deleteAction(Podcast $podcast, string $personId): RedirectResponse { - (new PersonModel())->removePersonFromPodcast($this->podcast->id, (int) $personId); + new PersonModel() + ->removePersonFromPodcast($podcast->id, (int) $personId); return redirect()->back(); } diff --git a/modules/Admin/Controllers/SettingsController.php b/modules/Admin/Controllers/SettingsController.php index 0ba69509..f0e8c508 100644 --- a/modules/Admin/Controllers/SettingsController.php +++ b/modules/Admin/Controllers/SettingsController.php @@ -30,10 +30,11 @@ class SettingsController extends BaseController public function index(): string { helper('form'); + $this->setHtmlHead(lang('Settings.title')); return view('settings/general'); } - public function attemptInstanceEdit(): RedirectResponse + public function instanceEditAction(): RedirectResponse { $rules = [ 'site_icon' => 'is_image[site_icon]|ext_in[site_icon,png,jpeg]|is_image_ratio[site_icon,1,1]|min_dims[image,512,512]|permit_empty', @@ -118,7 +119,7 @@ class SettingsController extends BaseController return redirect('settings-general')->with('message', lang('Settings.instance.editSuccess')); } - public function deleteIcon(): RedirectResponse + public function deleteIconAction(): RedirectResponse { /** @var FileManagerInterface $fileManager */ $fileManager = service('file_manager'); @@ -132,10 +133,11 @@ class SettingsController extends BaseController return redirect('settings-general')->with('message', lang('Settings.instance.deleteIconSuccess')); } - public function regenerateImages(): RedirectResponse + public function regenerateImagesAction(): RedirectResponse { /** @var Podcast[] $allPodcasts */ - $allPodcasts = (new PodcastModel())->findAll(); + $allPodcasts = new PodcastModel() + ->findAll(); /** @var FileManagerInterface $fileManager */ $fileManager = service('file_manager'); @@ -157,7 +159,8 @@ class SettingsController extends BaseController $fileManager->deletePersonImagesSizes(); - $persons = (new PersonModel())->findAll(); + $persons = new PersonModel() + ->findAll(); foreach ($persons as $person) { if ($person->avatar_id !== null) { $person->avatar->saveSizes(); @@ -167,20 +170,30 @@ class SettingsController extends BaseController return redirect('settings-general')->with('message', lang('Settings.images.regenerationSuccess')); } - public function runHousekeeping(): RedirectResponse + public function housekeepingAction(): RedirectResponse { if ($this->request->getPost('reset_counts') === 'yes') { // recalculate fediverse counts - (new ActorModel())->resetFollowersCount(); - (new ActorModel())->resetPostsCount(); - (new PostModel())->setEpisodeIdForRepliesOfEpisodePosts(); - (new PostModel())->resetFavouritesCount(); - (new PostModel())->resetReblogsCount(); - (new PostModel())->resetRepliesCount(); - (new EpisodeModel())->resetCommentsCount(); - (new EpisodeModel())->resetPostsCount(); - (new EpisodeCommentModel())->resetLikesCount(); - (new EpisodeCommentModel())->resetRepliesCount(); + new ActorModel() + ->resetFollowersCount(); + new ActorModel() + ->resetPostsCount(); + new PostModel() + ->setEpisodeIdForRepliesOfEpisodePosts(); + new PostModel() + ->resetFavouritesCount(); + new PostModel() + ->resetReblogsCount(); + new PostModel() + ->resetRepliesCount(); + new EpisodeModel() + ->resetCommentsCount(); + new EpisodeModel() + ->resetPostsCount(); + new EpisodeCommentModel() + ->resetLikesCount(); + new EpisodeCommentModel() + ->resetRepliesCount(); } if ($this->request->getPost('clear_cache') === 'yes') { @@ -189,7 +202,8 @@ class SettingsController extends BaseController if ($this->request->getPost('rename_episodes_files') === 'yes') { /** @var Audio[] $allAudio */ - $allAudio = (new MediaModel('audio'))->getAllOfType(); + $allAudio = new MediaModel('audio') + ->getAllOfType(); foreach ($allAudio as $audio) { $audio->rename(); @@ -199,13 +213,14 @@ class SettingsController extends BaseController return redirect('settings-general')->with('message', lang('Settings.housekeeping.runSuccess')); } - public function theme(): string + public function themeView(): string { helper('form'); + $this->setHtmlHead(lang('Settings.theme.title')); return view('settings/theme'); } - public function attemptSetInstanceTheme(): RedirectResponse + public function themeAction(): RedirectResponse { $theme = $this->request->getPost('theme'); service('settings') diff --git a/modules/Admin/Controllers/SoundbiteController.php b/modules/Admin/Controllers/SoundbiteController.php index ca96f56b..2ca68bac 100644 --- a/modules/Admin/Controllers/SoundbiteController.php +++ b/modules/Admin/Controllers/SoundbiteController.php @@ -22,47 +22,38 @@ use Modules\Media\Models\MediaModel; class SoundbiteController extends BaseController { - protected Podcast $podcast; - - protected Episode $episode; - public function _remap(string $method, string ...$params): mixed { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + + if (count($params) === 1) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + return $this->{$method}($podcast); + } + if ( - ! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast + ! ($episode = new EpisodeModel()->getEpisodeById((int) $params[1])) instanceof Episode ) { throw PageNotFoundException::forPageNotFound(); } - $this->podcast = $podcast; + unset($params[0]); + unset($params[1]); - if (count($params) > 1) { - if ( - ! ($episode = (new EpisodeModel()) - ->where([ - 'id' => $params[1], - 'podcast_id' => $params[0], - ]) - ->first()) instanceof Episode - ) { - throw PageNotFoundException::forPageNotFound(); - } - - $this->episode = $episode; - - unset($params[1]); - unset($params[0]); - } - - return $this->{$method}(...$params); + return $this->{$method}($episode, ...$params); } - public function list(): string + public function list(Episode $episode): string { - $soundbitesBuilder = (new ClipModel('audio')) + $soundbitesBuilder = new ClipModel('audio') ->where([ - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, + 'podcast_id' => $episode->podcast_id, + 'episode_id' => $episode->id, 'type' => 'audio', ]) ->orderBy('created_at', 'desc'); @@ -70,36 +61,38 @@ class SoundbiteController extends BaseController $soundbites = $soundbitesBuilder->paginate(10); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, 'soundbites' => $soundbites, 'pager' => $soundbitesBuilder->pager, ]; + $this->setHtmlHead(lang('Soundbite.list.title')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/soundbites_list', $data); } - public function create(): string + public function createView(Episode $episode): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Soundbite.form.title')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/soundbites_new', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(Episode $episode): RedirectResponse { $rules = [ 'title' => 'required', @@ -122,8 +115,8 @@ class SoundbiteController extends BaseController 'duration' => (float) $validData['duration'], 'type' => 'audio', 'status' => '', - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, + 'podcast_id' => $episode->podcast_id, + 'episode_id' => $episode->id, 'created_by' => user_id(), 'updated_by' => user_id(), ]); @@ -136,15 +129,16 @@ class SoundbiteController extends BaseController ->with('errors', $clipModel->errors()); } - return redirect()->route('soundbites-list', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('soundbites-list', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Soundbite.messages.createSuccess') + lang('Soundbite.messages.createSuccess'), ); } - public function delete(string $soundbiteId): RedirectResponse + public function deleteAction(Episode $episode, string $soundbiteId): RedirectResponse { - $soundbite = (new ClipModel())->getSoundbiteById((int) $soundbiteId); + $soundbite = new ClipModel() + ->getSoundbiteById((int) $soundbiteId); if (! $soundbite instanceof Soundbite) { throw PageNotFoundException::forPageNotFound(); @@ -152,9 +146,11 @@ class SoundbiteController extends BaseController if ($soundbite->media === null) { // delete Clip directly - (new ClipModel())->deleteSoundbite($this->podcast->id, $this->episode->id, $soundbite->id); + new ClipModel() + ->deleteSoundbite($episode->podcast_id, $episode->id, $soundbite->id); } else { - (new ClipModel())->clearSoundbiteCache($this->podcast->id, $this->episode->id, $soundbite->id); + new ClipModel() + ->clearSoundbiteCache($episode->podcast_id, $episode->id, $soundbite->id); $mediaModel = new MediaModel(); // delete the soundbite file, the clip will be deleted on cascade @@ -166,9 +162,9 @@ class SoundbiteController extends BaseController } } - return redirect()->route('soundbites-list', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('soundbites-list', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Soundbite.messages.deleteSuccess') + lang('Soundbite.messages.deleteSuccess'), ); } } diff --git a/modules/Admin/Controllers/VideoClipsController.php b/modules/Admin/Controllers/VideoClipsController.php index 5a5cd5d7..b91305ca 100644 --- a/modules/Admin/Controllers/VideoClipsController.php +++ b/modules/Admin/Controllers/VideoClipsController.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; +use App\Entities\Clip\BaseClip; use App\Entities\Clip\VideoClip; use App\Entities\Episode; use App\Entities\Podcast; @@ -23,51 +24,43 @@ use Modules\Media\Models\MediaModel; class VideoClipsController extends BaseController { - protected Podcast $podcast; - - protected Episode $episode; - public function _remap(string $method, string ...$params): mixed { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + + if (count($params) === 1) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + return $this->{$method}($podcast); + } + if ( - ! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast + ! ($episode = new EpisodeModel()->getEpisodeById((int) $params[1])) instanceof Episode ) { throw PageNotFoundException::forPageNotFound(); } - $this->podcast = $podcast; + unset($params[0]); + unset($params[1]); - if (count($params) > 1) { - if ( - ! ($episode = (new EpisodeModel()) - ->where([ - 'id' => $params[1], - 'podcast_id' => $params[0], - ]) - ->first()) instanceof Episode - ) { - throw PageNotFoundException::forPageNotFound(); - } - - $this->episode = $episode; - - unset($params[1]); - unset($params[0]); - } - - return $this->{$method}(...$params); + return $this->{$method}($episode, ...$params); } - public function list(): string + public function list(Episode $episode): string { - $videoClipsBuilder = (new ClipModel('video')) + $videoClipsBuilder = new ClipModel('video') ->where([ - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, + 'podcast_id' => $episode->podcast_id, + 'episode_id' => $episode->id, 'type' => 'video', ]) ->orderBy('created_at', 'desc'); + /** @var BaseClip[] $clips */ $clips = $videoClipsBuilder->paginate(10); $videoClips = []; @@ -76,47 +69,52 @@ class VideoClipsController extends BaseController } $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, 'videoClips' => $videoClips, 'pager' => $videoClipsBuilder->pager, ]; + $this->setHtmlHead(lang('VideoClip.list.title')); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/video_clips_list', $data); } - public function view(string $videoClipId): string + public function view(Episode $episode, string $videoClipId): string { - $videoClip = (new ClipModel())->getVideoClipById((int) $videoClipId); + $videoClip = new ClipModel() + ->getVideoClipById((int) $videoClipId); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, 'videoClip' => $videoClip, ]; + $this->setHtmlHead(lang('VideoClip.title', [ + 'videoClipLabel' => esc($videoClip->title), + ])); replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, 2 => $videoClip->title, ]); return view('episode/video_clip', $data); } - public function create(): string + public function createView(Episode $episode): string { $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; replace_breadcrumb_params([ - 0 => $this->podcast->at_handle, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); // First, check that requirements to create a video clip are met @@ -125,12 +123,13 @@ class VideoClipsController extends BaseController 'ffmpeg' => $ffmpeg !== null, 'gd' => extension_loaded('gd'), 'freetype' => extension_loaded('gd') && gd_info()['FreeType Support'], - 'transcript' => $this->episode->transcript instanceof Transcript, + 'transcript' => $episode->transcript instanceof Transcript, ]; - if (in_array(false, $checks, true)) { - $data['checks'] = $checks; + $this->setHtmlHead(lang('VideoClip.form.title')); + if (array_any($checks, fn (bool $value): bool => ! $value)) { + $data['checks'] = $checks; return view('episode/video_clips_requirements', $data); } @@ -138,7 +137,7 @@ class VideoClipsController extends BaseController return view('episode/video_clips_new', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(Episode $episode): RedirectResponse { $rules = [ 'title' => 'required', @@ -173,14 +172,14 @@ class VideoClipsController extends BaseController 'format' => $validData['format'], 'type' => 'video', 'status' => 'queued', - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, + 'podcast_id' => $episode->podcast_id, + 'episode_id' => $episode->id, 'created_by' => user_id(), 'updated_by' => user_id(), ]); // Check if video clip exists before inserting a new line - if ((new ClipModel())->doesVideoClipExist($videoClip)) { + if (new ClipModel()->doesVideoClipExist($videoClip)) { // video clip already exists return redirect() ->back() @@ -188,34 +187,38 @@ class VideoClipsController extends BaseController ->with('error', lang('VideoClip.messages.alreadyExistingError')); } - (new ClipModel())->insert($videoClip); + new ClipModel() + ->insert($videoClip); - return redirect()->route('video-clips-list', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('video-clips-list', [$episode->podcast_id, $episode->id])->with( 'message', - lang('VideoClip.messages.addToQueueSuccess') + lang('VideoClip.messages.addToQueueSuccess'), ); } - public function retry(string $videoClipId): RedirectResponse + public function retryAction(Episode $episode, string $videoClipId): RedirectResponse { - $videoClip = (new ClipModel())->getVideoClipById((int) $videoClipId); + $videoClip = new ClipModel() + ->getVideoClipById((int) $videoClipId); if (! $videoClip instanceof VideoClip) { throw PageNotFoundException::forPageNotFound(); } - (new ClipModel())->update($videoClip->id, [ - 'status' => 'queued', - 'job_started_at' => null, - 'job_ended_at' => null, - ]); + new ClipModel() + ->update($videoClip->id, [ + 'status' => 'queued', + 'job_started_at' => null, + 'job_ended_at' => null, + ]); return redirect()->back(); } - public function delete(string $videoClipId): RedirectResponse + public function deleteAction(Episode $episode, string $videoClipId): RedirectResponse { - $videoClip = (new ClipModel())->getVideoClipById((int) $videoClipId); + $videoClip = new ClipModel() + ->getVideoClipById((int) $videoClipId); if (! $videoClip instanceof VideoClip) { throw PageNotFoundException::forPageNotFound(); @@ -223,9 +226,11 @@ class VideoClipsController extends BaseController if ($videoClip->media === null) { // delete Clip directly - (new ClipModel())->deleteVideoClip($videoClip->id); + new ClipModel() + ->deleteVideoClip($videoClip->id); } else { - (new ClipModel())->clearVideoClipCache($videoClip->id); + new ClipModel() + ->clearVideoClipCache($videoClip->id); $mediaModel = new MediaModel(); // delete the videoClip file, the clip will be deleted on cascade diff --git a/modules/Admin/Language/.rsync-filter b/modules/Admin/Language/.rsync-filter index 38526af5..b802a93d 100644 --- a/modules/Admin/Language/.rsync-filter +++ b/modules/Admin/Language/.rsync-filter @@ -1,14 +1,12 @@ -+ br/*** -+ ca/*** -+ cs/*** -+ de/*** + en/*** -+ es/*** + fr/*** -+ lt/*** -+ nn-no/*** + pl/*** ++ de/*** + pt-br/*** -+ sr-latn/*** ++ nn-no/*** ++ es/*** + zh-hans/*** ++ ca/*** ++ br/*** ++ sr-latn/*** - ** diff --git a/modules/Admin/Language/cs/AboutCastopod.php b/modules/Admin/Language/cs/AboutCastopod.php deleted file mode 100644 index 77803886..00000000 --- a/modules/Admin/Language/cs/AboutCastopod.php +++ /dev/null @@ -1,22 +0,0 @@ - 'O Castopodu', - 'host_name' => 'Název hostitele', - 'version' => 'Verze Castopodu', - 'php_version' => 'PHP verze', - 'os' => 'Operační systém', - 'languages' => 'Jazyky', - 'update_database' => 'Aktualizovat databázi', - 'messages' => [ - 'databaseUpdateSuccess' => 'Databáze je aktuální!', - ], -]; diff --git a/modules/Admin/Language/cs/Admin.php b/modules/Admin/Language/cs/Admin.php deleted file mode 100644 index 0aa00192..00000000 --- a/modules/Admin/Language/cs/Admin.php +++ /dev/null @@ -1,15 +0,0 @@ - 'Administrátorský panel', - 'welcome_message' => 'Vítejte v administrační oblasti!', - 'choose_interact' => 'Zvolte způsob interakce', -]; diff --git a/modules/Admin/Language/cs/Breadcrumb.php b/modules/Admin/Language/cs/Breadcrumb.php deleted file mode 100644 index 83880787..00000000 --- a/modules/Admin/Language/cs/Breadcrumb.php +++ /dev/null @@ -1,57 +0,0 @@ - 'breadcrumb', - config('Admin') - ->gateway => 'Domovská stránka', - 'podcasts' => 'podcasty', - 'episodes' => 'epizody', - 'subscriptions' => 'Odběry', - 'contributors' => 'Přispívající', - 'pages' => 'stránky', - 'settings' => 'nastavení', - 'theme' => 'motiv', - 'about' => 'info', - 'add' => 'přidat', - 'new' => 'nový', - 'edit' => 'upravit', - 'persons' => 'osoby', - 'publish' => 'publikovat', - 'publish-edit' => 'upravit publikování', - 'publish-date-edit' => 'upravit datum publikování', - 'unpublish' => 'zrušit publikování', - 'delete' => 'smazat', - 'remove' => 'odstranit', - 'fediverse' => 'fediverse', - 'blocked-actors' => 'blokované subjekty', - 'blocked-domains' => 'blokované domény', - 'users' => 'uživatelé', - 'my-account' => 'můj účet', - 'change-password' => 'změna hesla', - 'imports' => 'importy', - 'sync-feeds' => 'synchronizovat kanály', - 'platforms' => 'platformy', - 'social' => 'sociální sítě', - 'funding' => 'financování', - 'monetization-other' => 'ostatní monetizace', - 'analytics' => 'analytika', - 'locations' => 'lokality', - 'webpages' => 'webové stránky', - 'unique-listeners' => 'unikátní posluchači', - 'players' => 'přehrávače', - 'listening-time' => 'čas poslechu', - 'time-periods' => 'časové období', - 'soundbites' => 'úryvky', - 'video-clips' => 'videoklipy', - 'embed' => 'vestavitelný přehrávač', - 'notifications' => 'oznámení', - 'suspend' => 'pozastavit', -]; diff --git a/modules/Admin/Language/cs/Charts.php b/modules/Admin/Language/cs/Charts.php deleted file mode 100644 index 08ea16cb..00000000 --- a/modules/Admin/Language/cs/Charts.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Stažení epizody podle služby (za poslední týden)', - 'by_player_weekly' => 'Stažení epizody podle přehrávače (za poslední týden)', - 'by_player_yearly' => 'Stažení epizody podle přehrávače (za poslední rok)', - 'by_device_weekly' => 'Stažení epizody podle podle zařízení (za poslední týden)', - 'by_os_weekly' => 'Stažení epizody podle O.S. (za poslední týden)', - 'podcast_by_region' => 'Stažení epizody podle regionu (za poslední týden)', - 'unique_daily_listeners' => 'Denní unikátní posluchači', - 'unique_monthly_listeners' => 'Měsíční unikátní posluchači', - 'by_browser' => 'Využití webových stránek podle prohlížeče (za poslední týden)', - 'podcast_by_day' => 'Denní stažení epizody', - 'podcast_by_month' => 'Měsíční stažení epizody', - 'episode_by_day' => 'Denní stažení epizody (prvních 60 dní)', - 'episode_by_month' => 'Měsíční stažení epizody', - 'episodes_by_day' => - 'Stažení 5 posledních epizod (během prvních 60 dnů)', - 'by_country_weekly' => 'Stažení epizody podle země (za poslední týden)', - 'by_country_yearly' => 'Stažení epizody podle země (za poslední rok)', - 'by_domain_weekly' => 'Návštěvy webových stránek podle zdroje (za minulý týden)', - 'by_domain_yearly' => 'Návštěvy webových stránek podle zdroje (za poslední rok)', - 'by_entry_page' => 'Návštěvy webových stránek podle vstupní stránky (za minulý týden)', - 'podcast_bots' => 'Roboti (crawlers)', - 'daily_listening_time' => 'Denní souhrnný čas poslechu', - 'monthly_listening_time' => 'Měsíční souhrnný čas poslechu', - 'by_weekday' => 'Podle dne v týdnu (za poslednách 60 dní)', - 'by_hour' => 'Podle denní doby (za posledních 60 dní)', - 'podcast_by_bandwidth' => 'Denní využití dat (v MB)', - 'total_storage_by_month' => 'Měsíční úložiště (v MB)', - 'total_bandwidth_by_month' => 'Měsíční využití dat (v MB)', - 'total_bandwidth_by_month_limit' => 'Omezeno na {totalBandwidth} měsíčně', -]; diff --git a/modules/Admin/Language/cs/Common.php b/modules/Admin/Language/cs/Common.php deleted file mode 100644 index 59187fa8..00000000 --- a/modules/Admin/Language/cs/Common.php +++ /dev/null @@ -1,52 +0,0 @@ - 'Ano', - 'no' => 'Ne', - 'cancel' => 'Zrušit', - 'optional' => 'Volitelné', - 'more' => 'Více', - 'no_data' => 'Nenalezena žádná data', - 'close' => 'Zavřít', - 'edit' => 'Editovat', - 'copy' => 'Kopírovat', - 'copied' => 'Zkopírováno!', - 'home' => 'Domů', - 'explicit' => 'Explicitní', - 'powered_by' => 'Běží na {castopod}', - 'actions' => 'Akce', - 'pageInfo' => 'Strana {currentPage} z {pageCount}', - 'go_back' => 'Jít zpět', - 'forms' => [ - 'editor' => [ - 'write' => 'Zapsat', - 'preview' => 'Náhled', - 'help' => 'Používá markdown', - ], - 'multiSelect' => [ - 'selectText' => 'Stiskněte pro výběr', - 'loadingText' => 'Načítání…', - 'noResultsText' => 'Nebyly nalezeny žádné výsledky', - 'noChoicesText' => 'Žádná volba pro výběr', - 'maxItemText' => 'Nelze přidat více položek', - ], - 'upload_file' => 'Nahrát soubor', - 'remote_url' => 'Vzdálené URL', - 'save' => 'Uložit', - ], - 'play_episode_button' => [ - 'play' => 'Přehrát', - 'playing' => 'Přehrávání', - ], - 'size_limit' => 'Omezení velikosti: {0}.', - 'choose_interact' => 'Zvolte způsob interakce', - 'view' => 'Zobrazit', -]; diff --git a/modules/Admin/Language/cs/Contributor.php b/modules/Admin/Language/cs/Contributor.php deleted file mode 100644 index aa359073..00000000 --- a/modules/Admin/Language/cs/Contributor.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Přispěvatelé podcastu', - 'view' => "Příspěvek {username} pro {podcastTitle}", - 'add' => 'Přidat přispěvatele', - 'add_contributor' => 'Přidat přispěvatele pro {0}', - 'edit_role' => 'Aktualizovat roli pro {0}', - 'edit' => 'Upravit', - 'remove' => 'Odstranit', - 'list' => [ - 'username' => 'Uživatelské jméno', - 'role' => 'Role', - ], - 'form' => [ - 'user' => 'Uživatel', - 'user_placeholder' => 'Vyberte uživatele…', - 'role' => 'Role', - 'role_placeholder' => 'Vyberte roli…', - 'submit_add' => 'Přidat přispěvatele', - 'submit_edit' => 'Aktualizovat roli', - ], - 'roles' => [ - 'podcast_admin' => 'Administrátor podcastu', - ], - 'messages' => [ - 'removeOwnerError' => "Vlastníka podcastu nelze odstranit!", - 'removeSuccess' => - 'Úspěšně jste odstranili {username} z {podcastTitle}', - 'alreadyAddedError' => - "Přispěvatel, který se pokoušíte přidat, již byl přidán!", - ], -]; diff --git a/modules/Admin/Language/cs/Countries.php b/modules/Admin/Language/cs/Countries.php deleted file mode 100644 index a7af49a9..00000000 --- a/modules/Admin/Language/cs/Countries.php +++ /dev/null @@ -1,264 +0,0 @@ - 'Andorra', - 'AE' => 'Spojené arabské emiráty', - 'AF' => 'Afghánistán', - 'AG' => 'Antigua a Barbuda', - 'AI' => 'Anguilla', - 'AL' => 'Albánie', - 'AM' => 'Arménie', - 'AO' => 'Angola', - 'AQ' => 'Antarktida', - 'AR' => 'Argentina', - 'AS' => 'Americká Samoa', - 'AT' => 'Rakousko', - 'AU' => 'Austrálie', - 'AW' => 'Aruba', - 'AX' => 'Alandské ostrovy', - 'AZ' => 'Azerbajdžán', - 'BA' => 'Bosna a Hercegovina', - 'BB' => 'Barbados', - 'BD' => 'Bangladéš', - 'BE' => 'Belgie', - 'BF' => 'Burkina Faso', - 'BG' => 'Bulharsko', - 'BH' => 'Bahrajn', - 'BI' => 'Burundi', - 'BJ' => 'Benin', - 'BL' => 'Svatý Bartoloměj', - 'BM' => 'Bermudy', - 'BN' => 'Brunej', - 'BO' => 'Bolívie', - 'BQ' => 'Bonaire, Svatý Eustach a Saba', - 'BR' => 'Brazílie', - 'BS' => 'Bahamy', - 'BT' => 'Bhútán', - 'BV' => 'Bouvetův ostrov', - 'BW' => 'Botswana', - 'BY' => 'Bělorusko', - 'BZ' => 'Belize', - 'CA' => 'Kanada', - 'CC' => 'Kokosové (Keelingovy) ostrovy', - 'CD' => 'Konžská demokratická republika', - 'CF' => 'Středoafrická republika', - 'CG' => 'Kongo', - 'CH' => 'Švýcarsko', - 'CI' => "Pobřeží slonoviny", - 'CK' => 'Cookovy ostrovy', - 'CL' => 'Chile', - 'CM' => 'Kamerun', - 'CN' => 'Čína', - 'CO' => 'Kolumbie', - 'CR' => 'Kostarika', - 'CU' => 'Kuba', - 'CV' => 'Kapverdy', - 'CW' => 'Curaçao', - 'CX' => 'Vánoční ostrov', - 'CY' => 'Kypr', - 'CZ' => 'Česká republika', - 'DE' => 'Německo', - 'DJ' => 'Džibuti', - 'DK' => 'Dánsko', - 'DM' => 'Dominika', - 'DO' => 'Dominikánská republika', - 'DZ' => 'Alžírsko', - 'EC' => 'Ekvádor', - 'EE' => 'Estonsko', - 'EG' => 'Egypt', - 'EH' => 'Západní Sahara', - 'ER' => 'Eritrea', - 'ES' => 'Španělsko', - 'ET' => 'Etiopie', - 'FI' => 'Finsko', - 'FJ' => 'Fidži', - 'FK' => 'Falklandy', - 'FM' => 'Federativní státy Mikronésie', - 'FO' => 'Faerské ostrovy', - 'FR' => 'Francie', - 'GA' => 'Gabon', - 'GB' => 'Spojené království', - 'GD' => 'Grenada', - 'GE' => 'Gruzie', - 'GF' => 'Francouzská Guyana', - 'GG' => 'Guernsey', - 'GH' => 'Ghana', - 'GI' => 'Gibraltar', - 'GL' => 'Grónsko', - 'GM' => 'Gambie', - 'GN' => 'Guinea', - 'GP' => 'Guadeloupe', - 'GQ' => 'Rovníková Guinea', - 'GR' => 'Řecko', - 'GS' => 'Jižní Georgie a Jižní Sandwichovy ostrovy', - 'GT' => 'Guatemala', - 'GU' => 'Guam', - 'GW' => 'Guinea-Bissau', - 'GY' => 'Guyana', - 'HK' => 'Hong Kong', - 'HM' => 'Heardův ostrov a McDonaldovy ostrovy', - 'HN' => 'Honduras', - 'HR' => 'Chorvatsko', - 'HT' => 'Haiti', - 'HU' => 'Maďarsko', - 'ID' => 'Indonésie', - 'IE' => 'Irsko', - 'IL' => 'Izrael', - 'IM' => 'Ostrov Man', - 'IN' => 'Indie', - 'IO' => 'Britské indickooceánské území', - 'IQ' => 'Irák', - 'IR' => 'Íránská islámská republika', - 'IS' => 'Island', - 'IT' => 'Itálie', - 'JE' => 'Jersey', - 'JM' => 'Jamajka', - 'JO' => 'Jordánsko', - 'JP' => 'Japonsko', - 'KE' => 'Keňa', - 'KG' => 'Kyrgyzstán', - 'KH' => 'Kambodža', - 'KI' => 'Kiribati', - 'KM' => 'Komory', - 'KN' => 'Svatý Kryštof a Nevis', - 'KP' => "Severní Korea", - 'KR' => 'Korea', - 'KW' => 'Kuvajt', - 'KY' => 'Kajmanské ostrovy', - 'KZ' => 'Kazachstán', - 'LA' => "Laos", - 'LB' => 'Libanon', - 'LC' => 'Svatá Lucie', - 'LI' => 'Lichtenštejnsko', - 'LK' => 'Srí Lanka', - 'LR' => 'Libérie', - 'LS' => 'Lesotho', - 'LT' => 'Litva', - 'LU' => 'Lucembursko', - 'LV' => 'Lotyšsko', - 'LY' => 'Libye', - 'MA' => 'Maroko', - 'MC' => 'Monako', - 'MD' => 'Moldavská republika', - 'ME' => 'Černá Hora', - 'MF' => 'Svatý Martin (francouzská část)', - 'MG' => 'Madagaskar', - 'MH' => 'Marshallovy ostrovy', - 'MK' => 'Makedonie, bývalá Jugoslávie', - 'ML' => 'Mali', - 'MM' => 'Myanmar (Barma)', - 'MN' => 'Mongolsko', - 'MO' => 'Macao', - 'MP' => 'Severní Mariany', - 'MQ' => 'Martinik', - 'MR' => 'Mauritánie', - 'MS' => 'Montserrat', - 'MT' => 'Malta', - 'MU' => 'Mauricius', - 'MV' => 'Maledivy', - 'MW' => 'Malawi', - 'MX' => 'Mexiko', - 'MY' => 'Malajsie', - 'MZ' => 'Mosambik', - 'N/A' => 'Nelze zvolit (lokální IP…)', - 'NA' => 'Namibie', - 'NC' => 'Nová Kaledonie', - 'NE' => 'Niger', - 'NF' => 'Ostrov Norfolk', - 'NG' => 'Nigérie', - 'NI' => 'Nikaragua', - 'NL' => 'Nizozemsko', - 'NO' => 'Norsko', - 'NP' => 'Nepál', - 'NR' => 'Nauru', - 'NU' => 'Niué', - 'NZ' => 'Nový Zéland', - 'OM' => 'Omán', - 'PA' => 'Panama', - 'PE' => 'Peru', - 'PF' => 'Francouzská Polynésie', - 'PG' => 'Papua-Nová Guinea', - 'PH' => 'Filipíny', - 'PK' => 'Pákistán', - 'PL' => 'Polsko', - 'PM' => 'Saint Pierre a Miquelon', - 'PN' => 'Pitcairnovy ostrovy', - 'PR' => 'Portoriko', - 'PS' => 'Palestina', - 'PT' => 'Portugalsko', - 'PW' => 'Palau', - 'PY' => 'Paraguay', - 'QA' => 'Katar', - 'RE' => 'Réunion', - 'RO' => 'Rumunsko', - 'RS' => 'Srbsko', - 'RU' => 'Ruská federace', - 'RW' => 'Rwanda', - 'SA' => 'Saúdská Arábie', - 'SB' => 'Šalamounovy ostrovy', - 'SC' => 'Seychely', - 'SD' => 'Súdán', - 'SE' => 'Švédsko', - 'SG' => 'Singapur', - 'SH' => 'Svatá Helena, Ascension a Tristan da Cunha', - 'SI' => 'Slovinsko', - 'SJ' => 'Špicberky', - 'SK' => 'Slovensko', - 'SL' => 'Sierra Leone', - 'SM' => 'San Marino', - 'SN' => 'Senegal', - 'SO' => 'Somálsko', - 'SR' => 'Surinam', - 'SS' => 'Jižní Súdán', - 'ST' => 'Svatý Tomáš a Princův ostrov', - 'SV' => 'El Salvador', - 'SX' => 'Svatý Martin (nizozemská část)', - 'SY' => 'Sýrie', - 'SZ' => 'Svazijsko', - 'TC' => 'Ostrovy Turks a Caicos', - 'TD' => 'Čad', - 'TF' => 'Francouzská jižní území', - 'TG' => 'Togo', - 'TH' => 'Thajsko', - 'TJ' => 'Tádžikistán', - 'TK' => 'Tokelau', - 'TL' => 'Východní Timor', - 'TM' => 'Turkmenistán', - 'TN' => 'Tunisko', - 'TO' => 'Tonga', - 'TR' => 'Turecko', - 'TT' => 'Trinidad a Tobago', - 'TV' => 'Tuvalu', - 'TW' => 'Tchajwan, provincie Číny', - 'TZ' => 'Tanzanie, Spojené republiky', - 'UA' => 'Ukrajina', - 'UG' => 'Uganda', - 'UM' => 'Menší odlehlé ostrovy Spojených států amerických', - 'US' => 'Spojené státy', - 'UY' => 'Uruguay', - 'UZ' => 'Uzbekistán', - 'VA' => 'Svatý stolec (Vatikánský městský stát)', - 'VC' => 'Svatý Vincent a Grenadiny', - 'VE' => 'Venezuela, Bolívarovská republika', - 'VG' => 'Britské Panenské ostrovy', - 'VI' => 'Americké Panenské ostrovy', - 'VN' => 'Vietnam', - 'VU' => 'Vanuatu', - 'WF' => 'Wallis a Futuna', - 'WS' => 'Samoa', - 'YE' => 'Jemen', - 'YT' => 'Mayotte', - 'ZA' => 'Jižní Afrika', - 'ZM' => 'Zambie', - 'ZW' => 'Zimbabwe', -]; diff --git a/modules/Admin/Language/cs/Dashboard.php b/modules/Admin/Language/cs/Dashboard.php deleted file mode 100644 index e66e362b..00000000 --- a/modules/Admin/Language/cs/Dashboard.php +++ /dev/null @@ -1,28 +0,0 @@ - 'Administrátorský panel', - 'welcome_message' => 'Vítejte v admin oblasti!', - 'podcasts' => [ - 'title' => 'Podcasty', - 'not_found' => 'Žádné publikované podcasty', - 'last_published' => 'Naposledy publikováno {lastPublicationDate}', - ], - 'episodes' => [ - 'title' => 'Epizody', - 'not_found' => 'Žádné publikované epizody', - 'last_published' => 'Naposledy publikováno {lastPublicationDate}', - ], - 'storage' => [ - 'title' => 'Úložiště', - 'subtitle' => '{totalUploaded} z {totalStorage}', - ], -]; diff --git a/modules/Admin/Language/cs/Episode.php b/modules/Admin/Language/cs/Episode.php deleted file mode 100644 index 2595eb2e..00000000 --- a/modules/Admin/Language/cs/Episode.php +++ /dev/null @@ -1,225 +0,0 @@ - 'Série {seasonNumber}', - 'season_abbr' => 'S{seasonNumber}', - 'number' => 'Epizoda {episodeNumber}', - 'number_abbr' => 'Ep. {episodeNumber}', - 'season_episode' => 'Série {seasonNumber} epizoda {episodeNumber}', - 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', - 'number_of_comments' => '{numberOfComments, plural, - one {# komentář} - other {# komentáře} - }', - 'all_podcast_episodes' => 'Všechny epizody podcastu', - 'back_to_podcast' => 'Přejít zpět na podcast', - 'edit' => 'Upravit', - 'preview' => 'Náhled', - 'publish' => 'Publikovat', - 'publish_edit' => 'Upravit publikování', - 'publish_date_edit' => 'Upravit datum publikování', - 'unpublish' => 'Zrušit publikování', - 'publish_error' => 'Epizoda již byla publikována.', - 'publish_edit_error' => 'Epizoda již byla publikována.', - 'publish_cancel_error' => 'Epizoda již byla publikována.', - 'publish_date_edit_error' => 'Epizoda ještě nebyla publikována, nemůžete upravit datum jejího zveřejnění.', - 'publish_date_edit_future_error' => 'Datum publikování epizody může být nastaveno pouze na dřívější datum! Pokud chcete změnit naplánování, nejprve zrušte publikování.', - 'publish_date_edit_success' => 'Datum publikování epizody bylo úspěšně aktualizováno!', - 'unpublish_error' => 'Epizoda není publikována.', - 'delete' => 'Smazat', - 'go_to_page' => 'Přejít na stránku', - 'create' => 'Přidat epizodu', - 'publication_status' => [ - 'published' => 'Publikováno', - 'with_podcast' => 'Publikováno', - 'scheduled' => 'Naplánováno', - 'not_published' => 'Nepublikováno', - ], - 'with_podcast_hint' => 'Zveřejní se současně s podcastem', - 'list' => [ - 'search' => [ - 'placeholder' => 'Hledat epizodu', - 'clear' => 'Vymazat vyhledávání', - 'submit' => 'Hledat', - ], - 'number_of_episodes' => '{numberOfEpisodes, plural, - one {# epizoda} - other {# epizody} - }', - 'episode' => 'Epizoda', - 'visibility' => 'Viditelnost', - 'downloads' => 'Stažení', - 'comments' => 'Komentáře', - 'actions' => 'Akce', - ], - 'messages' => [ - 'createSuccess' => 'Epizoda byla úspěšně vytvořena!', - 'editSuccess' => 'Epizoda byla úspěšně aktualizována!', - 'publishSuccess' => '{publication_status, select, - published {Epizoda byla úspěšně publikována!} - scheduled {Publikace epizody byla úspěšně naplánována!} - with_podcast {Tato epizoda bude zveřejněna současně s podcastem.} - other {Tato epizoda není publikována.} - }', - 'publishCancelSuccess' => 'Publikování epizody úspěšně zrušeno!', - 'unpublishBeforeDeleteTip' => 'Je nutné zrušit publikování epizody před jejím odstraněním.', - 'scheduleDateError' => 'Musí být nastaveno datum publikování!', - 'deletePublishedEpisodeError' => 'Před odstraněním epizody prosím zrušte publikování.', - 'deleteSuccess' => 'Epizoda byla úspěšně smazána!', - 'deleteError' => 'U epizody se nepodařilo odstranit {type, select, - transcript {přepis} - chapters {kapitoly} - image {obal} - audio {audio} - other {media} - }', - 'deleteFileError' => 'Nepodařilo se odstranit {type, select, - transcript {přepis} - chapters {kapitoly} - image {obal} - audio {audio} - other {média} - } soubor {file_key}. Můžete ručně odebrat ze svého disku.', - 'sameSlugError' => 'Epizoda se zvolenou částí URL již existuje.', - ], - 'form' => [ - 'file_size_error' => - 'Soubor je příliš velký! Maximální velikost je {0}. Zvyšte hodnoty `memory_limit`, `upload_max_filesize` a `post_max_size` v konfiguračním souboru php a pak restartujte váš webový server pro nahrání souboru.', - 'audio_file' => 'Zvukový soubor', - 'audio_file_hint' => 'Vyberte zvukový soubor .mp3 nebo .m4a.', - 'info_section_title' => 'Informace o epizodě', - 'cover' => 'Obal epizody', - 'cover_hint' => - 'Pokud nenastavíte obal, bude místo toho použit obal podcastu.', - 'cover_size_hint' => 'Obal musí být čtvercový a nejméně 1400px široký a vysoký.', - 'title' => 'Název', - 'title_hint' => - 'Mělo by obsahovat jasný a stručný název epizody. Zde nespecifikujte čísla epizod nebo sezóny.', - 'permalink' => 'Trvalý odkaz', - 'season_number' => 'Série', - 'episode_number' => 'Epizoda', - 'type' => [ - 'label' => 'Typ', - 'full' => 'Plné', - 'full_hint' => 'Kompletní obsah (epizoda)', - 'trailer' => 'Upoutávka', - 'trailer_hint' => 'Krátký propagační materiál, který představuje náhled aktuálního seriálu', - 'bonus' => 'Bonus', - 'bonus_hint' => 'Extra obsah pro seriál (například ze zákulisí nebo rozhovory s účinkujícími) nebo průřezový propagační obsah pro jiný seriál', - ], - 'premium_title' => 'Prémium', - 'premium' => 'Epizoda musí být přístupná pouze pro prémiové odběratele', - 'parental_advisory' => [ - 'label' => 'Rodičovské informace', - 'hint' => 'Obsahuje epizoda explicitní obsah?', - 'undefined' => 'nedefinováno', - 'clean' => 'Čisté', - 'explicit' => 'Explicitní', - ], - 'show_notes_section_title' => 'Zobrazit poznámky', - 'show_notes_section_subtitle' => - 'Až 4000 znaků, buďte jasní a struční. Poznámky pomáhají potenciálním posluchačům při hledání epizody.', - 'description' => 'Popis', - 'description_footer' => 'Zápatí popisu', - 'description_footer_hint' => - 'Tento text je přidán na konec popisu každé epizody, je to dobré místo pro vložení vašich sociálních odkazů.', - 'additional_files_section_title' => 'Další soubory', - 'additional_files_section_subtitle' => - 'Tyto soubory mohou být použity jinými platformami pro lepší zážitek pro vaše publikum. Pro více informací si přečtěte {podcastNamespaceLink}.', - 'location_section_title' => 'Místo', - 'location_section_subtitle' => 'O kterém místě je tato epizoda?', - 'location_name' => 'Název nebo adresa místa', - 'location_name_hint' => 'Toto může být skutečné nebo fiktivní místo', - 'transcript' => 'Přepis (titulky)', - 'transcript_hint' => 'Jsou povoleny pouze .srt nebo .vtt.', - 'transcript_download' => 'Stáhnout přepis', - 'transcript_file' => 'Soubor přepisu (.srt nebo .vtt)', - 'transcript_remote_url' => 'Vzdálená URL pro přepis', - 'transcript_file_delete' => 'Odstranit soubor přepisu', - 'chapters' => 'Kapitoly', - 'chapters_hint' => 'Soubor musí být ve formátu JSON kapitol.', - 'chapters_download' => 'Stáhnout kapitoly', - 'chapters_file' => 'Soubor kapitol', - 'chapters_remote_url' => 'Vzdálená url pro soubor kapitol', - 'chapters_file_delete' => 'Odstranit soubor kapitol', - 'advanced_section_title' => 'Pokročilá nastavení', - 'advanced_section_subtitle' => - 'Pokud potřebujete RSS tagy, které Castopod nepodporuje, nastavte je zde.', - 'custom_rss' => 'Vlastní RSS tagy pro epizodu', - 'custom_rss_hint' => 'Toto bude vloženo do tagu ❬item❭.', - 'block' => 'Epizoda by měla být skryta ve veřejných katalogech', - 'block_hint' => - 'Zobrazit nebo skrýt stav: přepnutí tohoto zabraňuje tomu, aby se epizoda objevila v Apple Podcasts, Google Podcasts, a všech aplikacích třetích stran, které stahují seriály z těchto adresářů. (Nezaručeno)', - 'submit_create' => 'Vytvořit epizodu', - 'submit_edit' => 'Uložit epizodu', - ], - 'publish_form' => [ - 'back_to_episode_dashboard' => 'Zpět na nástěnku epizody', - 'post' => 'Váš oznamovací příspěvek', - 'post_hint' => - "Napište zprávu pro oznámení zveřejnění vaší epizody. Zpráva bude odeslána všem vašim následovníkům ve fediverse a bude zobrazena na domovské stránce vašeho podcastu.", - 'message_placeholder' => 'Napište zprávu…', - 'publication_date' => 'Datum publikování', - 'publication_method' => [ - 'now' => 'Teď', - 'schedule' => 'Naplánovat', - 'with_podcast' => 'Publikovat s podcastem', - ], - 'scheduled_publication_date' => 'Naplánované datum publikování', - 'scheduled_publication_date_clear' => 'Vymazat datum publikování', - 'scheduled_publication_date_hint' => - 'Vydání epizody můžete naplánovat nastavením data zveřejnění. Toto pole musí být formátováno jako YY-MM-DD HH:mm', - 'submit' => 'Publikovat', - 'submit_edit' => 'Upravit publikování', - 'cancel_publication' => 'Zrušit publikování', - 'message_warning' => 'Nepsali jste zprávu pro váš příspěvek s oznámením!', - 'message_warning_hint' => 'Zpráva zvyšuje viditelnost na sociálních sítích, což má za následek lepší popularitu vaší epizody.', - 'message_warning_submit' => 'Přesto publikovat', - ], - 'publish_date_edit_form' => [ - 'new_publication_date' => 'Nové datum publikace', - 'new_publication_date_hint' => 'Musí být nastaveno na uplynulé datum.', - 'submit' => 'Upravit datum publikace', - ], - 'unpublish_form' => [ - 'disclaimer' => - "Zrušením publikování epizody smažete všechny komentáře a příspěvky spojené s ní a odeberete z RSS kanálu podcastu.", - 'understand' => 'Chápu, chci zrušit publikování epizody', - 'submit' => 'Zrušit publikování', - ], - 'delete_form' => [ - 'disclaimer' => - "Smazáním epizody smažete všechny mediální soubory, komentáře, videoklipy a zvuky, které jsou s ní spojeny.", - 'understand' => 'Chápu, chci odstranit epizodu', - 'submit' => 'Smazat', - ], - 'embed' => [ - 'title' => 'Vložitelný přehrávač', - 'label' => - 'Vyberte si barvu motivu, zkopírujte vložený přehrávač do schránky a vložte jej na váš web.', - 'clipboard_iframe' => 'Kopírovat vložitelný přehrávač do schránky', - 'clipboard_url' => 'Kopírovat adresu do schránky', - 'dark' => 'Tmavý', - 'dark-transparent' => 'Tmavý průhledný', - 'light' => 'Světlý', - 'light-transparent' => 'Světlý průhledný', - ], - 'publication_status_banner' => [ - 'draft_mode' => 'režim konceptu', - 'text' => '{publication_status, select, - published {Tato epizoda ještě není publikována.} - scheduled {Tato epizoda je naplánována pro publikování na {publication_date}} - with_podcast {Tato epizoda bude zveřejněna současně s podcastem.} - other {Tato epizoda ještě není publikována.} - }', - 'preview' => 'Náhled', - ], -]; diff --git a/modules/Admin/Language/cs/EpisodeNavigation.php b/modules/Admin/Language/cs/EpisodeNavigation.php deleted file mode 100644 index 46fd1d15..00000000 --- a/modules/Admin/Language/cs/EpisodeNavigation.php +++ /dev/null @@ -1,23 +0,0 @@ - 'Zobrazit stránku epizody', - 'dashboard' => 'Nástěnka epizody', - 'episode-view' => 'Domů', - 'episode-edit' => 'Upravit epizodu', - 'episode-persons-manage' => 'Spravovat osoby', - 'embed-add' => 'Vložitelný přehrávač', - 'clips' => 'Klipy', - 'video-clips-list' => 'Videoklipy', - 'video-clips-create' => 'Nový video klip', - 'soundbites-list' => 'Úryvky', - 'soundbites-create' => 'Nový úryvek', -]; diff --git a/modules/Admin/Language/cs/Fediverse.php b/modules/Admin/Language/cs/Fediverse.php deleted file mode 100644 index d74fac27..00000000 --- a/modules/Admin/Language/cs/Fediverse.php +++ /dev/null @@ -1,32 +0,0 @@ - [ - 'actorNotFound' => 'Účet nebyl nalezen.', - 'blockActorSuccess' => '{actor} byl zablokován!', - 'unblockActorSuccess' => 'Subjekt byl odblokován!', - 'blockDomainSuccess' => '{domain} bylo zablokováno!', - 'unblockDomainSuccess' => '{domain} bylo odblokováno!', - ], - 'blocked_actors' => 'Blokované účty', - 'blocked_domains' => 'Blokované domény', - 'block_lists_form' => [ - 'handle' => 'Handle účtu', - 'handle_hint' => 'Vložte @username@domain účet.', - 'domain' => 'Název domény', - 'submit' => 'Blokovat!', - ], - 'list' => [ - 'actor' => 'Účet', - 'domain' => 'Název domény', - 'unblock' => 'Odblokovat', - ], -]; diff --git a/modules/Admin/Language/cs/Home.php b/modules/Admin/Language/cs/Home.php deleted file mode 100644 index 25068818..00000000 --- a/modules/Admin/Language/cs/Home.php +++ /dev/null @@ -1,14 +0,0 @@ - 'Všechny podcasty', - 'no_podcast' => 'Nebyly nalezeny žádné podcasty', -]; diff --git a/modules/Admin/Language/cs/Install.php b/modules/Admin/Language/cs/Install.php deleted file mode 100644 index f39c1182..00000000 --- a/modules/Admin/Language/cs/Install.php +++ /dev/null @@ -1,61 +0,0 @@ - 'Ruční konfigurace', - 'manual_config_subtitle' => - 'Vytvořte soubor `.env` s vaším nastavením a obnovte stránku pro pokračování instalace.', - 'form' => [ - 'instance_config' => 'Konfigurace instance', - 'hostname' => 'Název hostitele', - 'media_base_url' => 'URL pro média', - 'media_base_url_hint' => - 'Pokud používáte CDN a/nebo externí analytickou službu, můžete je nastavit zde.', - 'admin_gateway' => 'Administrační brána', - 'admin_gateway_hint' => - 'Cesta pro přístup k administraci (např. https://example.com/cp-admin). Ve výchozím nastavení je nastaveno jako cp-admin, doporučujeme ji z bezpečnostních důvodů změnit.', - 'auth_gateway' => 'Ověřovací brána', - 'auth_gateway_hint' => - 'Cesta pro přístup k ověřovacím stránkám (např. https://example.com/cp-auth). Ve výchozím nastavení je nastaveno jako cp-auth, doporučujeme ji z bezpečnostních důvodů změnit.', - 'database_config' => 'Konfigurace databáze', - 'database_config_hint' => - 'Castopod se musí připojit k databázi MySQL (nebo MariaDB). Pokud nemáte tyto požadované informace, kontaktujte prosím správce serveru.', - 'db_hostname' => 'Název hostitele databáze', - 'db_name' => 'Název databáze', - 'db_username' => 'Uživatelské jméno databáze', - 'db_password' => 'Heslo k databázi', - 'db_prefix' => 'Předpona databáze', - 'db_prefix_hint' => - "Předpona Castopod tabulky, neměňte pokud nevíte, co to znamená.", - 'cache_config' => 'Nastavení mezipaměti', - 'cache_config_hint' => - 'Vyberte preferovaného zpracovatele mezipaměti. Ponechte výchozí hodnotu, pokud nemáte přehled o tom, co to znamená.', - 'cache_handler' => 'Zpracovatel mezipaměti', - 'cacheHandlerOptions' => [ - 'file' => 'Soubor', - 'redis' => 'Redis', - 'predis' => 'Predis', - ], - 'next' => 'Další', - 'submit' => 'Dokončit instalaci', - 'create_superadmin' => 'Vytvořte si svůj superadmin účet', - 'email' => 'Email', - 'username' => 'Uživatelské jméno', - 'password' => 'Heslo', - ], - 'messages' => [ - 'createSuperAdminSuccess' => - 'Váš superadmin účet byl úspěšně vytvořen. Přihlaste se a začněte s podcastem!', - 'databaseConnectError' => - 'Castopod se nemohl připojit k databázi. Upravte konfiguraci databáze a zkuste to znovu.', - 'writeError' => - "Nelze vytvořit / zapsat soubor `.env`. Musíte jej vytvořit ručně podle šablony souboru `.env.example` v balíčku Castopod.", - ], -]; diff --git a/modules/Admin/Language/cs/MyAccount.php b/modules/Admin/Language/cs/MyAccount.php deleted file mode 100644 index 5687a728..00000000 --- a/modules/Admin/Language/cs/MyAccount.php +++ /dev/null @@ -1,18 +0,0 @@ - 'Info o účtu', - 'changePassword' => 'Změnit heslo', - 'messages' => [ - 'wrongPasswordError' => "Zadali jste špatné heslo, zkuste to znovu.", - 'passwordChangeSuccess' => 'Heslo bylo úspěšně změněno', - ], -]; diff --git a/modules/Admin/Language/cs/Navigation.php b/modules/Admin/Language/cs/Navigation.php deleted file mode 100644 index 10514836..00000000 --- a/modules/Admin/Language/cs/Navigation.php +++ /dev/null @@ -1,44 +0,0 @@ - 'Boční lišta', - 'go_to_website' => 'Přejít na web', - 'go_to_admin' => 'Přejít do administrace', - 'not-authorized' => 'Neautorizovaný', - 'dashboard' => 'Nástěnka', - 'admin' => 'Domovská stránka', - 'podcasts' => 'Podcasty', - 'podcast-list' => 'Všechny podcasty', - 'podcast-create' => 'Nový podcast', - 'all-podcast-imports' => 'Všechny importy podcastu', - 'podcast-imports-add' => 'Importovat podcast', - 'persons' => 'Osoby', - 'person-list' => 'Všechny osoby', - 'person-create' => 'Nová osoba', - 'fediverse' => 'Fediverse', - 'fediverse-blocked-actors' => 'Blokované účty', - 'fediverse-blocked-domains' => 'Blokované domény', - 'users' => 'Uživatelé', - 'user-list' => 'Všichni uživatelé', - 'user-create' => 'Nový uživatel', - 'pages' => 'Stránky', - 'page-list' => 'Všechny stránky', - 'page-create' => 'Nová stránka', - 'settings' => 'Nastavení', - 'settings-general' => 'Obecné', - 'settings-theme' => 'Motiv', - 'admin-about' => 'Info', - 'account' => [ - 'my-account' => 'Můj účet', - 'change-password' => 'Změna hesla', - 'logout' => 'Odhlásit se', - ], -]; diff --git a/modules/Admin/Language/cs/Notifications.php b/modules/Admin/Language/cs/Notifications.php deleted file mode 100644 index 0912671e..00000000 --- a/modules/Admin/Language/cs/Notifications.php +++ /dev/null @@ -1,19 +0,0 @@ - 'Oznámení', - 'reply' => '{actor_username} odpověděl na Váš příspěvek', - 'favourite' => '{actor_username} si oblíbil Váš příspěvek', - 'reblog' => '{actor_username} sdílel Váš příspěvek', - 'follow' => '{actor_username} Vás začal sledovat', - 'no_notifications' => 'Žádná oznámení', - 'mark_all_as_read' => 'Označit vše jako přečtené', -]; diff --git a/modules/Admin/Language/cs/Page.php b/modules/Admin/Language/cs/Page.php deleted file mode 100644 index 841172aa..00000000 --- a/modules/Admin/Language/cs/Page.php +++ /dev/null @@ -1,30 +0,0 @@ - 'Zpátky domů', - 'page' => 'Stránka', - 'all_pages' => 'Všechny stránky', - 'create' => 'Nová stránka', - 'go_to_page' => 'Přejít na stránku', - 'edit' => 'Upravit stránku', - 'delete' => 'Odstranit stránku', - 'form' => [ - 'title' => 'Název', - 'permalink' => 'Trvalý odkaz', - 'content' => 'Obsah', - 'submit_create' => 'Vytvořit stránku', - 'submit_edit' => 'Uložit', - ], - 'messages' => [ - 'createSuccess' => 'Stránka „{pageTitle}“ byla úspěšně vytvořena!', - 'editSuccess' => 'Stránka úspěšně aktualizována!', - ], -]; diff --git a/modules/Admin/Language/cs/Pager.php b/modules/Admin/Language/cs/Pager.php deleted file mode 100644 index 34dab87c..00000000 --- a/modules/Admin/Language/cs/Pager.php +++ /dev/null @@ -1,21 +0,0 @@ - 'Navigace ve stránce', - 'first' => 'První', - 'previous' => 'Předchozí', - 'next' => 'Další', - 'last' => 'Poslední', - 'older' => 'Starší', - 'newer' => 'Novější', - 'invalidTemplate' => '{0} není platná Pager šablona.', - 'invalidPaginationGroup' => '{0} není platná Pagination skupina.', -]; diff --git a/modules/Admin/Language/cs/Person.php b/modules/Admin/Language/cs/Person.php deleted file mode 100644 index 6405203d..00000000 --- a/modules/Admin/Language/cs/Person.php +++ /dev/null @@ -1,65 +0,0 @@ - 'Osoby', - 'all_persons' => 'Všechny osoby', - 'no_person' => 'Nikdo nenalezen!', - 'create' => 'Vytvořit osobu', - 'view' => 'Zobrazit osobu', - 'edit' => 'Upravit osobu', - 'delete' => 'Smazat osobu', - 'messages' => [ - 'createSuccess' => 'Osoba byla úspěšně vytvořena!', - 'editSuccess' => 'Osoba byla úspěšně aktualizována!', - 'deleteSuccess' => 'Osoba byla odstraněna!', - ], - 'form' => [ - 'avatar' => 'Avatar', - 'avatar_size_hint' => - 'Avatar musí být čtvercový a alespoň 400px široký a vysoký.', - 'full_name' => 'Celé jméno', - 'full_name_hint' => 'Toto je celé jméno nebo přezdívka osoby.', - 'unique_name' => 'Jedinečné jméno', - 'unique_name_hint' => 'Používá se pro URL', - 'information_url' => 'URL informací', - 'information_url_hint' => - 'URL na relevantní zdroj informací o osobě, jako je domovská stránka nebo platforma profilu třetí strany.', - 'submit_create' => 'Vytvořit osobu', - 'submit_edit' => 'Uložit osobu', - ], - 'podcast_form' => [ - 'title' => 'Spravovat osoby', - 'add_section_title' => 'Přidat osoby do tohoto podcastu', - 'add_section_subtitle' => 'Můžete si vybrat několik osob a rolí.', - 'persons' => 'Osoby', - 'persons_hint' => - 'Můžete vybrat jednu nebo více osob se stejnými roli. Nejprve je třeba vytvořit osoby.', - 'roles' => 'Role', - 'roles_hint' => - 'Můžete si vybrat žádné, jednu nebo více rolí pro osobu.', - 'submit_add' => 'Přidat osobu (osoby)', - 'remove' => 'Odstranit', - ], - 'episode_form' => [ - 'title' => 'Spravovat osoby', - 'add_section_title' => 'Přidat osoby do této epizody', - 'add_section_subtitle' => 'Můžete si vybrat několik osob a rolí.', - 'persons' => 'Osoby', - 'persons_hint' => - 'Můžete vybrat jednu nebo více osob se stejnými roli. Nejprve je třeba vytvořit osoby.', - 'roles' => 'Role', - 'roles_hint' => - 'Můžete si vybrat žádné, jednu nebo více rolí pro osobu.', - 'submit_add' => 'Přidat osobu (osoby)', - 'remove' => 'Odebrat', - ], - 'credits' => 'Zásluhy', -]; diff --git a/modules/Admin/Language/cs/Platforms.php b/modules/Admin/Language/cs/Platforms.php deleted file mode 100644 index d1219d7e..00000000 --- a/modules/Admin/Language/cs/Platforms.php +++ /dev/null @@ -1,43 +0,0 @@ - [ - 'podcasting' => 'Platformy pro podcast', - 'social' => 'Sociální sítě', - 'funding' => 'Odkazy na financování', - ], - 'website' => 'Webová stránka', - 'home_url' => 'Přejít na web {platformName}', - 'register' => 'Registrovat se', - 'submit_url' => 'Odeslat podcast na {platformName}', - 'your_link' => 'Váš odkaz', - 'your_id' => [ - 'podcasting' => 'Vaše ID', - 'social' => 'Vaše ID', - 'funding' => 'Vaše CTA', - ], - 'your_cta' => 'Vaše výzva k akci', - 'visible' => 'Zobrazit na domovské stránce podcastu?', - 'on_embed' => 'Zobrazit na vložitelném přehrávači?', - 'remove' => 'Odstranit {platformName}', - 'submit' => 'Uložit', - 'messages' => [ - 'updateSuccess' => 'Odkazy na platformu byly úspěšně aktualizovány!', - 'removeLinkSuccess' => 'Odkaz na platformu byl odstraněn.', - 'removeLinkError' => - 'Odkaz na platformu nelze odstranit. Zkuste to znovu.', - ], - 'description' => [ - 'podcasting' => 'ID podcastu na této platformě', - 'social' => 'ID účtu podcast na této platformě', - 'funding' => 'Zpráva u výzvy k akci', - ], -]; diff --git a/modules/Admin/Language/cs/Podcast.php b/modules/Admin/Language/cs/Podcast.php deleted file mode 100644 index b65f2221..00000000 --- a/modules/Admin/Language/cs/Podcast.php +++ /dev/null @@ -1,330 +0,0 @@ - 'Všechny podcasty', - 'no_podcast' => 'Nenalezeny žádné podcasty!', - 'create' => 'Vytvořit podcast', - 'import' => 'Importovat podcast', - 'all_imports' => 'Importy podcastu', - 'new_episode' => 'Nová epizoda', - 'view' => 'Zobrazit podcast', - 'edit' => 'Upravit podcast', - 'publish' => 'Publikovat podcast', - 'publish_edit' => 'Upravit publikování', - 'delete' => 'Odstranit podcast', - 'see_episodes' => 'Zobrazit epizody', - 'see_contributors' => 'Zobrazit přispěvatele', - 'monetization_other' => 'Jiná monetizace', - 'go_to_page' => 'Přejít na stránku', - 'latest_episodes' => 'Nejnovější epizody', - 'see_all_episodes' => 'Zobrazit všechny epizody', - 'draft' => 'Koncept', - 'messages' => [ - 'createSuccess' => 'Podcast úspěšně vytvořen!', - 'editSuccess' => 'Podcast byl úspěšně aktualizován!', - 'importSuccess' => 'Podcast byl úspěšně importován!', - 'deleteSuccess' => 'Podcast @{podcast_handle} byl úspěšně smazán!', - 'deletePodcastMediaError' => 'U podcastu se nepodařilo odstranit {type, select, - cover {obal} - banner {banner} - other {media} - }', - 'deleteEpisodeMediaError' => 'U epizody podcastu {episode_slug} se nepodařilo odstranit {type, select, - transcript {přepis} - chapters {kapitoly} - image {obal} - audio {audio} - other {media} - }', - 'deletePodcastMediaFolderError' => 'Odstranění složky médií podcast {folder_path} se nezdařilo. Můžete ji ručně odebrat z disku.', - 'podcastFeedUpdateSuccess' => 'Úspěšná aktualizace: {number_of_new_episodes, plural, - one {# epizoda byla přidána} - other {# epizody byly přidány} - } k podcastu!', - 'podcastFeedUpToDate' => 'Podcast je již aktuální.', - 'publishError' => 'Tento podcast je buď již zveřejněn, nebo je naplánován na publikování.', - 'publishEditError' => 'Tento podcast není naplánován na publikování.', - 'publishCancelSuccess' => 'Publikování podcastu bylo úspěšně zrušeno!', - 'scheduleDateError' => 'Musí být nastaveno datum publikování!', - ], - 'form' => [ - 'identity_section_title' => 'Identita podcastu', - 'identity_section_subtitle' => 'Tato pole vám umožňují získat pozornost.', - 'fediverse_section_title' => 'Fediverse identita', - - 'cover' => 'Obal podcastu', - 'cover_size_hint' => 'Obal musí být čtvercový a nejméně 1400px široký a vysoký.', - 'banner' => 'Banner podcastu', - 'banner_size_hint' => 'Banner musí mít poměr 3:1 a musí být alespoň 1500px široký.', - 'banner_delete' => 'Odstranit banner podcastu', - 'title' => 'Název', - 'handle' => 'Handle', - 'handle_hint' => - 'Používá se k identifikaci podcastu. Velká písmena, malá písmena, čísla a podtržítka jsou přijímána.', - 'type' => [ - 'label' => 'Typ', - 'episodic' => 'Epizodický', - 'episodic_hint' => 'Pokud jsou epizody určeny ke sledování bez konkrétního pořadí. Nejnovější epizody budou prezentovány jako první.', - 'serial' => 'Sériový', - 'serial_hint' => 'Pokud mají být epizody sledovány v sekvenčním pořadí. Epizody budou uvedeny v číselném pořadí.', - ], - 'medium' => [ - 'label' => 'Medium', - 'hint' => 'Medium reprezentováno podcast:medium tagem v RSS. Změna může změnit způsob prezentace vašeho kanálu.', - 'podcast' => 'Podcast', - 'podcast_hint' => 'Popisuje kanál pro seriál podcastu.', - 'music' => 'Hudba', - 'music_hint' => 'Zdroj hudby organizovaný do alba, s každou skladbou v albu.', - 'audiobook' => 'Audiokniha', - 'audiobook_hint' => 'Specifické typy zvuku s jednou položkou z každého zdroje nebo tam, kde položky představují kapitoly v knize.', - ], - 'description' => 'Popis', - 'classification_section_title' => 'Klasifikace', - 'classification_section_subtitle' => - 'Tyto oblasti ovlivní vaše publikum a konkurenci.', - 'language' => 'Jazyk', - 'category' => 'Kategorie', - 'category_placeholder' => 'Vyberte kategorii…', - 'other_categories' => 'Ostatní kategorie', - 'parental_advisory' => [ - 'label' => 'Rodičovské info', - 'hint' => 'Obsahuje explicitní obsah?', - 'undefined' => 'nedefinováno', - 'clean' => 'Čistý', - 'explicit' => 'Explicitní', - ], - 'author_section_title' => 'Autor', - 'author_section_subtitle' => 'Kdo spravuje podcast?', - 'owner_name' => 'Jméno vlastníka', - 'owner_name_hint' => - 'Pouze pro administrativní použití. Viditelné ve veřejném kanálu RSS.', - 'owner_email' => 'E-mail vlastníka', - 'owner_email_hint' => - 'Bude použito většinou platforem k ověření vlastnictví podcastu. Viditelné ve veřejném RSS kanálu.', - 'is_owner_email_removed_from_feed' => 'Odstranit e-mail vlastníka z veřejného RSS kanálu', - 'is_owner_email_removed_from_feed_hint' => 'Možná budete muset dočasně odkrýt e-mail, aby mohl adresář ověřit vlastnictví podcastu.', - 'publisher' => 'Vydavatel', - 'publisher_hint' => - 'Skupina odpovědná za vytvoření seriálu. Často odkazuje na mateřskou společnost nebo síť podcastu. Toto pole je někdy označeno jako \'Autor\'.', - 'copyright' => 'Autorská práva', - 'location_section_title' => 'Místo', - 'location_section_subtitle' => 'O kterém místě je tento podcast?', - 'location_name' => 'Název nebo adresa místa', - 'location_name_hint' => 'To může být skutečné místo nebo fiktivní', - 'monetization_section_title' => 'Monetizace', - 'monetization_section_subtitle' => - 'Vydělávejte peníze díky vašemu publiku.', - 'premium' => 'Prémium', - 'premium_by_default' => 'Epizody musí být nastaveny jako prémiové', - 'premium_by_default_hint' => 'Epizody podcastu budou ve výchozím nastavení označeny jako prémiové. Stále si můžete vybrat nastavení některých epizod, trailery nebo bonusy jako veřejné.', - 'op3' => 'Open Podcast Prefix Project (OP3)', - 'op3_link' => 'Navštivte OP3 dashboard (externí odkaz)', - 'op3_hint' => 'Ohodnoťte svá analytická data OP3, otevřený zdroj a důvěryhodná analytická služba třetích stran. Sdílejte, ověřte a porovnejte svá analytická data s otevřeným ekosystémem podcastingu.', - 'op3_enable' => 'Povolit OP3 analytickou službu', - 'op3_enable_hint' => 'Z bezpečnostních důvodů nebudou analytická data prémiových epizod sdílena s OP3.', - 'payment_pointer' => 'Platební ukazatel pro Web Monetization', - 'payment_pointer_hint' => - 'Toto je Vaše místo, kde obdržíte peníze díky Web Monetization', - 'advanced_section_title' => 'Pokročilá nastavení', - 'advanced_section_subtitle' => - 'Pokud potřebujete RSS tagy, které Castopod nepodporuje, nastavte je zde.', - 'custom_rss' => 'Vlastní RSS tagy pro podcast', - 'custom_rss_hint' => 'Toto bude vloženo do tagu ❬channel❭.', - 'verify_txt' => 'Ověření vlastnictví TXT', - 'verify_txt_hint' => 'Některé služby třetích stran mohou spíše než spoléhat na e-mail, potvrdit vlastnictví podcastu požadavkem na vložení ověřovacího textu do vašeho kanálu.', - 'verify_txt_helper' => 'Tento text je vložen do tagu.', - 'new_feed_url' => 'Nová URL kanálu', - 'new_feed_url_hint' => 'Použijte toto pole při přesunu na jinou doménu nebo hostitelskou platformu podcastu. Ve výchozím nastavení je hodnota nastavena na aktuální RSS URL, pokud je podcast importován.', - 'old_feed_url' => 'Stará URL kanálu', - 'partnership' => 'Partnerství', - 'partner_id' => 'ID', - 'partner_link_url' => 'URL odkazu', - 'partner_image_url' => 'URL obrázku', - 'partner_id_hint' => 'Vaše vlastní partnerské ID', - 'partner_link_url_hint' => 'Generická adresa partnera', - 'partner_image_url_hint' => 'Generická adresa obrázku partnera', - 'block' => 'Podcast by měl být skrytý před veřejnými katalogy', - 'block_hint' => - 'Zobrazit nebo skrýt stav podcastu: přepnutí zabraňuje tomu, aby se celý podcast objevil v Apple Podcasts, Google Podcasts, a všech aplikacích třetích stran, které stahují seriály z těchto adresářů. (Nezaručeno)', - 'complete' => 'Podcast nebude mít nové epizody', - 'lock' => 'Zabránit kopírování podcastu', - 'lock_hint' => - 'Účelem je sdělit ostatním platformám podcastu, zda mohou tento kanál importovat. Hodnota ano znamená, že jakýkoli pokus o import tohoto kanálu do nové platformy by měl být zamítnut.', - 'submit_create' => 'Vytvořit podcast', - 'submit_edit' => 'Uložit podcast', - ], - 'category_options' => [ - 'uncategorized' => 'bez kategorie', - 'arts' => 'Umění', - 'business' => 'Byznys', - 'comedy' => 'Komedie', - 'education' => 'Vzdělání', - 'fiction' => 'Fikce', - 'government' => 'Vláda', - 'health_and_fitness' => 'Zdraví a fitness', - 'history' => 'Historie', - 'kids_and_family' => 'Děti a rodina', - 'leisure' => 'Volný čas', - 'music' => 'Hudba', - 'news' => 'Novinky', - 'religion_and_spirituality' => 'Náboženství a spiritualita', - 'science' => 'Věda', - 'society_and_culture' => 'Společnost a kultura', - 'sports' => 'Sport', - 'technology' => 'Technologie', - 'true_crime' => 'Skutečný zločin', - 'tv_and_film' => 'TV a film', - 'books' => 'Knihy', - 'design' => 'Design', - 'fashion_and_beauty' => 'Móda a Krása', - 'food' => 'Jídlo', - 'performing_arts' => 'Umělecké vystoupení', - 'visual_arts' => 'Vizuální umění', - 'careers' => 'Kariéra', - 'entrepreneurship' => 'Podnikání', - 'investing' => 'Investice', - 'management' => 'Management', - 'marketing' => 'Marketing', - 'non_profit' => 'Neziskovky', - 'comedy_interviews' => 'Komední rozhovory', - 'improv' => 'Improvizace', - 'stand_up' => 'Standup', - 'courses' => 'Learn-paths (spůsob učení)', - 'how_to' => 'Návody', - 'language_learning' => 'Studium jazyků', - 'self_improvement' => 'Sebezdokonalování', - 'comedy_fiction' => 'Komediální fikce', - 'drama' => 'Drama', - 'science_fiction' => 'Sci-Fi', - 'alternative_health' => 'Alternativní medicína', - 'fitness' => 'Zdraví', - 'medicine' => 'Medicína', - 'mental_health' => 'Duševní zdraví', - 'nutrition' => 'Výživa', - 'sexuality' => 'Sexualita', - 'education_for_kids' => 'Vzdělávání pro děti', - 'parenting' => 'Rodičovství', - 'pets_and_animals' => 'Zvířata a mazlíčci', - 'stories_for_kids' => 'Příběhy pro děti', - 'animation_and_manga' => 'Anime a manga', - 'automotive' => 'Automobily', - 'aviation' => 'Letectví', - 'crafts' => 'Výroba', - 'games' => 'Hry', - 'hobbies' => 'Koníčky', - 'home_and_garden' => 'Dům a zahrada', - 'video_games' => 'Videohry', - 'music_commentary' => 'Hudební komentář', - 'music_history' => 'Historie hudby', - 'music_interviews' => 'Hudební rozhovory', - 'business_news' => 'Obchodní novinky', - 'daily_news' => 'Denní zprávy', - 'entertainment_news' => 'Zábavné novinky', - 'news_commentary' => 'Komentář novinek', - 'politics' => 'Politika', - 'sports_news' => 'Sportovní zprávy', - 'tech_news' => 'Technické novinky', - 'buddhism' => 'Budhismus', - 'christianity' => 'Křesťanství', - 'hinduism' => 'Hinduismus', - 'islam' => 'Islám', - 'judaism' => 'Židovství', - 'religion' => 'Náboženství', - 'spirituality' => 'Duchovnost', - 'astronomy' => 'Astronomie', - 'chemistry' => 'Chemie', - 'earth_sciences' => 'Vědy o Zemi', - 'life_sciences' => 'Vědy o životě', - 'mathematics' => 'Matematika', - 'natural_sciences' => 'Přírodní vědy', - 'nature' => 'Příroda', - 'physics' => 'Fyzika', - 'social_sciences' => 'Společenské vědy', - 'documentary' => 'Dokumenty', - 'personal_journals' => 'Osobní deníky', - 'philosophy' => 'Filosofie', - 'places_and_travel' => 'Místa a cestování', - 'relationships' => 'Vztahy', - 'baseball' => 'Baseball', - 'basketball' => 'Basketbal', - 'cricket' => 'Kriket', - 'fantasy_sports' => 'Fantasy sporty', - 'football' => 'Fotbal', - 'golf' => 'Golf', - 'hockey' => 'Hokej', - 'rugby' => 'Rugby', - 'running' => 'Běh', - 'soccer' => 'Fotbal', - 'swimming' => 'Plavání', - 'tennis' => 'Tenis', - 'volleyball' => 'Volejbal', - 'wilderness' => 'Divočina', - 'wrestling' => 'Wrestling', - 'after_shows' => 'Po pořadu', - 'film_history' => 'Filmová historie', - 'film_interviews' => 'Filmové rozhovory', - 'film_reviews' => 'Filmové recenze', - 'tv_reviews' => 'Televizní recenze', - ], - 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Zpět na nástěnku podcastu', - 'post' => 'Váš oznamovací příspěvek', - 'post_hint' => - "Napište zprávu pro oznámení zveřejnění vašeho podcastu. Zpráva bude zobrazena na domovské stránce vašeho podcastu.", - 'message_placeholder' => 'Napište zprávu…', - 'submit' => 'Publikovat', - 'publication_date' => 'Datum publikování', - 'publication_method' => [ - 'now' => 'Nyní', - 'schedule' => 'Naplánovat', - ], - 'scheduled_publication_date' => 'Plánované datum publikování', - 'scheduled_publication_date_hint' => - 'Vydání podcast můžete naplánovat nastavením data budoucího zveřejnění. Toto pole musí být formátováno jako YY-MM-DD HH:mm', - 'submit_edit' => 'Upravit publikování', - 'cancel_publication' => 'Zrušit publikování', - 'message_warning' => 'Nepsali jste zprávu pro váš příspěvek s oznámením!', - 'message_warning_hint' => 'Zpráva zvyšuje viditelnost na sociálních sítích, což má za následek lepší popularitu pro vaše podcasty.', - 'message_warning_submit' => 'Přesto publikovat', - ], - 'publication_status_banner' => [ - 'draft_mode' => 'režim konceptu', - 'not_published' => 'Tento podcast ještě není publikován.', - 'scheduled' => 'Tento podcast je naplánován na publikování {publication_date}.', - ], - 'delete_form' => [ - 'disclaimer' => - "Smazání podcastu smaže všechny epizody, mediální soubory, příspěvky a analytiky spojené s ními. Tato akce je nevratná, poté je nebudete moci získat zpět.", - 'understand' => 'Chápu, chci, aby byl podcast trvale odstraněn', - 'submit' => 'Smazat', - ], - 'by' => 'Od {publisher}', - 'season' => 'Série {seasonNumber}', - 'list_of_episodes_year' => 'Epizody v {year} ({episodeCount})', - 'list_of_episodes_season' => - 'Epizody série {seasonNumber} ({episodeCount})', - 'no_episode' => 'Nebyla nalezena žádná epizoda', - 'follow' => 'Sledovat', - 'followers' => '{numberOfFollowers, plural, - one {# sledující} - other {# sledující} - }', - 'posts' => '{numberOfPosts, plural, - one {# příspěvek} - other {# příspěvků} - }', - 'activity' => 'Aktivita', - 'episodes' => 'Epizody', - 'sponsor' => 'Sponzor', - 'funding_links' => 'Odkazy na financování {podcastTitle}', - 'find_on' => 'Najít {podcastTitle} na', - 'listen_on' => 'Poslouchat na', -]; diff --git a/modules/Admin/Language/cs/PodcastImport.php b/modules/Admin/Language/cs/PodcastImport.php deleted file mode 100644 index a37d7724..00000000 --- a/modules/Admin/Language/cs/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'Tento postup může trvat dlouho. Vzhledem k tomu, že aktuální verze nezobrazuje žádný pokrok při spuštění, neuvidíte nic aktualizovaného, dokud nebude hotovo. V případě chyby timeoutu, zvýšte hodnotu `max_execution_time`.', - 'old_podcast_section_title' => 'Podcast k importu', - 'old_podcast_section_subtitle' => - 'Ujistěte se, že vlastníte práva pro tento podcast před jeho importem. Kopírování a vysílání bez řádných práv je pirátství a podléhá stíhání.', - 'imported_feed_url' => 'URL kanálu', - 'imported_feed_url_hint' => 'Zdroj musí být ve formátu XML nebo RSS.', - 'new_podcast_section_title' => 'Nový podcast', - 'advanced_params_section_title' => 'Pokročilá nastavení', - 'advanced_params_section_subtitle' => - 'Ponechte výchozí hodnoty, pokud nemáte žádnou představu o tom, k čemu jsou tato pole určena.', - 'slug_field' => 'Pole pro výpočet URL adresy epizody', - 'description_field' => - 'Zdrojové pole použité pro popis epizody / zobrazení poznámek', - 'force_renumber' => 'Vynutit přečíslování epizod', - 'force_renumber_hint' => - 'Toto použijte, pokud váš podcast nemá čísla epizody, ale přeje si je nastavit během importu.', - 'season_number' => 'Číslo série', - 'season_number_hint' => - 'Toto použijte, pokud váš podcast nemá číslo série, ale chce jej nastavit během importu. V opačném případě ponechte prázdné.', - 'max_episodes' => 'Maximální počet epizod k importu', - 'max_episodes_hint' => 'Nechte prázdné pro import všech epizod', - 'lock_import' => - 'Tento kanál je chráněn. Nemůžete jej importovat. Pokud jste vlastník, zrušte ochranu na zdrojové platformě.', - 'submit' => 'Importovat podcast', -]; diff --git a/modules/Admin/Language/cs/PodcastNavigation.php b/modules/Admin/Language/cs/PodcastNavigation.php deleted file mode 100644 index 1e8cbc66..00000000 --- a/modules/Admin/Language/cs/PodcastNavigation.php +++ /dev/null @@ -1,42 +0,0 @@ - 'Přejít na stránku podcastu', - 'rss_feed' => 'Kanál RSS', - 'dashboard' => 'Nástěnka podcastu', - 'podcast-view' => 'Domovská stránka', - 'podcast-edit' => 'Upravit podcast', - 'podcast-persons-manage' => 'Spravovat osoby', - 'podcast-imports' => 'Importy podcastu', - 'podcast-imports-sync' => 'Synchronizovat kanály', - 'episodes' => 'Epizody', - 'episode-list' => 'Všechny epizody', - 'episode-create' => 'Nová epizoda', - 'analytics' => 'Analytiky', - 'podcast-analytics' => 'Přehled diváků', - 'podcast-analytics-webpages' => 'Návštěvy webových stránek', - 'podcast-analytics-locations' => 'Místa', - 'podcast-analytics-unique-listeners' => 'Unikátní posluchači', - 'podcast-analytics-players' => 'Přehrávače', - 'podcast-analytics-listening-time' => 'Doba poslechu', - 'podcast-analytics-time-periods' => 'Časové období', - 'monetization' => 'Monetizace', - 'subscription-list' => 'Všechny odběry', - 'subscription-create' => 'Přidat odběr', - 'contributors' => 'Přispěvatelé', - 'contributor-list' => 'Všichni přispěvatelé', - 'contributor-add' => 'Přidat přispěvatele', - 'broadcast' => 'Vysílání', - 'platforms-podcasting' => 'Podcastovací aplikace', - 'platforms-social' => 'Sociální sítě', - 'platforms-funding' => 'Odkazy na financování', - 'podcast-monetization-other' => 'Ostatní', -]; diff --git a/modules/Admin/Language/cs/Settings.php b/modules/Admin/Language/cs/Settings.php deleted file mode 100644 index 080d7797..00000000 --- a/modules/Admin/Language/cs/Settings.php +++ /dev/null @@ -1,58 +0,0 @@ - 'Obecné nastavení', - 'instance' => [ - 'title' => 'Instalační soubor', - 'site_icon' => 'Ikona stránky', - 'site_icon_delete' => 'Odstranit ikonu stránky', - 'site_icon_hint' => 'Ikony stránky jsou to, co vidíte na kartách prohlížeče, v panelu záložek, a když přidáte webové stránky jako zkratku na mobilních zařízeních.', - 'site_icon_helper' => 'Ikona musí být čtvercová a alespoň 512px široká a vysoká.', - 'site_name' => 'Název stránky', - 'site_description' => 'Popis stránky', - 'submit' => 'Uložit', - 'editSuccess' => 'Instance byla úspěšně aktualizována!', - 'deleteIconSuccess' => 'Ikona stránek byla úspěšně odstraněna!', - ], - 'images' => [ - 'title' => 'Obrázky', - 'subtitle' => 'Zde můžete obnovit všechny obrázky na základě originálů, které byly nahrány. Použije se, pokud zjistíte, že některé obrázky chybí. Tato úloha může chvíli trvat.', - 'regenerate' => 'Obnovit obrázky', - 'regenerationSuccess' => 'Všechny obrázky byly úspěšně obnoveny!', - ], - 'housekeeping' => [ - 'title' => 'Úklid', - 'subtitle' => 'Spustí různé úklidové úkoly. Použijte tuto funkci, pokud někdy narazíte na problémy s mediálními soubory nebo integritou dat. Tyto úkoly mohou chvíli trvat.', - 'reset_counts' => 'Vynulovat počítadla', - 'reset_counts_helper' => 'Tato možnost přepočítá a resetuje všechny počty dat (počet sledovatelů, příspěvků, komentářů, …).', - 'rewrite_media' => 'Přepsat metadata médií', - 'rewrite_media_helper' => 'Tato možnost odstraní všechny nadbytečné mediální soubory a znovu je obnoví (obrázky, zvukové soubory, přepisy, kapitoly, …)', - 'rename_episodes_files' => 'Přejmenovat zvukové soubory epizody', - 'rename_episodes_files_hint' => 'Tato možnost přejmenuje všechny epizody zvukových souborů na náhodný řetězec znaků. Toto použijte pro opětovné skrytí, pokud uniklo URL jedné z vašich soukromých epizod.', - 'clear_cache' => 'Vymazat všechny mezipaměti', - 'clear_cache_helper' => 'Tato volba bude vyčistí redis mezipaměť nebo zapisovatelné soubory.', - 'run' => 'Spustit úklid', - 'runSuccess' => 'Úklid byl úspěšný!', - ], - 'theme' => [ - 'title' => 'Motiv', - 'accent_section_title' => 'Barevný tón', - 'accent_section_subtitle' => 'Vyberte barvu pro vzhled a dojem všech veřejných stránek', - 'pine' => 'Borovice', - 'crimson' => 'Purpurový', - 'amber' => 'Jantarový', - 'lake' => 'Jezero', - 'jacaranda' => 'Jacaranda', - 'onyx' => 'Onyx ', - 'submit' => 'Uložit', - 'setInstanceThemeSuccess' => 'Šablona byla úspěšně aktualizována!', - ], -]; diff --git a/modules/Admin/Language/cs/Soundbite.php b/modules/Admin/Language/cs/Soundbite.php deleted file mode 100644 index e08a6e9c..00000000 --- a/modules/Admin/Language/cs/Soundbite.php +++ /dev/null @@ -1,31 +0,0 @@ - [ - 'title' => 'Úryvky', - 'soundbite' => 'Úryvek', - ], - 'messages' => [ - 'createSuccess' => 'Úryvek byl úspěšně vytvořen!', - 'deleteSuccess' => 'Úryvek byl úspěšně odstraněn!', - ], - 'form' => [ - 'title' => 'Nový úryvek', - 'soundbite_title' => 'Název úryvku', - 'start_time' => 'Začátek v', - 'duration' => 'Doba trvání', - 'submit' => 'Vytvořit úryvek', - ], - 'play' => 'Přehrát úryvek', - 'stop' => 'Zastavit úryvek', - 'create' => 'Nový úryvek', - 'delete' => 'Smazat úryvek', -]; diff --git a/modules/Admin/Language/cs/User.php b/modules/Admin/Language/cs/User.php deleted file mode 100644 index fb0b4fa1..00000000 --- a/modules/Admin/Language/cs/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Upravit role {username}", - 'forcePassReset' => 'Vynutit obnovení hesla', - 'ban' => 'Ban', - 'unban' => 'Odbanovat', - 'delete' => 'Smazat', - 'create' => 'Nový uživatel', - 'view' => "Informace o {username}", - 'all_users' => 'Všichni uživatelé', - 'list' => [ - 'user' => 'Uživatel', - 'roles' => 'Role', - 'banned' => 'Zabanován?', - ], - 'form' => [ - 'email' => 'E-mail', - 'username' => 'Uživatelské jméno', - 'password' => 'Heslo', - 'new_password' => 'Nové heslo', - 'roles' => 'Role', - 'permissions' => 'Oprávnění', - 'submit_create' => 'Vytvořit uživatele', - 'submit_edit' => 'Uložit', - 'submit_password_change' => 'Změnit!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'Uživatel byl úspěšně vytvořen! {username} bude požádán o obnovení hesla při prvním ověření.', - 'rolesEditSuccess' => - "Role {username} byly úspěšně aktualizovány.", - 'forcePassResetSuccess' => - '{username} bude požádán o obnovení hesla při příští návštěvě.', - 'banSuccess' => '{username} byl zabanován.', - 'unbanSuccess' => '{username} byl odbanován.', - 'editOwnerError' => - '{username} je vlastníkem instance, nemůžete upravit role.', - 'banSuperAdminError' => - '{username} je superadmin, ban superadmina asi neni to pravé ořechové…', - 'deleteSuperAdminError' => - '{username} je superadmin, ostranit jej neni dobrý nápad…', - 'deleteSuccess' => '{username} byl smazán.', - ], -]; diff --git a/modules/Admin/Language/cs/Validation.php b/modules/Admin/Language/cs/Validation.php deleted file mode 100644 index bd619ec7..00000000 --- a/modules/Admin/Language/cs/Validation.php +++ /dev/null @@ -1,17 +0,0 @@ - - '{field} buď není obrázek, nebo není dostatečně široký nebo vysoký.', - 'is_image_ratio' => - '{field} buď není obrázek, nebo nemá správný poměr.', - 'is_json' => '{field} obsahuje neplatný JSON.', -]; diff --git a/modules/Admin/Language/cs/VideoClip.php b/modules/Admin/Language/cs/VideoClip.php deleted file mode 100644 index 2ac97c40..00000000 --- a/modules/Admin/Language/cs/VideoClip.php +++ /dev/null @@ -1,72 +0,0 @@ - [ - 'title' => 'Videoklipy', - 'status' => [ - 'label' => 'Stav', - 'queued' => 've frontě', - 'queued_hint' => 'Klip čeká na zpracování.', - 'pending' => 'čeká', - 'pending_hint' => 'Klip bude brzy vygenerován.', - 'running' => 'běží', - 'running_hint' => 'Vytváří se klip.', - 'failed' => 'selhalo', - 'failed_hint' => 'Klip nelze vygenerovat: skript selhal.', - 'passed' => 'prošel', - 'passed_hint' => 'Klip byl úspěšně vygenerován!', - ], - 'clip' => 'Klip', - 'duration' => 'Trvání úlohy', - ], - 'title' => 'Videoklip: {videoClipLabel}', - 'download_clip' => 'Stáhnout klip', - 'create' => 'Nový videoklip', - 'go_to_page' => 'Přejít na stránku klipu', - 'retry' => 'Opakovat generování klipu', - 'delete' => 'Odstranit klip', - 'logs' => 'Záznamy úloh', - 'messages' => [ - 'alreadyExistingError' => 'Videoklip, který se pokoušíte vytvořit, již existuje!', - 'addToQueueSuccess' => 'Videoklip byl přidán do fronty, čeká na vytvoření!', - 'deleteSuccess' => 'Videoklip byl úspěšně odstraněn!', - ], - 'format' => [ - 'landscape' => 'Na šířku', - 'portrait' => 'Na výšku', - 'squared' => 'Čtverec', - ], - 'form' => [ - 'title' => 'Nový videoklip', - 'params_section_title' => 'Parametry videoklipu', - 'clip_title' => 'Název klipu', - 'format' => [ - 'label' => 'Vyberte formát', - 'landscape_hint' => 'S poměrem 16:9 jsou videa na šířku skvělá pro PeerTube, YouTube a Vimeo.', - 'portrait_hint' => 'S poměrem 9:16 jsou videa na výšku skvělá pro TikTok, YouTube shorts a Instagram příběhy.', - 'squared_hint' => 'S poměrem 1:1 jsou čtvercová videa skvělá pro Mastodon, Facebook, Twitter a LinkedIn.', - ], - 'theme' => 'Vyberte šablonu', - 'start_time' => 'Začátek v', - 'duration' => 'Doba trvání', - 'trim_start' => 'Oříznout začátek', - 'trim_end' => 'Oříznout konec', - 'submit' => 'Vytvořit videoklip', - ], - 'requirements' => [ - 'title' => 'Chybějící požadavky', - 'missing' => 'Máte chybějící požadavky. Ujistěte se, že přidáte všechny požadované položky, aby bylo možné pro tuto epizodu vytvořit video!', - 'ffmpeg' => 'FFmpeg', - 'gd' => 'Graphics Draw (GD)', - 'freetype' => 'Freetype library pro GD', - 'transcript' => 'Soubor přepisu (.srt)', - ], -]; diff --git a/modules/Admin/Language/en/Breadcrumb.php b/modules/Admin/Language/en/Breadcrumb.php index 408c9f9f..fedd6b19 100644 --- a/modules/Admin/Language/en/Breadcrumb.php +++ b/modules/Admin/Language/en/Breadcrumb.php @@ -23,6 +23,7 @@ return [ 'add' => 'add', 'new' => 'new', 'edit' => 'edit', + 'plugins' => 'plugins', 'persons' => 'persons', 'publish' => 'publish', 'publish-edit' => 'edit publication', diff --git a/modules/Admin/Language/en/Common.php b/modules/Admin/Language/en/Common.php index 74addcf2..4dff9dd4 100644 --- a/modules/Admin/Language/en/Common.php +++ b/modules/Admin/Language/en/Common.php @@ -38,6 +38,10 @@ return [ 'noChoicesText' => 'No choices to choose from', 'maxItemText' => 'Cannot add more items', ], + 'fieldArray' => [ + 'add' => 'Add', + 'remove' => 'Remove', + ], 'upload_file' => 'Upload a file', 'remote_url' => 'Remote URL', 'save' => 'Save', diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php index 4fa846e3..f7eb1290 100644 --- a/modules/Admin/Language/en/Episode.php +++ b/modules/Admin/Language/en/Episode.php @@ -109,11 +109,11 @@ return [ 'type' => [ 'label' => 'Type', 'full' => 'Full', - 'full_hint' => 'Complete content (the episode)', + 'full_description' => 'Complete content (the episode)', 'trailer' => 'Trailer', - 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'trailer_description' => 'Short, promotional piece of content that represents a preview of the current show', 'bonus' => 'Bonus', - 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + 'bonus_description' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', ], 'premium_title' => 'Premium', 'premium' => 'Episode must be accessible to premium subscribers only', diff --git a/modules/Admin/Language/en/EpisodeNavigation.php b/modules/Admin/Language/en/EpisodeNavigation.php index 1406e301..ef3cdec0 100644 --- a/modules/Admin/Language/en/EpisodeNavigation.php +++ b/modules/Admin/Language/en/EpisodeNavigation.php @@ -20,4 +20,5 @@ return [ 'video-clips-create' => 'New video clip', 'soundbites-list' => 'Soundbites', 'soundbites-create' => 'New soundbite', + 'plugins' => 'Plugins', ]; diff --git a/modules/Admin/Language/en/Navigation.php b/modules/Admin/Language/en/Navigation.php index f3ffb129..fc7cfc30 100644 --- a/modules/Admin/Language/en/Navigation.php +++ b/modules/Admin/Language/en/Navigation.php @@ -20,6 +20,8 @@ return [ 'podcast-create' => 'New podcast', 'all-podcast-imports' => 'All Podcast imports', 'podcast-imports-add' => 'Import a podcast', + 'plugins' => 'Plugins', + 'plugins-installed' => 'Installed', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', diff --git a/modules/Admin/Language/en/Podcast.php b/modules/Admin/Language/en/Podcast.php index ac1aae77..89fa9491 100644 --- a/modules/Admin/Language/en/Podcast.php +++ b/modules/Admin/Language/en/Podcast.php @@ -72,19 +72,19 @@ return [ 'type' => [ 'label' => 'Type', 'episodic' => 'Episodic', - 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'episodic_description' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + 'serial_description' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', ], 'medium' => [ 'label' => 'Medium', 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', 'podcast' => 'Podcast', - 'podcast_hint' => 'Describes a feed for a podcast show.', + 'podcast_description' => 'Describes a feed for a podcast show.', 'music' => 'Music', - 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'music_description' => 'A feed of music organized into an "album" with each item a song within the album.', 'audiobook' => 'Audiobook', - 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + 'audiobook_description' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', diff --git a/modules/Admin/Language/en/PodcastNavigation.php b/modules/Admin/Language/en/PodcastNavigation.php index bb777707..a6b73e4a 100644 --- a/modules/Admin/Language/en/PodcastNavigation.php +++ b/modules/Admin/Language/en/PodcastNavigation.php @@ -20,6 +20,7 @@ return [ 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', + 'plugins' => 'Plugins', 'analytics' => 'Analytics', 'podcast-analytics' => 'Audience overview', 'podcast-analytics-webpages' => 'Web pages visits', diff --git a/modules/Admin/Language/en/Validation.php b/modules/Admin/Language/en/Validation.php index f76c3163..4db0fe89 100644 --- a/modules/Admin/Language/en/Validation.php +++ b/modules/Admin/Language/en/Validation.php @@ -14,4 +14,6 @@ return [ 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', 'is_json' => '{field} contains invalid JSON.', + 'is_boolean' => 'The {field} field must be a boolean (true or false).', + 'is_list' => 'The {field} field must be an array.', ]; diff --git a/modules/Admin/Language/lt/AboutCastopod.php b/modules/Admin/Language/lt/AboutCastopod.php deleted file mode 100644 index c0b50fc3..00000000 --- a/modules/Admin/Language/lt/AboutCastopod.php +++ /dev/null @@ -1,22 +0,0 @@ - 'Apie „Castopod“', - 'host_name' => 'Serverio vardas', - 'version' => '„Castopod“ versija', - 'php_version' => 'PHP versija', - 'os' => 'Operacinė sistema', - 'languages' => 'Kalbos', - 'update_database' => 'Atnaujinti duomenų bazę', - 'messages' => [ - 'databaseUpdateSuccess' => 'Duomenų bazė atnaujinta!', - ], -]; diff --git a/modules/Admin/Language/lt/Admin.php b/modules/Admin/Language/lt/Admin.php deleted file mode 100644 index 1b5cf166..00000000 --- a/modules/Admin/Language/lt/Admin.php +++ /dev/null @@ -1,15 +0,0 @@ - 'Administratoriaus skydelis', - 'welcome_message' => 'Sveiki, tai – administratoriaus skydelis!', - 'choose_interact' => 'Pasirinkite, kaip sąveikausite', -]; diff --git a/modules/Admin/Language/lt/Breadcrumb.php b/modules/Admin/Language/lt/Breadcrumb.php deleted file mode 100644 index 0c329961..00000000 --- a/modules/Admin/Language/lt/Breadcrumb.php +++ /dev/null @@ -1,57 +0,0 @@ - 'naršymo kelio elementas', - config('Admin') - ->gateway => 'Pradžia', - 'podcasts' => 'tinklalaidės', - 'episodes' => 'epizodai', - 'subscriptions' => 'prenumeratos', - 'contributors' => 'talkininkai', - 'pages' => 'puslapiai', - 'settings' => 'nuostatos', - 'theme' => 'apipavidalinimas', - 'about' => 'apie', - 'add' => 'pridėti', - 'new' => 'naujas', - 'edit' => 'taisyti', - 'persons' => 'asmenys', - 'publish' => 'skelbti', - 'publish-edit' => 'taisyti skelbimą', - 'publish-date-edit' => 'taisyti skelbimo datą', - 'unpublish' => 'nebeskelbti', - 'delete' => 'šalinti', - 'remove' => 'šalinti', - 'fediverse' => 'Fedivisata', - 'blocked-actors' => 'blokuojami naudotojai', - 'blocked-domains' => 'blokuojami domenai', - 'users' => 'naudotojai', - 'my-account' => 'mano paskyra', - 'change-password' => 'keisti slaptažodį', - 'imports' => 'importas', - 'sync-feeds' => 'sinchronizuoti srautus', - 'platforms' => 'platformos', - 'social' => 'socialiniai tinklai', - 'funding' => 'finansavimas', - 'monetization-other' => 'kiti monetizavimo būdai', - 'analytics' => 'analitika', - 'locations' => 'vietovės', - 'webpages' => 'interneto tinklalapiai', - 'unique-listeners' => 'unikalūs klausytojai', - 'players' => 'grotuvai', - 'listening-time' => 'klausymosi laikas', - 'time-periods' => 'laiko periodai', - 'soundbites' => 'įrašo ištraukos', - 'video-clips' => 'vaizdo klipai', - 'embed' => 'įtaisomasis grotuvas', - 'notifications' => 'pranešimai', - 'suspend' => 'sustabdyti', -]; diff --git a/modules/Admin/Language/lt/Charts.php b/modules/Admin/Language/lt/Charts.php deleted file mode 100644 index 30ae212e..00000000 --- a/modules/Admin/Language/lt/Charts.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Epizodų parsisiuntimai pagal tarnybą (pastarąją savaitę)', - 'by_player_weekly' => 'Epizodų parsisiuntimai pagal grotuvą (pastarąją savaitę)', - 'by_player_yearly' => 'Epizodų parsisiuntimai pagal grotuvą (pastaruosius metus)', - 'by_device_weekly' => 'Epizodų parsisiuntimai pagal įrenginį (pastarąją savaitę)', - 'by_os_weekly' => 'Epizodų parsisiuntimai pagal operacinę sistemą (pastarąją savaitę)', - 'podcast_by_region' => 'Epizodų parsisiuntimai pagal regioną (pastarąją savaitę)', - 'unique_daily_listeners' => 'Unikalūs klausytojai per dieną', - 'unique_monthly_listeners' => 'Unikalūs klausytojai per mėnesį', - 'by_browser' => 'Tinklalapių naudojimas pagal naršyklę (pastarąją savaitę)', - 'podcast_by_day' => 'Epizodų parsisiuntimai per dieną', - 'podcast_by_month' => 'Epizodų parsisiuntimai per mėnesį', - 'episode_by_day' => 'Epizodų parsisiuntimai per dieną (pirmas 60 dienų)', - 'episode_by_month' => 'Epizodų parsisiuntimai per mėnesį', - 'episodes_by_day' => - 'Pastarųjų 5 epizodų parsisiuntimai (pirmas 60 dienų)', - 'by_country_weekly' => 'Epizodų parsisiuntimai pagal šalį (pastarąją savaitę)', - 'by_country_yearly' => 'Epizodų parsisiuntimai pagal grotuvą (pastaruosius metus)', - 'by_domain_weekly' => 'Tinklalapių vizitai pagal šaltinį (pastarąją savaitę)', - 'by_domain_yearly' => 'Tinklalapių vizitai pagal šaltinį (pastaruosius metus)', - 'by_entry_page' => 'Tinklalapių vizitai pagal įėjimo tinklalapį (pastarąją savaitę)', - 'podcast_bots' => 'Robotai (siurbėlės)', - 'daily_listening_time' => 'Sukauptas perklausų laikas per dieną', - 'monthly_listening_time' => 'Sukauptas perklausų laikas per mėnesį', - 'by_weekday' => 'Pagal savaitės dieną (pastarąsias 60 dienų)', - 'by_hour' => 'Pagal paros laiką (pastarąsias 60 dienų)', - 'podcast_by_bandwidth' => 'Srauto naudojimas (MB) per dieną', - 'total_storage_by_month' => 'Saugyklos naudojimas (MB) per mėnesį', - 'total_bandwidth_by_month' => 'Srauto naudojimas (MB) per mėnesį', - 'total_bandwidth_by_month_limit' => 'Ribojama iki {totalBandwidth} per mėnesį', -]; diff --git a/modules/Admin/Language/lt/Common.php b/modules/Admin/Language/lt/Common.php deleted file mode 100644 index bc166f7b..00000000 --- a/modules/Admin/Language/lt/Common.php +++ /dev/null @@ -1,52 +0,0 @@ - 'Taip', - 'no' => 'Ne', - 'cancel' => 'Atsisakyti', - 'optional' => 'Neprivaloma', - 'more' => 'Plačiau', - 'no_data' => 'Duomenų nėra!', - 'close' => 'Užverti', - 'edit' => 'Taisyti', - 'copy' => 'Kopijuoti', - 'copied' => 'Nukopijuota!', - 'home' => 'Pradžia', - 'explicit' => 'Atviras', - 'powered_by' => 'Veikia {castopod} pagrindu', - 'actions' => 'Veiksmai', - 'pageInfo' => '{currentPage} puslapis iš {pageCount}', - 'go_back' => 'Grįžti', - 'forms' => [ - 'editor' => [ - 'write' => 'Rašyti', - 'preview' => 'Peržiūrėti', - 'help' => 'Patobulinta „markdown“', - ], - 'multiSelect' => [ - 'selectText' => 'Spustelėkite pasirinkti', - 'loadingText' => 'Įkeliama…', - 'noResultsText' => 'Rezultatų nerasta', - 'noChoicesText' => 'Galimų pasirinkimų nėra', - 'maxItemText' => 'Daugiau elementų pridėti negalima', - ], - 'upload_file' => 'Įkelti failą', - 'remote_url' => 'Nuotolinis URL', - 'save' => 'Įrašyti', - ], - 'play_episode_button' => [ - 'play' => 'Leisti', - 'playing' => 'Leidžiama', - ], - 'size_limit' => 'Leistinas dydis: iki {0}.', - 'choose_interact' => 'Pasirinkite, kaip sąveikausite', - 'view' => 'Rodymas', -]; diff --git a/modules/Admin/Language/lt/Contributor.php b/modules/Admin/Language/lt/Contributor.php deleted file mode 100644 index a2b02c64..00000000 --- a/modules/Admin/Language/lt/Contributor.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Tinklalaidės talkininkai', - 'view' => "{username} indėlis į „{podcastTitle}“", - 'add' => 'Pridėti talkininką', - 'add_contributor' => 'Pridėti „{0}“ talkininką', - 'edit_role' => 'Atnaujinti {0} rolę', - 'edit' => 'Taisyti', - 'remove' => 'Šalinti', - 'list' => [ - 'username' => 'Naudotojo vardas', - 'role' => 'Rolė', - ], - 'form' => [ - 'user' => 'Naudotojas', - 'user_placeholder' => 'Pasirinkite naudotoją…', - 'role' => 'Rolė', - 'role_placeholder' => 'Pasirinkite rolę…', - 'submit_add' => 'Pridėti talkininką', - 'submit_edit' => 'Atnaujinti rolę', - ], - 'roles' => [ - 'podcast_admin' => 'Tinklalaidės administratorius', - ], - 'messages' => [ - 'removeOwnerError' => "Tinklalaidės savininko pašalinti negalite!", - 'removeSuccess' => - '{username} pašalinta(s) iš „{podcastTitle}“', - 'alreadyAddedError' => - "Bandomas pridėti talkininkas jau ir taip pridėtas!", - ], -]; diff --git a/modules/Admin/Language/lt/Countries.php b/modules/Admin/Language/lt/Countries.php deleted file mode 100644 index e3c49fb4..00000000 --- a/modules/Admin/Language/lt/Countries.php +++ /dev/null @@ -1,264 +0,0 @@ - 'Andora', - 'AE' => 'Jungtiniai Arabų Emyratai', - 'AF' => 'Afganistanas', - 'AG' => 'Antigva ir Barbuda', - 'AI' => 'Angilija', - 'AL' => 'Albanija', - 'AM' => 'Armėnija', - 'AO' => 'Angola', - 'AQ' => 'Antarktida', - 'AR' => 'Argentina', - 'AS' => 'Amerikos Samoa', - 'AT' => 'Austrija', - 'AU' => 'Australija', - 'AW' => 'Aruba', - 'AX' => 'Alandai', - 'AZ' => 'Azerbaidžanas', - 'BA' => 'Bosnija ir Hercegovina', - 'BB' => 'Barbadosas', - 'BD' => 'Bangladešas', - 'BE' => 'Belgija', - 'BF' => 'Burkina Fasas', - 'BG' => 'Bulgarija', - 'BH' => 'Bahreinas', - 'BI' => 'Burundis', - 'BJ' => 'Beninas', - 'BL' => 'San Bartelemis', - 'BM' => 'Bermuda', - 'BN' => 'Brunėjaus Darusalamas', - 'BO' => 'Bolivijos Daugiatautė Valstybė', - 'BQ' => 'Bonairė, Sint Eustatijus ir Saba', - 'BR' => 'Brazilija', - 'BS' => 'Bahamos', - 'BT' => 'Butanas', - 'BV' => 'Buvė sala', - 'BW' => 'Botsvana', - 'BY' => 'Baltarusija', - 'BZ' => 'Belizas', - 'CA' => 'Kanada', - 'CC' => 'Kokosų (Kilingo) salos', - 'CD' => 'Kongo Demokratinė Respublika', - 'CF' => 'Centrinės Afrikos Respublika', - 'CG' => 'Kongas', - 'CH' => 'Šveicarija', - 'CI' => "Dramblio Kaulo Krantas", - 'CK' => 'Kuko salos', - 'CL' => 'Čilė', - 'CM' => 'Kamerūnas', - 'CN' => 'Kinija', - 'CO' => 'Kolumbija', - 'CR' => 'Kosta Rika', - 'CU' => 'Kuba', - 'CV' => 'Žaliasis Kyšulys', - 'CW' => 'Kiurasao', - 'CX' => 'Kalėdų sala', - 'CY' => 'Kipras', - 'CZ' => 'Čekijos Respublika', - 'DE' => 'Vokietija', - 'DJ' => 'Džibutis', - 'DK' => 'Danija', - 'DM' => 'Dominika', - 'DO' => 'Dominikos Respublika', - 'DZ' => 'Alžyras', - 'EC' => 'Ekvadoras', - 'EE' => 'Estija', - 'EG' => 'Egiptas', - 'EH' => 'Vakarų Sachara', - 'ER' => 'Eritrėja', - 'ES' => 'Ispanija', - 'ET' => 'Etiopija', - 'FI' => 'Suomija', - 'FJ' => 'Fidžis', - 'FK' => 'Folklando (Malvinų) salos', - 'FM' => 'Mikronezijos Federacinės Valstijos', - 'FO' => 'Farerų salos', - 'FR' => 'Prancūzija', - 'GA' => 'Gabonas', - 'GB' => 'Jungtinė Karalystė', - 'GD' => 'Grenada', - 'GE' => 'Gruzija', - 'GF' => 'Prancūzijos Gviana', - 'GG' => 'Gernsis', - 'GH' => 'Gana', - 'GI' => 'Gibraltaras', - 'GL' => 'Grenlandija', - 'GM' => 'Gambija', - 'GN' => 'Gvinėja', - 'GP' => 'Gvadelupa', - 'GQ' => 'Pusiaujo Gvinėja', - 'GR' => 'Graikija', - 'GS' => 'Pietų Džordžijos ir Pietų Sandvičo salos', - 'GT' => 'Gvatemala', - 'GU' => 'Guamas', - 'GW' => 'Bisau Gvinėja', - 'GY' => 'Gajana', - 'HK' => 'Honkongas', - 'HM' => 'Herdo ir Makdonaldo salos', - 'HN' => 'Hondūras', - 'HR' => 'Kroatija', - 'HT' => 'Haitis', - 'HU' => 'Vengrija', - 'ID' => 'Indonezija', - 'IE' => 'Airija', - 'IL' => 'Izraelis', - 'IM' => 'Meno sala', - 'IN' => 'Indija', - 'IO' => 'Indijos Vandenyno Britų sritis', - 'IQ' => 'Irakas', - 'IR' => 'Irano Islamo Respublika', - 'IS' => 'Islandija', - 'IT' => 'Italija', - 'JE' => 'Džersis', - 'JM' => 'Jamaika', - 'JO' => 'Jordanija', - 'JP' => 'Japonija', - 'KE' => 'Kenija', - 'KG' => 'Kirgizija', - 'KH' => 'Kambodža', - 'KI' => 'Kiribatis', - 'KM' => 'Komorai', - 'KN' => 'Sent Kitsas ir Nevis', - 'KP' => "Korėjos Liaudies Demokratinė Respublika", - 'KR' => 'Korėjos Respublika', - 'KW' => 'Kuveitas', - 'KY' => 'Kaimanų salos', - 'KZ' => 'Kazachstanas', - 'LA' => "Laoso Liaudies Demokratinė Respublika", - 'LB' => 'Libanas', - 'LC' => 'Sent Lusija', - 'LI' => 'Lichtenšteinas', - 'LK' => 'Šri Lanka', - 'LR' => 'Liberija', - 'LS' => 'Lesotas', - 'LT' => 'Lietuva', - 'LU' => 'Liuksemburgas', - 'LV' => 'Latvija', - 'LY' => 'Libija', - 'MA' => 'Marokas', - 'MC' => 'Monakas', - 'MD' => 'Moldovos Respublika', - 'ME' => 'Juodkalnija', - 'MF' => 'San Martenas (Prancūzijos dalis)', - 'MG' => 'Madagaskaras', - 'MH' => 'Maršalo salos', - 'MK' => 'Šiaurės Makedonija', - 'ML' => 'Malis', - 'MM' => 'Mianmaras', - 'MN' => 'Mongolija', - 'MO' => 'Makao', - 'MP' => 'Marianos šiaurinės salos', - 'MQ' => 'Martinika', - 'MR' => 'Mauritanija', - 'MS' => 'Montseratas', - 'MT' => 'Malta', - 'MU' => 'Mauricijus', - 'MV' => 'Maldyvai', - 'MW' => 'Malavis', - 'MX' => 'Meksika', - 'MY' => 'Malaizija', - 'MZ' => 'Mozambikas', - 'N/A' => 'Netaikoma (vietinis IP…)', - 'NA' => 'Namibija', - 'NC' => 'Naujoji Kaledonija', - 'NE' => 'Nigeris', - 'NF' => 'Norfolko sala', - 'NG' => 'Nigerija', - 'NI' => 'Nikaragva', - 'NL' => 'Nyderlandai', - 'NO' => 'Norvegija', - 'NP' => 'Nepalas', - 'NR' => 'Nauru', - 'NU' => 'Niujė', - 'NZ' => 'Naujoji Zelandija', - 'OM' => 'Omanas', - 'PA' => 'Panama', - 'PE' => 'Peru', - 'PF' => 'Prancūzijos Polinezija', - 'PG' => 'Papua Naujoji Gvinėja', - 'PH' => 'Filipinai', - 'PK' => 'Pakistanas', - 'PL' => 'Lenkija', - 'PM' => 'Sen Pjeras ir Mikelonas', - 'PN' => 'Pitkernas', - 'PR' => 'Puerto Rikas', - 'PS' => 'Palestinos valstybė', - 'PT' => 'Portugalija', - 'PW' => 'Palau', - 'PY' => 'Paragvajus', - 'QA' => 'Kataras', - 'RE' => 'Reunjonas', - 'RO' => 'Rumunija', - 'RS' => 'Serbija', - 'RU' => 'Rusijos Federacija', - 'RW' => 'Ruanda', - 'SA' => 'Saudo Arabija', - 'SB' => 'Saliamono salos', - 'SC' => 'Seišeliai', - 'SD' => 'Sudanas', - 'SE' => 'Švedija', - 'SG' => 'Singapūras', - 'SH' => 'Šv. Elenos, Dangun Žengimo ir Tristano da Kunjos salos', - 'SI' => 'Slovėnija', - 'SJ' => 'Svalbardas ir Jan Majenas', - 'SK' => 'Slovakija', - 'SL' => 'Siera Leonė', - 'SM' => 'San Marinas', - 'SN' => 'Senegalas', - 'SO' => 'Somalis', - 'SR' => 'Surinamas', - 'SS' => 'Pietų Sudanas', - 'ST' => 'San Tomė ir Prinsipė', - 'SV' => 'Salvadoras', - 'SX' => 'Sin Martenas (Nyderalandų dalis)', - 'SY' => 'Sirijos Arabų Respublika', - 'SZ' => 'Svazilendas', - 'TC' => 'Terkso ir Kaikoso salos', - 'TD' => 'Čadas', - 'TF' => 'Prancūzijos Pietų Sritys', - 'TG' => 'Togas', - 'TH' => 'Tailandas', - 'TJ' => 'Tadžikija', - 'TK' => 'Tokelau', - 'TL' => 'Rytų Timoras', - 'TM' => 'Turkmėnija', - 'TN' => 'Tunisas', - 'TO' => 'Tonga', - 'TR' => 'Turkija', - 'TT' => 'Trinidadas ir Tobagas', - 'TV' => 'Tuvalu', - 'TW' => 'Taivanas, Kinijos provincija', - 'TZ' => 'Tanzanijos Jungtinė Respublika', - 'UA' => 'Ukraina', - 'UG' => 'Uganda', - 'UM' => 'Jungtinių Valstijų Mažosios pakraštinės salos', - 'US' => 'Jungtinės Amerikos Valstijos', - 'UY' => 'Urugvajus', - 'UZ' => 'Uzbekija', - 'VA' => 'Šventasis Sostas (Vatikano Miesto Valstybė)', - 'VC' => 'Sent Vinsentas ir Grenadinai', - 'VE' => 'Venesuelos Bolivaro Respublika', - 'VG' => 'Mergelių salos (Britų)', - 'VI' => 'Mergelių salos (JAV)', - 'VN' => 'Vietnamas', - 'VU' => 'Vanuatu', - 'WF' => 'Volisas ir Futūna', - 'WS' => 'Samoa', - 'YE' => 'Jemenas', - 'YT' => 'Majotas', - 'ZA' => 'Pietų Afrika', - 'ZM' => 'Zambija', - 'ZW' => 'Zimbabvė', -]; diff --git a/modules/Admin/Language/lt/Dashboard.php b/modules/Admin/Language/lt/Dashboard.php deleted file mode 100644 index 5e1ddd75..00000000 --- a/modules/Admin/Language/lt/Dashboard.php +++ /dev/null @@ -1,28 +0,0 @@ - 'Administratoriaus skydelis', - 'welcome_message' => 'Sveiki, tai – administratoriaus skydelis!', - 'podcasts' => [ - 'title' => 'Tinklalaidės', - 'not_found' => 'Nėra paskelbtų tinklalaidžių', - 'last_published' => 'Paskiausiai skelbta {lastPublicationDate}', - ], - 'episodes' => [ - 'title' => 'Epizodai', - 'not_found' => 'Nėra paskelbtų epizodų', - 'last_published' => 'Paskiausiai skelbta {lastPublicationDate}', - ], - 'storage' => [ - 'title' => 'Saugykla', - 'subtitle' => '{totalUploaded} iš {totalStorage}', - ], -]; diff --git a/modules/Admin/Language/lt/Episode.php b/modules/Admin/Language/lt/Episode.php deleted file mode 100644 index 60256a1e..00000000 --- a/modules/Admin/Language/lt/Episode.php +++ /dev/null @@ -1,227 +0,0 @@ - '{seasonNumber} sezonas', - 'season_abbr' => 'S{seasonNumber}', - 'number' => '{episodeNumber} epizodas', - 'number_abbr' => 'Ep. {episodeNumber}', - 'season_episode' => '{seasonNumber} sezono {episodeNumber} epizodas', - 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', - 'number_of_comments' => '{numberOfComments, plural, - one {# komentaras} - few {# komentarai} - other {# komentarų} - }', - 'all_podcast_episodes' => 'Visi tinklalaidės epizodai', - 'back_to_podcast' => 'Grįžti į tinklalaidę', - 'edit' => 'Taisyti', - 'preview' => 'Peržiūrėti', - 'publish' => 'Paskelbti', - 'publish_edit' => 'Taisyti paskelbimą', - 'publish_date_edit' => 'Taisyti paskelbimo datą', - 'unpublish' => 'Nebeskelbti', - 'publish_error' => 'Šis epizodas jau paskelbtas.', - 'publish_edit_error' => 'Šis epizodas jau paskelbtas.', - 'publish_cancel_error' => 'Šis epizodas jau paskelbtas.', - 'publish_date_edit_error' => 'Šis epizodas dar nepaskelbtas, jo paskelbimo datos taisyti negalima.', - 'publish_date_edit_future_error' => 'Nurodyta epizodo paskelbimo data gali būti tik praeityje. Jei norite jį paskelbti vėliau, pirma nurodykite jo nebeskelbti.', - 'publish_date_edit_success' => 'Epizodo paskelbimo data sėkmingai pakeista!', - 'unpublish_error' => 'Šis epizodas dar nepaskelbtas.', - 'delete' => 'Šalinti', - 'go_to_page' => 'Eiti į puslapį', - 'create' => 'Pridėti epizodą', - 'publication_status' => [ - 'published' => 'Paskelbtas', - 'with_podcast' => 'Paskelbtas', - 'scheduled' => 'Suplanuotas', - 'not_published' => 'Nepaskelbtas', - ], - 'with_podcast_hint' => 'Bus paskelbtas kartu su tinklalaide', - 'list' => [ - 'search' => [ - 'placeholder' => 'Ieškoti epizodo', - 'clear' => 'Išvalyti paiešką', - 'submit' => 'Ieškoti', - ], - 'number_of_episodes' => '{numberOfEpisodes, plural, - one {# epizodas} - few {# epizodai} - other {# epizodų} - }', - 'episode' => 'Epizodas', - 'visibility' => 'Matomumas', - 'downloads' => 'Parsisiuntimai', - 'comments' => 'Komentarai', - 'actions' => 'Veiksmai', - ], - 'messages' => [ - 'createSuccess' => 'Epizodas sėkmingai sukurtas!', - 'editSuccess' => 'Epizodas sėkmingai atnaujintas!', - 'publishSuccess' => '{publication_status, select, - published {Epizodas sėkmingai paskelbtas!} - scheduled {Epizodo paskelbimo data numatyta sėkmingai.} - with_podcast {Epizodą planuojama paskelbti kartu su tinklalaide.} - other {Šis epizodas nepaskelbtas.} - }', - 'publishCancelSuccess' => 'Epizodo paskelbimas sėkmingai atšauktas!', - 'unpublishBeforeDeleteTip' => 'Prieš šalindami epizodą, turite atšaukti jo paskelbimą.', - 'scheduleDateError' => 'Turite nurodyti paskelbimo datą!', - 'deletePublishedEpisodeError' => 'Prieš pašalindami šį epizodą, atšaukite jo paskelbimą.', - 'deleteSuccess' => 'Epizodas sėkmingai pašalintas!', - 'deleteError' => 'Nepavyko pašalinti epizodo {type, select, - transcript {nuorašo} - chapters {skyrelių} - image {viršelio} - audio {garso įrašo} - other {daugialypės terpės} - }.', - 'deleteFileError' => 'Nepavyko pašalinti {type, select, - transcript {nuorašo} - chapters {skyrelių} - image {viršelio} - audio {garso įrašo} - other {daugialypės terpės} - } failo {file_key}. Jį galite pašalinti iš disko rankiniu būdu.', - 'sameSlugError' => 'Epizodas su tokiu nuorodiniu pavadinimu jau yra.', - ], - 'form' => [ - 'file_size_error' => - 'Jūsų įkeltas failas per didelis! Leistinas dydis yra iki {0}. Jei norite šį failą įkelti, savo PHP konfigūracijoje padidinkite `memory_limit`, `upload_max_filesize` ir `post_max_size` reikšmes, tada perleiskite saityno serverio tarnybą.', - 'audio_file' => 'Garso įrašas', - 'audio_file_hint' => 'Pasirinkite .mp3 arba .m4a garso įrašą.', - 'info_section_title' => 'Epizodo duomenys', - 'cover' => 'Epizodo viršelis', - 'cover_hint' => - 'Jei viršelio nenurodysite, bus naudojamas tinklalaidės viršelis.', - 'cover_size_hint' => 'Viršelis turi būti kvadratinis, bent 1400 taškų aukščio ir pločio.', - 'title' => 'Pavadinimas', - 'title_hint' => - 'Įveskite glaustą ir aiškų epizodo pavadinimą. Čia nerašykite epizodo ar sezono numerio.', - 'permalink' => 'Pastovi nuoroda', - 'season_number' => 'Sezonas', - 'episode_number' => 'Epizodas', - 'type' => [ - 'label' => 'Tipas', - 'full' => 'Visas', - 'full_hint' => 'Visas turinys (epizodas)', - 'trailer' => 'Anonsas', - 'trailer_hint' => 'Trumpas reklaminis įrašas, pristatantis šią laidą', - 'bonus' => 'Papildomas', - 'bonus_hint' => 'Papildomas laidos turinys (pavyzdžiui, įrašas „už kadro“ ar interviu su komanda) arba kitos laidos reklama', - ], - 'premium_title' => 'Premium', - 'premium' => 'Epizodas turi būti pasiekiamas tik premium prenumeratoriams', - 'parental_advisory' => [ - 'label' => 'Pastaba tėvams', - 'hint' => 'Ar epizode yra atviro turinio (necenzūrinės leksikos, nevaikiškų temų ar pan.)?', - 'undefined' => 'neapibrėžta', - 'clean' => 'Saugus', - 'explicit' => 'Atviras', - ], - 'show_notes_section_title' => 'Laidos pastabos', - 'show_notes_section_subtitle' => - 'Iki 4000 ženklų, rašykite aiškiai ir glaustai. Laidos pastabos gali padėti potencialiems klausytojams atrasti šį epizodą.', - 'description' => 'Aprašymas', - 'description_footer' => 'Aprašymo prierašas', - 'description_footer_hint' => - 'Šis tekstas bus pridedamas prie kiekvieno epizodo aprašymo. Tai – nebloga vieta, pavyzdžiui, sudėti nuorodoms į jūsų soc. tinklų profilius.', - 'additional_files_section_title' => 'Papildomi failai', - 'additional_files_section_subtitle' => - 'Šie failai gali būti naudojami kitų platformų geresnei jūsų klausytojų patirčiai suteikti. Išsamiau apie juos – čia: {podcastNamespaceLink}.', - 'location_section_title' => 'Vietovė', - 'location_section_subtitle' => 'Apie kokią vietovę yra šis epizodas?', - 'location_name' => 'Vietovės vardas ar adresas', - 'location_name_hint' => 'Vietovė gali būti tikra ar išgalvota', - 'transcript' => 'Nuorašas (subtitrai)', - 'transcript_hint' => 'Leidžiami tik .srt ir .vtt failai.', - 'transcript_download' => 'Parsisiųsti nuorašą', - 'transcript_file' => 'Nuorašo failas (.srt ar .vtt)', - 'transcript_remote_url' => 'Nuorašo URL adresas', - 'transcript_file_delete' => 'Šalinti nuorašo failą', - 'chapters' => 'Skyreliai', - 'chapters_hint' => 'Failas turi būti „JSON Chapters“ formatu.', - 'chapters_download' => 'Parsisiųsti skyrelius', - 'chapters_file' => 'Skyrelių failas', - 'chapters_remote_url' => 'Skyrelių failo URL adresas', - 'chapters_file_delete' => 'Šalinti skyrelių failą', - 'advanced_section_title' => 'Papildomi parametrai', - 'advanced_section_subtitle' => - 'Jeigu norite naudoti RSS gaires, kurių „Castopod“ neapdoroja, galite jas nustatyti čia.', - 'custom_rss' => 'Papildomos epizodo RSS gairės', - 'custom_rss_hint' => 'Tai bus įterpta į gairę.', - 'block' => 'Epizodas neturėtų būti matomas viešuosiuose kataloguose', - 'block_hint' => - 'Ribotas epizodo matomumas: įjungus šią parinktį, epizodas nebus matomas „Apple Podcasts“, „Google Podcasts“ ir kitose trečiųjų šalių programose, naudojančiose šių tarnybų katalogus (negarantuojama).', - 'submit_create' => 'Sukurti epizodą', - 'submit_edit' => 'Įrašyti epizodą', - ], - 'publish_form' => [ - 'back_to_episode_dashboard' => 'Grįžti į epizodų skydelį', - 'post' => 'Jūsų įrašas-anonsas', - 'post_hint' => - "Parašykite pranešimą, kuriuo anonsuosite šio epizodo išleidimą. Šis pranešimas bus ištransliuotas visiems jūsų sekėjams Fedivisatoje bei matomas jūsų tinklalaidės pradžios tinklalapyje.", - 'message_placeholder' => 'Parašykite savo pranešimą…', - 'publication_date' => 'Paskelbimo data', - 'publication_method' => [ - 'now' => 'Dabar', - 'schedule' => 'Suplanuoti', - 'with_podcast' => 'Paskelbti kartu su tinklalaide', - ], - 'scheduled_publication_date' => 'Suplanuota paskelbimo data', - 'scheduled_publication_date_clear' => 'Pašalinti paskelbimo datą', - 'scheduled_publication_date_hint' => - 'Galite suplanuoti epizodo paskelbimą pagal tvarkaraštį, nurodydami paskelbimo datą ateityje. Reikšmę turite įrašyti tokiu formatu: metai-mėnuo-diena valandos:minutės', - 'submit' => 'Paskelbti', - 'submit_edit' => 'Taisyti paskelbimą', - 'cancel_publication' => 'Atšaukti paskelbimą', - 'message_warning' => 'Neparašėte teksto savo įrašui-anonsui!', - 'message_warning_hint' => 'Parašę trumpą pranešimą, padidinsite epizodo matomumą ir klausytojų įsitraukimą.', - 'message_warning_submit' => 'Vis tiek paskelbti', - ], - 'publish_date_edit_form' => [ - 'new_publication_date' => 'Nauja paskelbimo data', - 'new_publication_date_hint' => 'Data turi būti praeityje.', - 'submit' => 'Taisyti paskelbimo datą', - ], - 'unpublish_form' => [ - 'disclaimer' => - "Nutraukdami epizodo skelbimą, pašalinsite visus su juo susijusius komentarus ir įrašus bei pašalinsite jį iš tinklalaidės RSS sklaidos kanalo.", - 'understand' => 'Suprantu, bet vis tiek noriu nutraukti epizodo skelbimą', - 'submit' => 'Nebeskelbti', - ], - 'delete_form' => [ - 'disclaimer' => - "Pašalinus epizodą, bus pašalinti ir visi su juo susieti daugialypės terpės failai, komentarai, vaizdo įrašai bei įrašo ištraukos.", - 'understand' => 'Suprantu, bet vis tiek noriu pašalinti epizodą', - 'submit' => 'Šalinti', - ], - 'embed' => [ - 'title' => 'Įtaisomasis grotuvas', - 'label' => - 'Pasirinkite akcento slapvą, nukopijuokite įtaisomąjį grotuvą į iškarpinę, tada įdėkite jį į savo svetainę.', - 'clipboard_iframe' => 'Kopijuoti įtaisomąjį grotuvą į iškarpinę', - 'clipboard_url' => 'Kopijuoti adresą į iškarpinę', - 'dark' => 'Tamsus', - 'dark-transparent' => 'Tamsus skaidrus', - 'light' => 'Šviesus', - 'light-transparent' => 'Šviesus skaidrus', - ], - 'publication_status_banner' => [ - 'draft_mode' => 'juodraščio veiksena', - 'text' => '{publication_status, select, - published {Šis epizodas dar nepaskelbtas.} - scheduled {Šį epizodą numatoma paskelbti {publication_date}.} - with_podcast {Šį epizodą numatoma paskelbti kartu su tinklalaide.} - other {Šis epizodas dar nepaskelbtas.} - }', - 'preview' => 'Peržiūra', - ], -]; diff --git a/modules/Admin/Language/lt/EpisodeNavigation.php b/modules/Admin/Language/lt/EpisodeNavigation.php deleted file mode 100644 index 0010724b..00000000 --- a/modules/Admin/Language/lt/EpisodeNavigation.php +++ /dev/null @@ -1,23 +0,0 @@ - 'Parodyti epizodo tinklalapį', - 'dashboard' => 'Epizodų skydelis', - 'episode-view' => 'Pradžia', - 'episode-edit' => 'Taisyti epizodą', - 'episode-persons-manage' => 'Tvarkyti asmenis', - 'embed-add' => 'Įtaisomasis grotuvas', - 'clips' => 'Klipai', - 'video-clips-list' => 'Vaizdo klipai', - 'video-clips-create' => 'Naujas vaizdo klipas', - 'soundbites-list' => 'įrašo ištraukos', - 'soundbites-create' => 'Nauja įrašo ištrauka', -]; diff --git a/modules/Admin/Language/lt/Fediverse.php b/modules/Admin/Language/lt/Fediverse.php deleted file mode 100644 index 5db088ea..00000000 --- a/modules/Admin/Language/lt/Fediverse.php +++ /dev/null @@ -1,32 +0,0 @@ - [ - 'actorNotFound' => 'Paskyra nerasta!', - 'blockActorSuccess' => 'Paskyra {actor} užblokuota!', - 'unblockActorSuccess' => 'Paskyra atblokuota!', - 'blockDomainSuccess' => 'Domenas {domain} užblokuotas!', - 'unblockDomainSuccess' => 'Domenas {domain} atblokuotas!', - ], - 'blocked_actors' => 'Blokuojamos paskyros', - 'blocked_domains' => 'Blokuojami domenai', - 'block_lists_form' => [ - 'handle' => 'Paskyros vardas', - 'handle_hint' => 'Įveskite paskyrą @naudotojas@domenas formatu.', - 'domain' => 'Domeno vardas', - 'submit' => 'Blokuoti!', - ], - 'list' => [ - 'actor' => 'Paskyra', - 'domain' => 'Domeno vardas', - 'unblock' => 'Atblokuoti', - ], -]; diff --git a/modules/Admin/Language/lt/Home.php b/modules/Admin/Language/lt/Home.php deleted file mode 100644 index b6332546..00000000 --- a/modules/Admin/Language/lt/Home.php +++ /dev/null @@ -1,14 +0,0 @@ - 'Visos tinklalaidės', - 'no_podcast' => 'Tinklalaidžių nerasta', -]; diff --git a/modules/Admin/Language/lt/Install.php b/modules/Admin/Language/lt/Install.php deleted file mode 100644 index 710652e2..00000000 --- a/modules/Admin/Language/lt/Install.php +++ /dev/null @@ -1,61 +0,0 @@ - 'Rankinis konfigūravimas', - 'manual_config_subtitle' => - 'Sukurkite failą `.env` su naudotinais nustatymais ir įkelkite šį tinklalapį iš naujo diegimui pratęsti.', - 'form' => [ - 'instance_config' => 'Serverio konfigūracija', - 'hostname' => 'Serverio vardas', - 'media_base_url' => 'Daugialypės terpės failų bazinis URL', - 'media_base_url_hint' => - 'Jei naudojatės CDN ir/ar išorine srauto analizės tarnyba, galite tai nurodyti čia.', - 'admin_gateway' => 'Administratoriaus skydelio adresas', - 'admin_gateway_hint' => - 'Kelias administratoriaus skydeliui pasiekti (pvz., https://example.com/cp-admin). Numatytuoju atveju naudojamas kelias „cp-admin“, tačiau mes rekomenduojame jį pasikeisti saugumo sumetimais.', - 'auth_gateway' => 'Autentifikacijos tinklalapių adresas', - 'auth_gateway_hint' => - 'Kelias autentifikacijos tinklalapiams pasiekti (pvz., https://example.com/cp-auth). Numatytuoju atveju naudojamas kelias „cp-auth“, tačiau mes rekomenduojame jį pasikeisti saugumo sumetimais.', - 'database_config' => 'Duomenų bazės konfigūracija', - 'database_config_hint' => - '„Castopod“ reikia prisijungti prie jūsų „MySQL“ ar „MariaDB“ duomenų bazės. Jei neturite prisijungimo prie duomenų bazės duomenų, kreipkitės į savo serverio administratorių.', - 'db_hostname' => 'DB serveris', - 'db_name' => 'DB pavadinimas', - 'db_username' => 'DB naudotojo vardas', - 'db_password' => 'DB slaptažodis', - 'db_prefix' => 'DB prefiksas', - 'db_prefix_hint' => - "„Castopod“ lentelių pavadinimų prefiksas. Jei nežinote, kas tai – palikite kas įrašyta.", - 'cache_config' => 'Podėlio konfigūracija', - 'cache_config_hint' => - 'Pasirinkite ketinamą naudoti podėlio tipą. Jei nežinote, kas tai – palikite numatytąjį parinktį.', - 'cache_handler' => 'Podėlio tipas', - 'cacheHandlerOptions' => [ - 'file' => 'Failas', - 'redis' => '„Redis“', - 'predis' => '„Predis“', - ], - 'next' => 'Toliau', - 'submit' => 'Užbaigti diegimą', - 'create_superadmin' => 'Susikurkite savo superadministratoriaus paskyrą', - 'email' => 'El. paštas', - 'username' => 'Naudotojo vardas', - 'password' => 'Slaptažodis', - ], - 'messages' => [ - 'createSuperAdminSuccess' => - 'Jūsų superadministratoriaus paskyra sukurta sėkmingai. Prisijunkite ir kurkite savo pirmąją tinklalaidę!', - 'databaseConnectError' => - '„Castopod“ nepavyko prisijungti prie nurodytos duomenų bazės. Pakoreguokite DB konfigūraciją ir bandykite dar kartą.', - 'writeError' => - "Nepavyko sukurti/rašyti į jūsų `.env` failą. Užpildykite jį rankiniu būdu, pasinaudodami šabloniniu `.env.example` failu iš „Castopod“ paketo.", - ], -]; diff --git a/modules/Admin/Language/lt/MyAccount.php b/modules/Admin/Language/lt/MyAccount.php deleted file mode 100644 index fefc5fd6..00000000 --- a/modules/Admin/Language/lt/MyAccount.php +++ /dev/null @@ -1,18 +0,0 @@ - 'Mano paskyros duomenys', - 'changePassword' => 'Keisti mano slaptažodį', - 'messages' => [ - 'wrongPasswordError' => "Įvedėte blogą slaptažodį, pabandykite dar kartą.", - 'passwordChangeSuccess' => 'Slaptažodis sėkmingai pakeistas!', - ], -]; diff --git a/modules/Admin/Language/lt/Navigation.php b/modules/Admin/Language/lt/Navigation.php deleted file mode 100644 index 1253a76b..00000000 --- a/modules/Admin/Language/lt/Navigation.php +++ /dev/null @@ -1,44 +0,0 @@ - 'Įjungti/išjungti šoninę juostą', - 'go_to_website' => 'Eiti į svetainę', - 'go_to_admin' => 'Eiti į administravimą', - 'not-authorized' => 'Trūksta teisių', - 'dashboard' => 'Skydelis', - 'admin' => 'Pradžia', - 'podcasts' => 'Tinklalaidės', - 'podcast-list' => 'Visos tinklalaidės', - 'podcast-create' => 'Nauja tinklalaidė', - 'all-podcast-imports' => 'Visos importuojamos tinklalaidės', - 'podcast-imports-add' => 'Importuoti tinklalaidę', - 'persons' => 'Asmenys', - 'person-list' => 'Visi asmenys', - 'person-create' => 'Naujas asmuo', - 'fediverse' => 'Fedivisata', - 'fediverse-blocked-actors' => 'Blokuojamos paskyros', - 'fediverse-blocked-domains' => 'Blokuojami domenai', - 'users' => 'Naudotojai', - 'user-list' => 'Visi naudotojai', - 'user-create' => 'Naujas naudotojas', - 'pages' => 'Tinklalapiai', - 'page-list' => 'Visi tinklalapiai', - 'page-create' => 'Naujas tinklalapis', - 'settings' => 'Nuostatos', - 'settings-general' => 'Bendrosios', - 'settings-theme' => 'Apipavidalinimas', - 'admin-about' => 'Apie', - 'account' => [ - 'my-account' => 'Mano paskyra', - 'change-password' => 'Keisti slaptažodį', - 'logout' => 'Atsijungti', - ], -]; diff --git a/modules/Admin/Language/lt/Notifications.php b/modules/Admin/Language/lt/Notifications.php deleted file mode 100644 index b79b61cf..00000000 --- a/modules/Admin/Language/lt/Notifications.php +++ /dev/null @@ -1,19 +0,0 @@ - 'Prranešimai', - 'reply' => '{actor_username} atsakė į jūsų įrašą', - 'favourite' => '{actor_username} pamėgo jūsų įrašą', - 'reblog' => '{actor_username} pasidalino jūsų įrašu', - 'follow' => '{actor_username} pradėjo jus sekti', - 'no_notifications' => 'Pranešimų nėra', - 'mark_all_as_read' => 'Žymėti visus kaip skaitytus', -]; diff --git a/modules/Admin/Language/lt/Page.php b/modules/Admin/Language/lt/Page.php deleted file mode 100644 index b66367c1..00000000 --- a/modules/Admin/Language/lt/Page.php +++ /dev/null @@ -1,30 +0,0 @@ - 'Grįžti į pradžią', - 'page' => 'Tinklalapis', - 'all_pages' => 'Visi tinklalapiai', - 'create' => 'Naujas tinklalapis', - 'go_to_page' => 'Eiti į tinklalapį', - 'edit' => 'Taisyti tinklalapį', - 'delete' => 'Šalinti tinklalapį', - 'form' => [ - 'title' => 'Pavadinimas', - 'permalink' => 'Pastovi nuoroda', - 'content' => 'Turinys', - 'submit_create' => 'Kurti tinklalapį', - 'submit_edit' => 'Įrašyti', - ], - 'messages' => [ - 'createSuccess' => 'Tinklalapis „{pageTitle}“ sėkmingai sukurtas!', - 'editSuccess' => 'Tinklalapis sėkmingai atnaujintas!', - ], -]; diff --git a/modules/Admin/Language/lt/Pager.php b/modules/Admin/Language/lt/Pager.php deleted file mode 100644 index 22c8a7ad..00000000 --- a/modules/Admin/Language/lt/Pager.php +++ /dev/null @@ -1,21 +0,0 @@ - 'Tinklalapių naršymas', - 'first' => 'Pirmas', - 'previous' => 'Ankstesnis', - 'next' => 'Kitas', - 'last' => 'Paskutinis', - 'older' => 'Senesnis', - 'newer' => 'Naujesnis', - 'invalidTemplate' => 'Puslapiavimo šablonas „{0}“ negalimas.', - 'invalidPaginationGroup' => 'Puslapiavimo grupė „{0}“ negalima.', -]; diff --git a/modules/Admin/Language/lt/Person.php b/modules/Admin/Language/lt/Person.php deleted file mode 100644 index d8e402e7..00000000 --- a/modules/Admin/Language/lt/Person.php +++ /dev/null @@ -1,65 +0,0 @@ - 'Asmenys', - 'all_persons' => 'Visi asmenys', - 'no_person' => 'Nieko nerasta!', - 'create' => 'Kurti asmenį', - 'view' => 'Rodyti asmenį', - 'edit' => 'Taisyti asmenį', - 'delete' => 'Šalinti asmenį', - 'messages' => [ - 'createSuccess' => 'Asmuo sėkmingai sukurtas!', - 'editSuccess' => 'Asmuo sėkmingai atnaujintas!', - 'deleteSuccess' => 'Asmuo pašalintas!', - ], - 'form' => [ - 'avatar' => 'Avataras', - 'avatar_size_hint' => - 'Avataras turi būti kvadratinis ir bent 400 taškų aukščio ir pločio.', - 'full_name' => 'Visas vardas', - 'full_name_hint' => 'Tai – asmens vardas ir pavardė arba pseudonimas.', - 'unique_name' => 'Unikalus vardas', - 'unique_name_hint' => 'Naudojamas URL adresuose', - 'information_url' => 'Informacinis URL', - 'information_url_hint' => - 'Su asmeniu susijusio ištekliaus, pavyzdžiui, jo svetainės ar profilio socialiniame tinkle, URL adresas.', - 'submit_create' => 'Sukurti asmenį', - 'submit_edit' => 'Įrašyti asmenį', - ], - 'podcast_form' => [ - 'title' => 'Tvarkyti asmenis', - 'add_section_title' => 'Pridėti asmenis prie šios tinklalaidės', - 'add_section_subtitle' => 'Galite pasirinkti keletą asmenų ir jų rolių.', - 'persons' => 'Asmenys', - 'persons_hint' => - 'Tą pačią rolę gali atlikti vienas ar keli asmenys. Asmenys turi būti jau sukurti sistemoje.', - 'roles' => 'Rolės', - 'roles_hint' => - 'Asmeniui galite parinkti vieną ar kelias roles, ar neparinkti jokios rolės.', - 'submit_add' => 'Pridėti asmenį(-is)', - 'remove' => 'Šalinti', - ], - 'episode_form' => [ - 'title' => 'Tvarkyti asmenis', - 'add_section_title' => 'Pridėti asmenis prie šio epizodo', - 'add_section_subtitle' => 'Galite pasirinkti keletą asmenų ir rolių.', - 'persons' => 'Asmenys', - 'persons_hint' => - 'Toje pačioje rolėje gali dalyvauti vienas ar keli asmenys. Asmenys turi būti jau sukurti sistemoje.', - 'roles' => 'Rolės', - 'roles_hint' => - 'Asmeniui galite parinkti vieną ar kelias roles, ar neparinkti jokios rolės.', - 'submit_add' => 'Pridėti asmenį(-is)', - 'remove' => 'Šalinti', - ], - 'credits' => 'Autoriai ir padėkos', -]; diff --git a/modules/Admin/Language/lt/Platforms.php b/modules/Admin/Language/lt/Platforms.php deleted file mode 100644 index 24046658..00000000 --- a/modules/Admin/Language/lt/Platforms.php +++ /dev/null @@ -1,43 +0,0 @@ - [ - 'podcasting' => 'Tinklalaidžių skelbimo platformos', - 'social' => 'Socialiniai tinklai', - 'funding' => 'Rėmimo nuorodos', - ], - 'website' => 'Svetainė', - 'home_url' => 'Eiti į „{platformName}“ svetainę', - 'register' => 'Registruoti', - 'submit_url' => 'Registruoti jūsų tinklalaidę į „platformName}“', - 'your_link' => 'Jūsų nuoroda', - 'your_id' => [ - 'podcasting' => 'Jūsų ID', - 'social' => 'Jūsų ID', - 'funding' => 'Jūsų raginimas veikti', - ], - 'your_cta' => 'Jūsų raginimas veikti', - 'visible' => 'Rodyti tinklalaidės pradžios tinklalapyje?', - 'on_embed' => 'Rodyti įtaisomajame grotuve?', - 'remove' => 'Pašalinti „{platformName}“', - 'submit' => 'Įrašyti', - 'messages' => [ - 'updateSuccess' => 'Platformų nuorodos sėkmingai atnaujintos!', - 'removeLinkSuccess' => 'Platformos nuoroda pašalinta.', - 'removeLinkError' => - 'Platformos nuorodos pašalinti nepavyko. Pabandykite dar kartą.', - ], - 'description' => [ - 'podcasting' => 'Tinklalaidės ID šioje platformoje', - 'social' => 'Tinklalaidės paskyros ID šioje platformoje', - 'funding' => 'Raginimo veikti žinutė', - ], -]; diff --git a/modules/Admin/Language/lt/Podcast.php b/modules/Admin/Language/lt/Podcast.php deleted file mode 100644 index 17fbb30a..00000000 --- a/modules/Admin/Language/lt/Podcast.php +++ /dev/null @@ -1,333 +0,0 @@ - 'Visos tinklalaidės', - 'no_podcast' => 'Jokių tinklalaidžių nerasta!', - 'create' => 'Kurti tinklalaidę', - 'import' => 'Importuoti tinklalaidę', - 'all_imports' => 'Importuojamos tinklalaidės', - 'new_episode' => 'Naujas epizodas', - 'view' => 'Rodyti tinklalaidę', - 'edit' => 'Taisyti tinklalaidę', - 'publish' => 'Paskelbti tinklalaidę', - 'publish_edit' => 'Taisyti paskelbimą', - 'delete' => 'Šalinti tinklalaidę', - 'see_episodes' => 'Rodyti epizodus', - 'see_contributors' => 'Rodyti talkininkus', - 'monetization_other' => 'Kiti monetizavimo būdai', - 'go_to_page' => 'Eiti į tinklalapį', - 'latest_episodes' => 'Paskiausi epizodai', - 'see_all_episodes' => 'Rodyti visus epizodus', - 'draft' => 'Juodraštis', - 'messages' => [ - 'createSuccess' => 'Tinklalaidė sėkmingai sukurta!', - 'editSuccess' => 'Tinklalaidė sėkmingai atnaujinta!', - 'importSuccess' => 'Tinklalaidė sėkmingai importuota!', - 'deleteSuccess' => 'Tinklalaidė @{podcast_handle} sėkmingai pašalinta!', - 'deletePodcastMediaError' => 'Nepavyko pašalinti tinklalaidės {type, select, - cover {viršelio} - banner {reklamjuostės} - other {medijos failo} - }.', - 'deleteEpisodeMediaError' => 'Nepavyko pašalinti tinklalaidės epizodo {episode_slug} {type, select, - transcript {nuorašo} - chapters {skyrelių failo} - image {viršelio} - audio {garso įrašo} - other {medijos failo} - }.', - 'deletePodcastMediaFolderError' => 'Nepavyko pašalinti tinklalaidės medijos aplanko {folder_path}. Jį iš disko galite pašalinti rankiniu būdu.', - 'podcastFeedUpdateSuccess' => 'Sėkmingai atnaujinta: {number_of_new_episodes, plural, - one {# epizodas pridėtas} - other {# epizodai pridėti} - other {# epizodų pridėta} - } prie tinklalaidės!', - 'podcastFeedUpToDate' => 'Tinklalaidė jau yra atnaujinta.', - 'publishError' => 'Ši tinklalaidė arba jau paskelbta, arba jos paskelbimas suplanuotas.', - 'publishEditError' => 'Ši tinklalaidė nesuplanuota paskelbimui.', - 'publishCancelSuccess' => 'Tinklalaidės paskelbimas sėkmingai atšauktas!', - 'scheduleDateError' => 'Turite nurodyti paskelbimo datą!', - ], - 'form' => [ - 'identity_section_title' => 'Tinklalaidės identitetas', - 'identity_section_subtitle' => 'Šie laukai jums padės išsiskirti.', - 'fediverse_section_title' => 'Identitetas fedivisatoje', - - 'cover' => 'Tinklalaidės viršelis', - 'cover_size_hint' => 'Viršelis turi būti kvadratinis, bent 1400 taškų aukščio ir pločio.', - 'banner' => 'Tinklalaidės reklamjuostė', - 'banner_size_hint' => 'Reklamjuostės kraštinių santykis turi būti 3∶1, o plotis – bent 1500 taškų.', - 'banner_delete' => 'Šalinti tinklalaidės reklamjuostę', - 'title' => 'Pavadinimas', - 'handle' => 'Paskyros vardas', - 'handle_hint' => - 'Naudojamas tinklalaidei identifikuoti. Galima naudoti didžiąsias ir mažąsias raides, skaitmenis ir apatinio brūkšnio ženklą.', - 'type' => [ - 'label' => 'Tipas', - 'episodic' => 'Epizodinė', - 'episodic_hint' => 'Jei epizodus galima klausyti nepaisant konkrečios tvarkos. Naujausi epizodai bus pateikiami viršuje.', - 'serial' => 'Nuosekli', - 'serial_hint' => 'Jei epizodai turėtų būti klausomi iš eilės. Epizodai bus rikiuojami didėjančia tvarka pagal eilės numerį.', - ], - 'medium' => [ - 'label' => 'Turinio tipas', - 'hint' => 'Turinio tipas nurodomas `podcast:medium` RSS gairėje. Keičiant šią reikšmę, gali keistis sklaidos kanalo turinys pateikimas grotuvuose.', - 'podcast' => 'Tinklalaidė', - 'podcast_hint' => 'Nurodo, jog sklaidos kanalo turinys yra tinklalaidė.', - 'music' => 'Muzika', - 'music_hint' => 'Sklaidos kanalą sudaro muzika, sugrupuota albumais, kur kiekvienas elementas – tai kūrinys albume.', - 'audiobook' => 'Audioknyga', - 'audiobook_hint' => 'Specifinio tipo garso įrašai, pateikiami su vienu įrašu kanale, arba kai kiekvienas įrašas yra atskiras knygos skyrius.', - ], - 'description' => 'Aprašymas', - 'classification_section_title' => 'Klasifikacija', - 'classification_section_subtitle' => - 'Šių laukų nurodytos reikšmės turės įtakos jūsų galimai auditorijai ir konkurencijai.', - 'language' => 'Kalba', - 'category' => 'Kategorija', - 'category_placeholder' => 'Pasirinkite kategoriją…', - 'other_categories' => 'Kitos kategorijos', - 'parental_advisory' => [ - 'label' => 'Pastaba tėvams', - 'hint' => 'Ar yra atviro turinio?', - 'undefined' => 'neapibrėžta', - 'clean' => 'Saugi', - 'explicit' => 'Atvira', - ], - 'author_section_title' => 'Autorius', - 'author_section_subtitle' => 'Kas valdo šią tinklalaidę?', - 'owner_name' => 'Savininkas', - 'owner_name_hint' => - 'Tik administracinėms reikmėms. Matoma viešame RSS sklaidos kanale.', - 'owner_email' => 'Savininko el. pašto adresas', - 'owner_email_hint' => - 'Bus naudojama daugumoje platformų tinklalaidės nuosavybės patvirtinimui. Matoma viešame RSS sklaidos kanale.', - 'is_owner_email_removed_from_feed' => 'Pašalinti savininko el. pašto adresą iš viešo RSS sklaidos kanalo', - 'is_owner_email_removed_from_feed_hint' => 'Gali būti, kad laikinai turėsite padaryti el. paštą matomą, kad katalogas galėtų patikrinti, jog tinklalaidė priklauso jums.', - 'publisher' => 'Leidėjas', - 'publisher_hint' => - 'Grupė, atsakinga už laidos kūrimą. Dažnai čia nurodoma tinklalaidės motininė įmonė ar tinklas. Kartais šis laukas pristatomas kaip autoriaus laukas.', - 'copyright' => 'Autorių teisės', - 'location_section_title' => 'Vietovė', - 'location_section_subtitle' => 'Apie kokią vietą kalbama šioje tinklalaidėje?', - 'location_name' => 'Vietovės vardas ar adresas', - 'location_name_hint' => 'Vietovė gali būti tikra ar išgalvota', - 'monetization_section_title' => 'Monetizavimas', - 'monetization_section_subtitle' => - 'Uždirbkite pinigų iš savo auditorijos.', - 'premium' => 'Premium', - 'premium_by_default' => 'Epizodai numatytai turi būti laikomi premium', - 'premium_by_default_hint' => 'Tinklalaidės epizodai bus numatytai žymimi kaip premium. Norimus epizodus, anonsus ir/ar papildomus failus galėsite palikti viešus.', - 'op3' => 'Open Podcast Prefix Project (OP3)', - 'op3_link' => 'Apsilankyti jūsų OP3 skydelyje (išorinis saitas)', - 'op3_hint' => 'OP3 – tai atviro kodo patikima trečiosios šalies analitikos tarnyba. Ja naudodamiesi, galite dalintis, tikrinti ir lyginti savo analitikos duomenis su kitomis atvirosiomis tinklalaidėmis.', - 'op3_enable' => 'Įgalinti OP3 analitikos tarnybą', - 'op3_enable_hint' => 'Saugumo sumetimais premium epizodų analitikos duomenimis nebus dalinamasi su OP3.', - 'payment_pointer' => '„Web Monetization“ Mokėjimų rodyklė („Payment Pointer“)', - 'payment_pointer_hint' => - 'Čia gausite pinigus „Web Monetization“ dėka', - 'advanced_section_title' => 'Papildomi parametrai', - 'advanced_section_subtitle' => - 'Jeigu norite naudoti RSS gaires, kurių „Castopod“ neapdoroja, galite jas nustatyti čia.', - 'custom_rss' => 'Tinklalaidės papildomos RSS gairės', - 'custom_rss_hint' => 'Tai bus įterpta į gairę.', - 'verify_txt' => 'Nuosavybės patvirtinimo TXT', - 'verify_txt_hint' => 'Užuot pasikliovusios el. pašto adresu, kai kurios trečiųjų šalių tarnybos gali reikalauti patvirtinti jūsų tinklalaidės nuosavybę, patvirtinimo kodą įterpiant į jūsų sklaidos kanalą.', - 'verify_txt_helper' => 'Ši reikšmė bus pateikta gairėje.', - 'new_feed_url' => 'Naujo sklaidos kanalo URL', - 'new_feed_url_hint' => 'Pasinaudokite šiuo lauku, jei norėsite perkelti savo tinklalaidę į kitą domeną ar tinklalaidžių platformą. Numatytuoju atveju, čia bus dabartinio RSS sklaidos kanalo URL, jei ši tinklalaidė importuota.', - 'old_feed_url' => 'Seno sklaidos kanalo URL', - 'partnership' => 'Bendradarbiavimas', - 'partner_id' => 'ID', - 'partner_link_url' => 'Nuorodos URL', - 'partner_image_url' => 'Atvaizdo URL', - 'partner_id_hint' => 'Jūsų nuosavas partnerio ID', - 'partner_link_url_hint' => 'Bendrinės nuorodos partneriams adresas', - 'partner_image_url_hint' => 'Bendrinio paveikslėlio partneriams adresas', - 'block' => 'Tinklaraidė neturėtų būti matoma viešuosiuose kataloguose', - 'block_hint' => - 'Ribotas tinklalaidės matomumas: įjungus šią parinktį, tinklalaidė nebus matoma „Apple Podcasts“, „Google Podcasts“ ir kitose trečiųjų šalių programose, naudojančiose šių tarnybų katalogus (negarantuojama).', - 'complete' => 'Tinklalaidėje nebebus naujų epizodų', - 'lock' => 'Neleisti tinklalaidės kopijuoti', - 'lock_hint' => - 'Šio lauko paskirtis – informuoti kitas platformas, ar joms leidžiama importuoti šį sklaidos kanalą. Pažymėjus „Taip“, bet kokie mėginimai importuoti šį sklaidos kanalą į kitas platformas turėtų būti atmetami.', - 'submit_create' => 'Sukurti tinklalaidę', - 'submit_edit' => 'Įrašyti tinklalaidę', - ], - 'category_options' => [ - 'uncategorized' => 'be kategorijos', - 'arts' => 'Menas', - 'business' => 'Verslas', - 'comedy' => 'Komedija', - 'education' => 'Švietimas', - 'fiction' => 'Fantastika', - 'government' => 'Vyriausybė', - 'health_and_fitness' => 'Sveikata ir kūno rengyba', - 'history' => 'Istorija', - 'kids_and_family' => 'Vaikai ir šeima', - 'leisure' => 'Laisvalaikis', - 'music' => 'Muzika', - 'news' => 'Naujienos', - 'religion_and_spirituality' => 'Religija ir dvasingumas', - 'science' => 'Mokslas', - 'society_and_culture' => 'Visuomenė ir kultūra', - 'sports' => 'Sportas', - 'technology' => 'Technologijos', - 'true_crime' => 'Nusikaltimai', - 'tv_and_film' => 'Televizija ir filmai', - 'books' => 'Knygos', - 'design' => 'Dizainas', - 'fashion_and_beauty' => 'Mada ir grožis', - 'food' => 'Maistas', - 'performing_arts' => 'Atliekamieji menai', - 'visual_arts' => 'Vaizduojamieji menai', - 'careers' => 'Karjera', - 'entrepreneurship' => 'Entreprenerystė', - 'investing' => 'Investavimas', - 'management' => 'Vadyba', - 'marketing' => 'Rinkodara', - 'non_profit' => 'Visuomeninė veikla', - 'comedy_interviews' => 'Komediniai interviu', - 'improv' => 'Improvizacijos', - 'stand_up' => '„Stand-up“ pasirodymai', - 'courses' => 'Kursai', - 'how_to' => 'Instrukcijos', - 'language_learning' => 'Kalbų mokymasis', - 'self_improvement' => 'Savęs tobulinimas', - 'comedy_fiction' => 'Komedinė fantastika', - 'drama' => 'Drama', - 'science_fiction' => 'Mokslinė fantastika', - 'alternative_health' => 'Alternatyvioji sveikata', - 'fitness' => 'Kūno rengyba', - 'medicine' => 'Medicina', - 'mental_health' => 'Psichinė sveikata', - 'nutrition' => 'Mityba', - 'sexuality' => 'Seksualumas', - 'education_for_kids' => 'Vaikų švietimas', - 'parenting' => 'Tėvystė', - 'pets_and_animals' => 'Gyvūnai', - 'stories_for_kids' => 'Pasakos vaikams', - 'animation_and_manga' => 'Animacija ir manga', - 'automotive' => 'Automobiliai', - 'aviation' => 'Aviacija', - 'crafts' => 'Rankdarbiai', - 'games' => 'Žaidimai', - 'hobbies' => 'Pomėgiai', - 'home_and_garden' => 'Namai ir sodas', - 'video_games' => 'Kompiuteriniai žaidimai', - 'music_commentary' => 'Muzikos aptarimas', - 'music_history' => 'Muzikos istorija', - 'music_interviews' => 'Muzikiniai interviu', - 'business_news' => 'Verslo naujienos', - 'daily_news' => 'Dienos naujienos', - 'entertainment_news' => 'Pramogų pasaulio naujienos', - 'news_commentary' => 'Naujienų aptarimas', - 'politics' => 'Politika', - 'sports_news' => 'Sporto naujienos', - 'tech_news' => 'Technologijų naujienos', - 'buddhism' => 'Budizmas', - 'christianity' => 'Krikščionybė', - 'hinduism' => 'Induizmas', - 'islam' => 'Islamas', - 'judaism' => 'Judaizmas', - 'religion' => 'Religija', - 'spirituality' => 'Dvasingumas', - 'astronomy' => 'Astronomija', - 'chemistry' => 'Chemija', - 'earth_sciences' => 'Žemės mokslai', - 'life_sciences' => 'Gyvybės mokslai', - 'mathematics' => 'Matematika', - 'natural_sciences' => 'Gamtos mokslai', - 'nature' => 'Gamta', - 'physics' => 'Fizika', - 'social_sciences' => 'Socialiniai mokslai', - 'documentary' => 'Dokumentika', - 'personal_journals' => 'Asmeniniai dienoraščiai', - 'philosophy' => 'Filosofija', - 'places_and_travel' => 'Vietovės ir kelionės', - 'relationships' => 'Santykiai', - 'baseball' => 'Beisbolas', - 'basketball' => 'Krepšinis', - 'cricket' => 'Kriketas', - 'fantasy_sports' => 'Fantastinės sporto šakos', - 'football' => 'Amerikietiškas futbolas', - 'golf' => 'Golfas', - 'hockey' => 'Ledo ritulys', - 'rugby' => 'Regbis', - 'running' => 'Bėgimas', - 'soccer' => 'Futbolas', - 'swimming' => 'Plaukimas', - 'tennis' => 'Tenisas', - 'volleyball' => 'Tinklinis', - 'wilderness' => 'Laukinė gamta', - 'wrestling' => 'Imtynės', - 'after_shows' => 'Pasirodymų aptarimai', - 'film_history' => 'Kino istorija', - 'film_interviews' => 'Kino interviu', - 'film_reviews' => 'Kino apžvalgos', - 'tv_reviews' => 'TV apžvalgos', - ], - 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Grįžti į tinklalaidžių skydelį', - 'post' => 'Jūsų įrašas-anonsas', - 'post_hint' => - "Parašykite pranešimą šios tinklalaidės paskelbimui anansuoti. Šis pranešimas bus rodomas tinklalaidės pradžios tinklalapyje.", - 'message_placeholder' => 'Rašykite pranešimą…', - 'submit' => 'Paskelbti', - 'publication_date' => 'Paskelbimo data', - 'publication_method' => [ - 'now' => 'Dabar', - 'schedule' => 'Suplanuoti', - ], - 'scheduled_publication_date' => 'Suplanupti paskelbimo datą', - 'scheduled_publication_date_hint' => - 'Galite suplanuoti tinklalaidės paskelbimą konkrečiam laikui ateityje. Lauko reikšmės formatas: MMMM-mm-dd HH:mm', - 'submit_edit' => 'Taisyti paskelbimą', - 'cancel_publication' => 'Atšaukti paskelbimą', - 'message_warning' => 'Neparašėte teksto savo įrašui-anonsui!', - 'message_warning_hint' => 'Parašę trumpą pranešimą, padidinsite tinklalaidės matomumą ir klausytojų įsitraukimą.', - 'message_warning_submit' => 'Vis tiek paskelbti', - ], - 'publication_status_banner' => [ - 'draft_mode' => 'juodraščio veiksena', - 'not_published' => 'Ši tinklalaidė dar nepaskelbta.', - 'scheduled' => 'Šią tinklalaidę numatyta paskelbti {publication_date}.', - ], - 'delete_form' => [ - 'disclaimer' => - "Šalinant tinklalaidę, pašalinami visi jos epizodai, failai ir su ja susijusi analitika. Šis veiksmas negrįžtamas, jokių minėtų išteklių vėliau parsisiųsti nebegalėsite.", - 'understand' => 'Suprantu ir noriu visam laikui pašalinti tinklalaidę', - 'submit' => 'Šalinti', - ], - 'by' => 'Leidžia {publisher}', - 'season' => '{seasonNumber} sezonas', - 'list_of_episodes_year' => '{year} metų epizodai ({episodeCount})', - 'list_of_episodes_season' => - '{seasonNumber} sezono epizodai ({episodeCount})', - 'no_episode' => 'Epizodas nerastas!', - 'follow' => 'Sekti', - 'followers' => '{numberOfFollowers, plural, - one {# sekėjas} - few {# sekėjai} - other {# sekėjų} - }', - 'posts' => '{numberOfPosts, plural, - one {# įrašas} - few {# įrašai} - other {# įrašų} - }', - 'activity' => 'Veikla', - 'episodes' => 'Epizodai', - 'sponsor' => 'Paremti', - 'funding_links' => '„{podcastTitle}“ rėmimo nuorodos', - 'find_on' => 'Raskite „{podcastTitle}“', - 'listen_on' => 'Klausykitės', -]; diff --git a/modules/Admin/Language/lt/PodcastImport.php b/modules/Admin/Language/lt/PodcastImport.php deleted file mode 100644 index 2506667c..00000000 --- a/modules/Admin/Language/lt/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'Šis procesas gali ilgai užtrukti. Šioje versijoje jokios proceso eigos jūs nematysite, kol jis nebus užbaigtas. Jei procesas netilptų į jam skirtąjį laiką ir gautumėte apie tai klaidos pranešimą, padidinkite `max_execution_time` reiškmę.', - 'old_podcast_section_title' => 'Importuotina tinklalaidė', - 'old_podcast_section_subtitle' => - 'Prieš importuodami šią tinklalaidę, įsitikinkite, jog turite teisę tai daryti. Kopijuojant ir retransliuojant tinklalaidę, neturint tam reikiamų teisių, yra piratavimas, už tai gali būti baudžiama.', - 'imported_feed_url' => 'Sklaidos kanalo URL', - 'imported_feed_url_hint' => 'Sklaidos kanalas turi būti XML arba RSS formatu.', - 'new_podcast_section_title' => 'Naujoji tinklalaidė', - 'advanced_params_section_title' => 'Papildomi parametrai', - 'advanced_params_section_subtitle' => - 'Jei nežinote, kam šie laukai reikalingi, palikite numatytąsias reikšmes.', - 'slug_field' => 'Laukas, naudotinas epizodų nuorodiniams pavadinimams formuoti', - 'description_field' => - 'Laukas su epizodų aprašymais ir rodomomis pastabomis', - 'force_renumber' => 'Pernumeruoti epizodus', - 'force_renumber_hint' => - 'Pasirinkite, jei jūsų tinklalaidėje epizodai nesunumberuoti, bet norite, kad jie būtų sunumeruoti importo metu.', - 'season_number' => 'Sezono numeris', - 'season_number_hint' => - 'Įveskite reikšmę, jei jūsų tinklalaidė nenurodo sezono numerio, tačiau norite jį nustatyti importo metu. Priešingu atveju lauką palikite tuščią.', - 'max_episodes' => 'Didžiausias leistinas importuotinų epizodų skaičius', - 'max_episodes_hint' => 'Palikite lauką tuščią, jeigu norite importuoti visus epizodus', - 'lock_import' => - 'Šis sklaidos kanalas apsaugotas. Jo importuoti negalite. Jei esate savininkas, atjunkite jo apsaugą dabartinėje platformoje.', - 'submit' => 'Importuoti tinklalaidę', -]; diff --git a/modules/Admin/Language/lt/PodcastNavigation.php b/modules/Admin/Language/lt/PodcastNavigation.php deleted file mode 100644 index f0f66500..00000000 --- a/modules/Admin/Language/lt/PodcastNavigation.php +++ /dev/null @@ -1,42 +0,0 @@ - 'Eiti į tinklalaidės tinklalapį', - 'rss_feed' => 'RSS sklaidos kanalas', - 'dashboard' => 'Tinklalaidės skydelis', - 'podcast-view' => 'Pradžios tinklalapis', - 'podcast-edit' => 'Taisyti tinklalaidę', - 'podcast-persons-manage' => 'Tvarkyti asmenis', - 'podcast-imports' => 'Importuojamos tinklalaidės', - 'podcast-imports-sync' => 'Sinchronizuoti sklaidos kanalus', - 'episodes' => 'Epizodai', - 'episode-list' => 'Visi epizodai', - 'episode-create' => 'Naujas epizodas', - 'analytics' => 'Analitika', - 'podcast-analytics' => 'Auditorijos apžvalga', - 'podcast-analytics-webpages' => 'Apsilankymai tinklalapiuose', - 'podcast-analytics-locations' => 'Vietovės', - 'podcast-analytics-unique-listeners' => 'Unikalūs klausytojai', - 'podcast-analytics-players' => 'Grotuvai', - 'podcast-analytics-listening-time' => 'Klausymosi laikas', - 'podcast-analytics-time-periods' => 'Laiko periodai', - 'monetization' => 'Monetizavimas', - 'subscription-list' => 'Visos Prenumeratos', - 'subscription-create' => 'Pridėti prenumeratą', - 'contributors' => 'Talkininkai', - 'contributor-list' => 'Visi talkininkai', - 'contributor-add' => 'Pridėti talkininką', - 'broadcast' => 'Transliacija', - 'platforms-podcasting' => 'Tinklalaidžių klausymosi programos', - 'platforms-social' => 'Socialiniai tinklai', - 'platforms-funding' => 'Rėmimo nuorodos', - 'podcast-monetization-other' => 'Kita', -]; diff --git a/modules/Admin/Language/lt/Settings.php b/modules/Admin/Language/lt/Settings.php deleted file mode 100644 index dc303d63..00000000 --- a/modules/Admin/Language/lt/Settings.php +++ /dev/null @@ -1,58 +0,0 @@ - 'Bendrosios nuostatos', - 'instance' => [ - 'title' => 'Serveris', - 'site_icon' => 'Svetainės piktograma', - 'site_icon_delete' => 'Šalinti svetainės piktogramą', - 'site_icon_hint' => 'Svetainės piktograma – tai ženkliukas, matomas naršyklių kortelėse, adresynuose ar įtraukus svetainės nuorodą į mobilųjį telefoną.', - 'site_icon_helper' => 'Piktograma turi būti kvadratinė ir bent 512 taškų aukščio ir pločio.', - 'site_name' => 'Svetainės pavadinimas', - 'site_description' => 'Svetainės aprašymas', - 'submit' => 'Įrašyti', - 'editSuccess' => 'Serveris atnaujintas sėkmingai!', - 'deleteIconSuccess' => 'Svetainės piktograma pašalinta sėkmingai!', - ], - 'images' => [ - 'title' => 'Vaizdai', - 'subtitle' => 'Čia galite iš naujo sugeneruoti visus vaizdo failus, remiantis įkeltais originalais. Galite pasinaudoti, jei kurių nors vaizdo failų pasigendate. Šis procesas gali užtrukti.', - 'regenerate' => 'Pergeneruoti vaizdus', - 'regenerationSuccess' => 'Visi vaizdai sėkmingai pergeneruoti!', - ], - 'housekeeping' => [ - 'title' => 'Apsitvarkymas', - 'subtitle' => 'Atliekami įvairūs apsitvarkymo darbai. Pasinaudokite šia funkcija, jei kada pastebėtumėte problemų su daugialypės terpės failais ar duomenų vientisumu. Šie procesai gali užtrukti.', - 'reset_counts' => 'Atkurti skaitliukus', - 'reset_counts_helper' => 'Perskaičiuoti ir perrašyti visus skaitliukus (sekėjų, įrašų, komentarų ir kt. skaičius).', - 'rewrite_media' => 'Perrašyti daugialypės terpės failų metaduomenis', - 'rewrite_media_helper' => 'Pašalinti visus generuotus daugialypės terpės failus (vaizdus, garso įrašus, nuorašus, skyrelius ir kt.) ir sukurti juos iš naujo', - 'rename_episodes_files' => 'Pervardinti epizodų garso įrašų failus', - 'rename_episodes_files_hint' => 'Pervardinti visus garso įrašų failus atsitiktiniais vardais. Šia parinktimi galite pasinaudoti, jei kas nors nutekintų nuorodą į kurį nors jūsų privatų epizodą, nes tai – paprastas būdas jį vėl paslėpti.', - 'clear_cache' => 'Išvalyti visą podėlį', - 'clear_cache_helper' => 'Ištuštinti „Redis“ podėlį ar podėlio failus.', - 'run' => 'Pradėti apsitvarkymą', - 'runSuccess' => 'Apsitvarkymas sėkmingai paleistas!', - ], - 'theme' => [ - 'title' => 'Apipavidalinimas', - 'accent_section_title' => 'Akcento spalva', - 'accent_section_subtitle' => 'Pasirinkite spalvą, naudotiną visuose viešuosiuose tinklalapiuose.', - 'pine' => 'Pušis', - 'crimson' => 'Raudonis', - 'amber' => 'Gintaras', - 'lake' => 'Ežeras', - 'jacaranda' => 'Žibuoklė', - 'onyx' => 'Oniksas', - 'submit' => 'Įrašyti', - 'setInstanceThemeSuccess' => 'Tema atnaujinta sėkmingai!', - ], -]; diff --git a/modules/Admin/Language/lt/Soundbite.php b/modules/Admin/Language/lt/Soundbite.php deleted file mode 100644 index f63dbcf5..00000000 --- a/modules/Admin/Language/lt/Soundbite.php +++ /dev/null @@ -1,31 +0,0 @@ - [ - 'title' => 'Įrašo ištraukos', - 'soundbite' => 'Įrašo ištrauka', - ], - 'messages' => [ - 'createSuccess' => 'Įrašo ištrauka sėkmingai sukurta!', - 'deleteSuccess' => 'Įrašo ištrauka sėkmingai pašalinta!', - ], - 'form' => [ - 'title' => 'Nauja įrašo ištrauka', - 'soundbite_title' => 'Įrašo ištraukos pavadinimas', - 'start_time' => 'Pradžia', - 'duration' => 'Trukmė', - 'submit' => 'Kurti įrašo ištrauką', - ], - 'play' => 'Groti įrašo ištrauką', - 'stop' => 'Stabdyti įrašo ištrauką', - 'create' => 'Nauja įrašo ištrauka', - 'delete' => 'Šalinti įrašo ištrauką', -]; diff --git a/modules/Admin/Language/lt/User.php b/modules/Admin/Language/lt/User.php deleted file mode 100644 index 27515489..00000000 --- a/modules/Admin/Language/lt/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Keisti {username} roles", - 'forcePassReset' => 'Reikalauti pasikeisti slaptažodį', - 'ban' => 'Blokuoti', - 'unban' => 'Atblokuoti', - 'delete' => 'Šalinti', - 'create' => 'Naujas naudotojas', - 'view' => "{username} duomenys", - 'all_users' => 'Visi naudotojai', - 'list' => [ - 'user' => 'Naudotojas', - 'roles' => 'Rolės', - 'banned' => 'Užblokuotas?', - ], - 'form' => [ - 'email' => 'El. paštas', - 'username' => 'Naudotojo vardas', - 'password' => 'Slaptažodis', - 'new_password' => 'Naujas slaptažodis', - 'roles' => 'Rolės', - 'permissions' => 'Leidimai', - 'submit_create' => 'Kurti naudotoją', - 'submit_edit' => 'Įrašyti', - 'submit_password_change' => 'Pakeisti!', - ], - 'roles' => [ - 'superadmin' => 'Superadministratorius', - ], - 'messages' => [ - 'createSuccess' => - 'Naudotojas sukurtas sėkmingai! {username} privalės pasikeisti slaptažodį, kai primąkart prisijungs.', - 'rolesEditSuccess' => - "{username} rolės sėkmingai atnaujintos.", - 'forcePassResetSuccess' => - '{username} privalės pasikeisti slaptažodį, kai kišąkart prisijungs.', - 'banSuccess' => 'Paskyra {username} užblokuota.', - 'unbanSuccess' => 'Paskyra {username} atblokuota.', - 'editOwnerError' => - 'Paskyra {username} priklauso šio serverio savininkui, jos rolių keisti negalite.', - 'banSuperAdminError' => - 'Paskyra {username} priklauso superadministratoriui. Jos užblokuoti negalima.', - 'deleteSuperAdminError' => - 'Paskyra {username} priklauso superadministratoriui, jos pašalinti negalima.', - 'deleteSuccess' => 'Paskyra {username} pašalinta.', - ], -]; diff --git a/modules/Admin/Language/lt/Validation.php b/modules/Admin/Language/lt/Validation.php deleted file mode 100644 index 7223ee5f..00000000 --- a/modules/Admin/Language/lt/Validation.php +++ /dev/null @@ -1,17 +0,0 @@ - - '{field} arba nėra paveikslėlis, arba yra per siauras, arba per žemas.', - 'is_image_ratio' => - '{field} arba nėra paveikslėlis, arba jo kraštinių santykis netinkamas.', - 'is_json' => 'Lauke {field} aptiktas netinkamas JSON.', -]; diff --git a/modules/Admin/Language/lt/VideoClip.php b/modules/Admin/Language/lt/VideoClip.php deleted file mode 100644 index 9a2cb980..00000000 --- a/modules/Admin/Language/lt/VideoClip.php +++ /dev/null @@ -1,72 +0,0 @@ - [ - 'title' => 'Vaizdo klipai', - 'status' => [ - 'label' => 'Būsena', - 'queued' => 'eilėje', - 'queued_hint' => 'Klipas laukia apdorojimo.', - 'pending' => 'laukiama', - 'pending_hint' => 'Klipas bus sugeneruotas netrukus.', - 'running' => 'rengiamas', - 'running_hint' => 'Klipas šiuo metu generuojamas.', - 'failed' => 'nepavyko', - 'failed_hint' => 'Klipo sugeneruoti nepavyko: scenarijaus klaida.', - 'passed' => 'parengtas', - 'passed_hint' => 'Klipas sėkmingai sugeneruotas!', - ], - 'clip' => 'Klipas', - 'duration' => 'Užduoties vykdymo trukmė', - ], - 'title' => 'Vaizdo klipas: {videoClipLabel}', - 'download_clip' => 'Atsisiųsti klipą', - 'create' => 'Naujas vaizdo klipas', - 'go_to_page' => 'Eiti į klipo tinklalapį', - 'retry' => 'Kartoti generavimo bandymą', - 'delete' => 'Šalinti klipą', - 'logs' => 'Užduočių žurnalai', - 'messages' => [ - 'alreadyExistingError' => 'Bandomas sukurti vaizdo klipas jau egzistuoja!', - 'addToQueueSuccess' => 'Vaizdo klipo parengimo užduotis patalpinta į eilę!', - 'deleteSuccess' => 'Vaizdo klipas sėkmingai pašalintas!', - ], - 'format' => [ - 'landscape' => 'Gulsčias', - 'portrait' => 'Stačias', - 'squared' => 'Kvadratinis', - ], - 'form' => [ - 'title' => 'Naujas vaizdo klipas', - 'params_section_title' => 'Vaizdo klipo parametrai', - 'clip_title' => 'Klipo pavadinimas', - 'format' => [ - 'label' => 'Pasirinkite formatą', - 'landscape_hint' => 'Klipai, kurių kraštinių santykis 16:9, puikiai tinka kėlimui į „PeerTube“, „Youtube“ ir „Vimeo“.', - 'portrait_hint' => 'Klipai, kurių kraštinių santykis 9:16, puikiai tinka kėlimui į „TikTok“, „Youtube shorts“ ir „Instagram stories“.', - 'squared_hint' => 'Klipai, kurių kraštinių santykis 1:1, puikiai tinka kėlimui į „Mastodon“, „Facebook“, „Twitter“ ir „LinkedIn“.', - ], - 'theme' => 'Pasirinkite temą', - 'start_time' => 'Pradžia', - 'duration' => 'Trukmė', - 'trim_start' => 'Nukirpti pradžią', - 'trim_end' => 'Nukirpti pabaigą', - 'submit' => 'Kurti vaizdo klipą', - ], - 'requirements' => [ - 'title' => 'Netenkinami reikalavimai', - 'missing' => 'Trūksta įdiegtų priklausomybių. Įsitikinkite, jog įdiegta visa programinė įranga, būtina šio epizodo vaizdo įrašui parengti!', - 'ffmpeg' => 'FFmpeg', - 'gd' => 'Graphics Draw (GD)', - 'freetype' => 'GD „Freetype“ biblioteka', - 'transcript' => 'Nuorašo failas (.srt)', - ], -]; diff --git a/modules/Analytics/AnalyticsTrait.php b/modules/Analytics/AnalyticsTrait.php index 3b2b32bc..a345c597 100644 --- a/modules/Analytics/AnalyticsTrait.php +++ b/modules/Analytics/AnalyticsTrait.php @@ -14,6 +14,11 @@ trait AnalyticsTrait { protected function registerPodcastWebpageHit(int $podcastId): void { + // Prevent analytics hit when authenticated + if (auth()->loggedIn()) { + return; + } + helper('analytics'); set_user_session_deny_list_ip(); diff --git a/modules/Analytics/Config/Analytics.php b/modules/Analytics/Config/Analytics.php index 8eca95d3..fb86c03b 100644 --- a/modules/Analytics/Config/Analytics.php +++ b/modules/Analytics/Config/Analytics.php @@ -20,9 +20,9 @@ class Analytics extends BaseConfig * @var array */ public array $routeFilters = [ - 'analytics-full-data' => 'permission:podcast#.view', - 'analytics-data' => 'permission:podcast#.view', - 'analytics-filtered-data' => 'permission:podcast#.view', + 'analytics-full-data' => 'permission:podcast$1.view', + 'analytics-data' => 'permission:podcast$1.view', + 'analytics-filtered-data' => 'permission:podcast$1.view', ]; /** @@ -37,17 +37,4 @@ class Analytics extends BaseConfig * Z&|qECKBrwgaaD>~;U/tXG1U%tSe_oi5Tzy)h>}5NC2npSrjvM0w_Q>cs=0o=H]* */ public string $salt = ''; - - /** - * -------------------------------------------------------------------------- - * The Open Podcast Prefix Project Config - * -------------------------------------------------------------------------- - * - * @var array - */ - public array $OP3 = [ - 'host' => 'https://op3.dev/', - ]; - - public bool $enableOP3 = false; } diff --git a/modules/Analytics/Controllers/EpisodeAnalyticsController.php b/modules/Analytics/Controllers/EpisodeAnalyticsController.php index 743ccd2a..dfaff0a9 100644 --- a/modules/Analytics/Controllers/EpisodeAnalyticsController.php +++ b/modules/Analytics/Controllers/EpisodeAnalyticsController.php @@ -15,12 +15,11 @@ use App\Models\EpisodeModel; use CodeIgniter\Controller; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; +use Deprecated; class EpisodeAnalyticsController extends Controller { - /** - * @deprecated Replaced by EpisodeAudioController::index method - */ + #[Deprecated(message: 'Replaced by EpisodeAudioController::index method')] public function hit(string $base64EpisodeData, string ...$audioPath): RedirectResponse { $episodeData = unpack( @@ -32,7 +31,8 @@ class EpisodeAnalyticsController extends Controller throw PageNotFoundException::forPageNotFound(); } - $episode = (new EpisodeModel())->getEpisodeById($episodeData['episodeId']); + $episode = new EpisodeModel() + ->getEpisodeById($episodeData['episodeId']); if (! $episode instanceof Episode) { throw PageNotFoundException::forPageNotFound(); @@ -40,7 +40,7 @@ class EpisodeAnalyticsController extends Controller return redirect()->route( 'episode-audio', - [$episode->podcast->handle, $episode->slug, $episode->audio->file_extension] + [$episode->podcast->handle, $episode->slug, $episode->audio->file_extension], ); } } diff --git a/modules/Analytics/Database/Migrations/2017-12-01-000000_add_analytics_podcasts.php b/modules/Analytics/Database/Migrations/2017-12-01-000000_add_analytics_podcasts.php index ab757aa0..e95fb7a8 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-000000_add_analytics_podcasts.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-000000_add_analytics_podcasts.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsPodcasts extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -56,6 +58,7 @@ class AddAnalyticsPodcasts extends BaseMigration $this->forge->createTable('analytics_podcasts'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-010000_add_analytics_podcasts_by_episode.php b/modules/Analytics/Database/Migrations/2017-12-01-010000_add_analytics_podcasts_by_episode.php index 490b296f..421d76ca 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-010000_add_analytics_podcasts_by_episode.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-010000_add_analytics_podcasts_by_episode.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsPodcastsByEpisode extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -49,6 +51,7 @@ class AddAnalyticsPodcastsByEpisode extends BaseMigration $this->forge->createTable('analytics_podcasts_by_episode'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_episode'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-020000_add_analytics_podcasts_by_hour.php b/modules/Analytics/Database/Migrations/2017-12-01-020000_add_analytics_podcasts_by_hour.php index 73fb643d..3050d0af 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-020000_add_analytics_podcasts_by_hour.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-020000_add_analytics_podcasts_by_hour.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsPodcastsByHour extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -44,6 +46,7 @@ class AddAnalyticsPodcastsByHour extends BaseMigration $this->forge->createTable('analytics_podcasts_by_hour'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_hour'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-030000_add_analytics_podcasts_by_player.php b/modules/Analytics/Database/Migrations/2017-12-01-030000_add_analytics_podcasts_by_player.php index 6187b755..e6b653c1 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-030000_add_analytics_podcasts_by_player.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-030000_add_analytics_podcasts_by_player.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsPodcastsByPlayer extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -61,6 +63,7 @@ class AddAnalyticsPodcastsByPlayer extends BaseMigration $this->forge->createTable('analytics_podcasts_by_player'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_player'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-040000_add_analytics_podcasts_by_country.php b/modules/Analytics/Database/Migrations/2017-12-01-040000_add_analytics_podcasts_by_country.php index 33db986e..cd668c0b 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-040000_add_analytics_podcasts_by_country.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-040000_add_analytics_podcasts_by_country.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsPodcastsByCountry extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -45,6 +47,7 @@ class AddAnalyticsPodcastsByCountry extends BaseMigration $this->forge->createTable('analytics_podcasts_by_country'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_country'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-050000_add_analytics_podcasts_by_region.php b/modules/Analytics/Database/Migrations/2017-12-01-050000_add_analytics_podcasts_by_region.php index 51a12a05..3e5ec698 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-050000_add_analytics_podcasts_by_region.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-050000_add_analytics_podcasts_by_region.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsPodcastsByRegion extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -58,6 +60,7 @@ class AddAnalyticsPodcastsByRegion extends BaseMigration $this->forge->createTable('analytics_podcasts_by_region'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_region'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-060000_add_analytics_website_by_browser.php b/modules/Analytics/Database/Migrations/2017-12-01-060000_add_analytics_website_by_browser.php index 3b47f828..8e37d154 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-060000_add_analytics_website_by_browser.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-060000_add_analytics_website_by_browser.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsWebsiteByBrowser extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -45,6 +47,7 @@ class AddAnalyticsWebsiteByBrowser extends BaseMigration $this->forge->createTable('analytics_website_by_browser'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_website_by_browser'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-070000_add_analytics_website_by_referer.php b/modules/Analytics/Database/Migrations/2017-12-01-070000_add_analytics_website_by_referer.php index c7d6d40c..cfbbd362 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-070000_add_analytics_website_by_referer.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-070000_add_analytics_website_by_referer.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsWebsiteByReferer extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -55,6 +57,7 @@ class AddAnalyticsWebsiteByReferer extends BaseMigration $this->forge->createTable('analytics_website_by_referer'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_website_by_referer'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-080000_add_analytics_website_by_entry_page.php b/modules/Analytics/Database/Migrations/2017-12-01-080000_add_analytics_website_by_entry_page.php index 9d21b9ce..bdfebcbc 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-080000_add_analytics_website_by_entry_page.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-080000_add_analytics_website_by_entry_page.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsWebsiteByEntryPage extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -44,6 +46,7 @@ class AddAnalyticsWebsiteByEntryPage extends BaseMigration $this->forge->createTable('analytics_website_by_entry_page'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_website_by_entry_page'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-090000_add_analytics_unknown_useragents.php b/modules/Analytics/Database/Migrations/2017-12-01-090000_add_analytics_unknown_useragents.php index bdff40a7..986848c5 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-090000_add_analytics_unknown_useragents.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-090000_add_analytics_unknown_useragents.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsUnknownUseragents extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -45,6 +47,7 @@ class AddAnalyticsUnknownUseragents extends BaseMigration $this->forge->createTable('analytics_unknown_useragents'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_unknown_useragents'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-100000_add_analytics_podcasts_by_subscription.php b/modules/Analytics/Database/Migrations/2017-12-01-100000_add_analytics_podcasts_by_subscription.php index 9d6e2c7e..44ea8c5e 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-100000_add_analytics_podcasts_by_subscription.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-100000_add_analytics_podcasts_by_subscription.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsPodcastsBySubscription extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -48,6 +50,7 @@ class AddAnalyticsPodcastsBySubscription extends BaseMigration $this->forge->createTable('analytics_podcasts_by_subscription'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_subscription'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_podcasts_procedure.php b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_podcasts_procedure.php index fafe2d5c..9472f0f2 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_podcasts_procedure.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_podcasts_procedure.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsPodcastsProcedure extends BaseMigration { + #[Override] public function up(): void { // Creates Procedure for data insertion @@ -85,6 +87,7 @@ class AddAnalyticsPodcastsProcedure extends BaseMigration $this->db->query($createQuery); } + #[Override] public function down(): void { $prefix = $this->db->getPrefix(); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_unknown_useragents_procedure.php b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_unknown_useragents_procedure.php index d236318e..af627bf2 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_unknown_useragents_procedure.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_unknown_useragents_procedure.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsUnknownUseragentsProcedure extends BaseMigration { + #[Override] public function up(): void { // Creates Procedure for data insertion @@ -33,6 +35,7 @@ class AddAnalyticsUnknownUseragentsProcedure extends BaseMigration $this->db->query($createQuery); } + #[Override] public function down(): void { $procedureName = $this->db->prefixTable('analytics_unknown_useragents'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_website_procedure.php b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_website_procedure.php index 452b6b0a..dc2b0bd2 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_website_procedure.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_website_procedure.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddAnalyticsWebsiteProcedure extends BaseMigration { + #[Override] public function up(): void { // Creates Procedure for data insertion @@ -51,6 +53,7 @@ class AddAnalyticsWebsiteProcedure extends BaseMigration $this->db->query($createQuery); } + #[Override] public function down(): void { $procedureName = $this->db->prefixTable('analytics_website'); diff --git a/modules/Analytics/Helpers/analytics_helper.php b/modules/Analytics/Helpers/analytics_helper.php index df7181bf..adf97da4 100644 --- a/modules/Analytics/Helpers/analytics_helper.php +++ b/modules/Analytics/Helpers/analytics_helper.php @@ -209,12 +209,15 @@ if (! function_exists('podcast_hit')) { * Counting podcast episode downloads for analytic purposes ✅ No IP address is ever stored on the server. ✅ Only * aggregate data is stored in the database. We follow IAB Podcast Measurement Technical Guidelines Version 2.0: * https://iabtechlab.com/standards/podcast-measurement-guidelines/ - * https://iabtechlab.com/wp-content/uploads/2017/12/Podcast_Measurement_v2-Dec-20-2017.pdf ✅ 24-hour window ✅ - * Castopod does not do pre-load ✅ IP deny list https://github.com/client9/ipcat ✅ User-agent Filtering - * https://github.com/opawg/user-agents ✅ RSS User-agent https://github.com/opawg/podcast-rss-useragents ✅ - * Ignores 2 bytes range "Range: 0-1" (performed by official Apple iOS Podcast app) ✅ In case of partial content, - * adds up all requests to check >1mn was downloaded ✅ Identifying Uniques is done with a combination of IP - * Address and User Agent + * https://iabtechlab.com/wp-content/uploads/2017/12/Podcast_Measurement_v2-Dec-20-2017.pdf + * ✅ 24-hour window + * ✅ Castopod does not do pre-load + * ✅ IP deny list https://github.com/client9/ipcat + * ✅ User-agent Filtering https://github.com/opawg/user-agents-v2 + * ✅ RSS User-agent https://github.com/opawg/podcast-rss-useragents + * ✅ Ignores 2 bytes range "Range: 0-1" (performed by official Apple iOS Podcast app) + * ✅ In case of partial content, adds up all requests to check >1mn was downloaded + * ✅ Identifying Uniques is done with a combination of IP Address and User Agent * * @param integer $podcastId The podcast ID * @param integer $episodeId The Episode ID @@ -256,8 +259,8 @@ if (! function_exists('podcast_hit')) { 'Analytics_Episode_' . sha1( $salt . '_' . date( - 'Y-m-d' - ) . '_' . $clientIp . '_' . $superglobals->server('HTTP_USER_AGENT') . '_' . $episodeId + 'Y-m-d', + ) . '_' . $clientIp . '_' . $superglobals->server('HTTP_USER_AGENT') . '_' . $episodeId, ); // The cache expires at midnight: $secondsToMidnight = strtotime('tomorrow') - time(); @@ -305,8 +308,8 @@ if (! function_exists('podcast_hit')) { 'Analytics_Podcast_' . sha1( $salt . '_' . date( - 'Y-m-d' - ) . '_' . $clientIp . '_' . $superglobals->server('HTTP_USER_AGENT') . '_' . $podcastId + 'Y-m-d', + ) . '_' . $clientIp . '_' . $superglobals->server('HTTP_USER_AGENT') . '_' . $podcastId, ); $newListener = 1; diff --git a/modules/Analytics/Models/AnalyticsPodcastByCountryModel.php b/modules/Analytics/Models/AnalyticsPodcastByCountryModel.php index 654860cf..b79d6de8 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByCountryModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByCountryModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByCountryModel extends Model protected $table = 'analytics_podcasts_by_country'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByCountry::class; diff --git a/modules/Analytics/Models/AnalyticsPodcastByEpisodeModel.php b/modules/Analytics/Models/AnalyticsPodcastByEpisodeModel.php index 06aef9f4..cb4d47a9 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByEpisodeModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByEpisodeModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByEpisodeModel extends Model protected $table = 'analytics_podcasts_by_episode'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByEpisode::class; @@ -66,7 +66,7 @@ class AnalyticsPodcastByEpisodeModel extends Model /** * @return AnalyticsPodcastsByEpisode[] */ - public function getDataByMonth(int $podcastId, int $episodeId = null): array + public function getDataByMonth(int $podcastId, ?int $episodeId = null): array { if ( ! ($found = cache("{$podcastId}_{$episodeId}_analytics_podcast_by_episode_by_month")) diff --git a/modules/Analytics/Models/AnalyticsPodcastByHourModel.php b/modules/Analytics/Models/AnalyticsPodcastByHourModel.php index 8e00a832..071df0f9 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByHourModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByHourModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByHourModel extends Model protected $table = 'analytics_podcasts_by_hour'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByHour::class; diff --git a/modules/Analytics/Models/AnalyticsPodcastByPlayerModel.php b/modules/Analytics/Models/AnalyticsPodcastByPlayerModel.php index fff52ab1..1cc7d74d 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByPlayerModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByPlayerModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByPlayerModel extends Model protected $table = 'analytics_podcasts_by_player'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByPlayer::class; diff --git a/modules/Analytics/Models/AnalyticsPodcastByRegionModel.php b/modules/Analytics/Models/AnalyticsPodcastByRegionModel.php index c24ac6af..0bba643b 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByRegionModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByRegionModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByRegionModel extends Model protected $table = 'analytics_podcasts_by_region'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByRegion::class; diff --git a/modules/Analytics/Models/AnalyticsPodcastByServiceModel.php b/modules/Analytics/Models/AnalyticsPodcastByServiceModel.php index b83116e3..e7bacc11 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByServiceModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByServiceModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByServiceModel extends Model protected $table = 'analytics_podcasts_by_player'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByService::class; diff --git a/modules/Analytics/Models/AnalyticsPodcastBySubscriptionModel.php b/modules/Analytics/Models/AnalyticsPodcastBySubscriptionModel.php index 9368f696..c629d057 100644 --- a/modules/Analytics/Models/AnalyticsPodcastBySubscriptionModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastBySubscriptionModel.php @@ -21,7 +21,7 @@ class AnalyticsPodcastBySubscriptionModel extends Model protected $table = 'analytics_podcasts_by_subscription'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsBySubscription::class; diff --git a/modules/Analytics/Models/AnalyticsPodcastModel.php b/modules/Analytics/Models/AnalyticsPodcastModel.php index e58f574e..751620e2 100644 --- a/modules/Analytics/Models/AnalyticsPodcastModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastModel.php @@ -24,7 +24,7 @@ class AnalyticsPodcastModel extends Model protected $table = 'analytics_podcasts'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcasts::class; @@ -246,7 +246,7 @@ class AnalyticsPodcastModel extends Model { if (! ($found = cache('analytics_total_bandwidth_by_month'))) { $found = $this->select( - 'DATE_FORMAT(updated_at,"%Y-%m") as labels, ROUND(sum(bandwidth) / 1000000, 2) as `values`' + 'DATE_FORMAT(updated_at,"%Y-%m") as labels, ROUND(sum(bandwidth) / 1000000, 2) as `values`', ) ->groupBy('labels') ->orderBy('labels', 'ASC') @@ -267,9 +267,8 @@ class AnalyticsPodcastModel extends Model public function getDataTotalStorageByMonth(): array { if (! ($found = cache('analytics_total_storage_by_month'))) { - $found = (new MediaModel())->select( - 'DATE_FORMAT(uploaded_at,"%Y-%m") as labels, ROUND(sum(file_size) / 1000000, 2) as `values`' - ) + $found = new MediaModel() + ->select('DATE_FORMAT(uploaded_at,"%Y-%m") as labels, ROUND(sum(file_size) / 1000000, 2) as `values`') ->groupBy('labels') ->orderBy('labels', 'ASC') ->findAll(); diff --git a/modules/Analytics/Models/AnalyticsUnknownUserAgentsModel.php b/modules/Analytics/Models/AnalyticsUnknownUserAgentsModel.php index 846ca598..59053466 100644 --- a/modules/Analytics/Models/AnalyticsUnknownUserAgentsModel.php +++ b/modules/Analytics/Models/AnalyticsUnknownUserAgentsModel.php @@ -23,7 +23,7 @@ class AnalyticsUnknownUserAgentsModel extends Model protected $table = 'analytics_unknown_useragents'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsUnknownUserAgent::class; diff --git a/modules/Analytics/Models/AnalyticsWebsiteByBrowserModel.php b/modules/Analytics/Models/AnalyticsWebsiteByBrowserModel.php index c225df24..e5398311 100644 --- a/modules/Analytics/Models/AnalyticsWebsiteByBrowserModel.php +++ b/modules/Analytics/Models/AnalyticsWebsiteByBrowserModel.php @@ -23,7 +23,7 @@ class AnalyticsWebsiteByBrowserModel extends Model protected $table = 'analytics_website_by_browser'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsWebsiteByBrowser::class; diff --git a/modules/Analytics/Models/AnalyticsWebsiteByEntryPageModel.php b/modules/Analytics/Models/AnalyticsWebsiteByEntryPageModel.php index 790443d8..90bfcfde 100644 --- a/modules/Analytics/Models/AnalyticsWebsiteByEntryPageModel.php +++ b/modules/Analytics/Models/AnalyticsWebsiteByEntryPageModel.php @@ -23,7 +23,7 @@ class AnalyticsWebsiteByEntryPageModel extends Model protected $table = 'analytics_website_by_entry_page'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsWebsiteByEntryPage::class; diff --git a/modules/Analytics/Models/AnalyticsWebsiteByRefererModel.php b/modules/Analytics/Models/AnalyticsWebsiteByRefererModel.php index 7b50e84b..cd8fb2a5 100644 --- a/modules/Analytics/Models/AnalyticsWebsiteByRefererModel.php +++ b/modules/Analytics/Models/AnalyticsWebsiteByRefererModel.php @@ -23,7 +23,7 @@ class AnalyticsWebsiteByRefererModel extends Model protected $table = 'analytics_website_by_referer'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsWebsiteByReferer::class; diff --git a/modules/Analytics/OP3.php b/modules/Analytics/OP3.php deleted file mode 100644 index 76eaa6e0..00000000 --- a/modules/Analytics/OP3.php +++ /dev/null @@ -1,34 +0,0 @@ - $config - */ - public function __construct(array $config) - { - $this->host = rtrim($config['host'], '/'); - } - - public function wrap(string $audioURL, Episode $episode): string - { - // remove scheme from audioURI if https - $audioURIWithoutHTTPS = preg_replace('(^https://)', '', $audioURL); - - return $this->host . '/e,pg=' . $episode->podcast->guid . '/' . $audioURIWithoutHTTPS; - } -} diff --git a/modules/Api/Rest/V1/Config/Routes.php b/modules/Api/Rest/V1/Config/Routes.php index 99429fa7..5f717129 100644 --- a/modules/Api/Rest/V1/Config/Routes.php +++ b/modules/Api/Rest/V1/Config/Routes.php @@ -19,7 +19,7 @@ $routes->group( $routes->get('/', 'PodcastController::list'); $routes->get('(:num)', 'PodcastController::view/$1'); $routes->get('(:any)', 'ExceptionController::notFound'); - } + }, ); $routes->group( @@ -35,5 +35,5 @@ $routes->group( $routes->post('(:num)/publish', 'EpisodeController::attemptPublish/$1'); $routes->get('(:num)', 'EpisodeController::view/$1'); $routes->get('(:any)', 'ExceptionController::notFound'); - } + }, ); diff --git a/modules/Api/Rest/V1/Controllers/EpisodeController.php b/modules/Api/Rest/V1/Controllers/EpisodeController.php index cdd10509..efedbd23 100644 --- a/modules/Api/Rest/V1/Controllers/EpisodeController.php +++ b/modules/Api/Rest/V1/Controllers/EpisodeController.php @@ -49,7 +49,7 @@ class EpisodeController extends BaseApiController /** @var array $data */ $data = $builder->findAll( (int) ($this->request->getGet('limit') ?? config('RestApi')->limit), - (int) $this->request->getGet('offset') + (int) $this->request->getGet('offset'), ); array_map(static function (Episode $episode): void { @@ -61,7 +61,8 @@ class EpisodeController extends BaseApiController public function view(int $id): ResponseInterface { - $episode = (new EpisodeModel())->getEpisodeById($id); + $episode = new EpisodeModel() + ->getEpisodeById($id); if (! $episode instanceof Episode) { return $this->failNotFound('Episode not found'); @@ -93,7 +94,8 @@ class EpisodeController extends BaseApiController $podcastId = (int) $this->request->getPost('podcast_id'); - $podcast = (new PodcastModel())->getPodcastById($podcastId); + $podcast = new PodcastModel() + ->getPodcastById($podcastId); if (! $podcast instanceof Podcast) { return $this->failNotFound('Podcast not found'); @@ -126,7 +128,7 @@ class EpisodeController extends BaseApiController $validData = $this->validator->getValidated(); - if ((new EpisodeModel()) + if (new EpisodeModel() ->where([ 'slug' => $validData['slug'], 'podcast_id' => $podcast->id, @@ -152,10 +154,10 @@ class EpisodeController extends BaseApiController ? $this->request->getPost('parental_advisory') : null, 'number' => $this->request->getPost('episode_number') ? (int) $this->request->getPost( - 'episode_number' + 'episode_number', ) : null, 'season_number' => $this->request->getPost('season_number') ? (int) $this->request->getPost( - 'season_number' + 'season_number', ) : null, 'type' => $this->request->getPost('type'), 'is_blocked' => $this->request->getPost('block') === 'yes', @@ -169,7 +171,7 @@ class EpisodeController extends BaseApiController $newEpisode->setTranscript($this->request->getFile('transcript_file')); } elseif ($transcriptChoice === 'remote-url') { $newEpisode->transcript_remote_url = $this->request->getPost( - 'transcript_remote_url' + 'transcript_remote_url', ) === '' ? null : $this->request->getPost('transcript_remote_url'); } @@ -178,7 +180,7 @@ class EpisodeController extends BaseApiController $newEpisode->setChapters($this->request->getFile('chapters_file')); } elseif ($chaptersChoice === 'remote-url') { $newEpisode->chapters_remote_url = $this->request->getPost( - 'chapters_remote_url' + 'chapters_remote_url', ) === '' ? null : $this->request->getPost('chapters_remote_url'); } @@ -254,7 +256,7 @@ class EpisodeController extends BaseApiController $episode->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, - $clientTimezone + $clientTimezone, )->setTimezone(app_timezone()); } else { $db->transRollback(); @@ -293,7 +295,6 @@ class EpisodeController extends BaseApiController { $episode->cover_url = $episode->getCover() ->file_url; - $episode->audio_url = $episode->getAudioUrl(); $episode->duration = round($episode->audio->duration); return $episode; diff --git a/modules/Api/Rest/V1/Controllers/PodcastController.php b/modules/Api/Rest/V1/Controllers/PodcastController.php index 06fdefc1..8daf7c33 100644 --- a/modules/Api/Rest/V1/Controllers/PodcastController.php +++ b/modules/Api/Rest/V1/Controllers/PodcastController.php @@ -18,7 +18,8 @@ class PodcastController extends BaseApiController public function list(): ResponseInterface { /** @var array $data */ - $data = (new PodcastModel())->findAll(); + $data = new PodcastModel() + ->findAll(); array_map(static function (Podcast $podcast): void { self::mapPodcast($podcast); }, $data); @@ -27,7 +28,8 @@ class PodcastController extends BaseApiController public function view(int $id): ResponseInterface { - $podcast = (new PodcastModel())->getPodcastById($id); + $podcast = new PodcastModel() + ->getPodcastById($id); if (! $podcast instanceof Podcast) { return $this->failNotFound('Podcast not found'); } diff --git a/modules/Api/Rest/V1/Core/RestApiExceptions.php b/modules/Api/Rest/V1/Core/RestApiExceptions.php index 7484c1b0..7d132aea 100644 --- a/modules/Api/Rest/V1/Core/RestApiExceptions.php +++ b/modules/Api/Rest/V1/Core/RestApiExceptions.php @@ -5,10 +5,12 @@ declare(strict_types=1); namespace Modules\Api\Rest\V1\Core; use CodeIgniter\Debug\Exceptions; +use Override; use Throwable; class RestApiExceptions extends Exceptions { + #[Override] protected function render(Throwable $exception, int $statusCode): void { header('Content-Type: application/json'); diff --git a/modules/Api/Rest/V1/Filters/ApiFilter.php b/modules/Api/Rest/V1/Filters/ApiFilter.php index dee4982c..8bb25935 100644 --- a/modules/Api/Rest/V1/Filters/ApiFilter.php +++ b/modules/Api/Rest/V1/Filters/ApiFilter.php @@ -11,13 +11,16 @@ use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use Modules\Api\Rest\V1\Config\RestApi; +use Override; class ApiFilter implements FilterInterface { /** * @param Request $request - * @return RequestInterface|ResponseInterface|string|void + * + * @return RequestInterface|ResponseInterface|string|null */ + #[Override] public function before(RequestInterface $request, $arguments = null) { /** @var RestApi $restApiConfig */ @@ -60,13 +63,16 @@ class ApiFilter implements FilterInterface return $response; } } + + return null; } /** - * @param string[]|null $arguments + * @param string[]|null $arguments * * @return ResponseInterface|null */ + #[Override] public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return null; diff --git a/modules/Api/Rest/V1/podcast.json b/modules/Api/Rest/V1/podcast.json deleted file mode 100644 index 3d24fc83..00000000 --- a/modules/Api/Rest/V1/podcast.json +++ /dev/null @@ -1,687 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "Castopod podcasts" - }, - "paths": { - "/api/rest/v1/podcasts": { - "get": { - "summary": "List all podcasts", - "responses": { - "200": { - "description": "Object of podcasts", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Podcasts" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/api/rest/v1/podcasts/{id}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "The id of the podcast to retrieve", - "schema": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - } - } - ], - "get": { - "summary": "Info for a specific podcast", - "responses": { - "200": { - "description": "Expected response to a valid request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Podcast" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/api/rest/v1/episodes": { - "get": { - "summary": "List all episodes", - "responses": { - "200": { - "description": "Object of episodes", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Episodes" - } - } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - }, - "post": { - "summary": "Create a new episode", - "requestBody": { - "description": "Episode object that needs to be added", - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/EpisodeCreateRequest" - } - } - } - }, - "responses": { - "201": { - "description": "Episode created successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Episode" - } - } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/api/rest/v1/episodes/{id}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "The id of the episode to retrieve", - "schema": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - } - } - ], - "get": { - "summary": "Info for a specific episode", - "responses": { - "200": { - "description": "Expected response to a valid request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Episode" - } - } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/api/rest/v1/episodes/{id}/publish": { - "post": { - "summary": "Publish an episode", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "The id of the episode to publish", - "schema": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - } - } - ], - "requestBody": { - "description": "Publish parameters", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EpisodePublishRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Episode published successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Episode" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Podcast": { - "type": "object", - "required": [ - "id", - "guid", - "actor_id", - "handle", - "title", - "description_markdown", - "description_html", - "cover_id", - "language_code", - "category_id", - "owner_name", - "owner_email", - "type", - "is_blocked", - "is_completed", - "is_locked", - "is_published_on_hubs", - "created_by", - "updated_by", - "created_at", - "updated_at", - "feed_url" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "guid": { - "type": "string", - "maxLength": 36 - }, - "actor_id": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "handle": { - "type": "string", - "maxLength": 32 - }, - "title": { - "type": "string", - "maxLength": 128 - }, - "description_markdown": { - "type": "string" - }, - "description_html": { - "type": "string" - }, - "cover_id": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "banner_id": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "language_code": { - "type": "string", - "maxLength": 2 - }, - "category_id": { - "type": "integer", - "format": "int64", - "minimum": 1 - }, - "parental_advisory": { - "type": "string", - "enum": ["clean", "explicit"] - }, - "owner_name": { - "type": "string", - "maxLength": 128 - }, - "owner_email": { - "type": "string", - "maxLength": 255 - }, - "publisher": { - "type": "string", - "maxLength": 128 - }, - "type": { - "type": "string", - "enum": ["episodic", "serial"] - }, - "copyright": { - "type": "string", - "maxLength": 128 - }, - "episode_description_footer_markdown": { - "type": "string" - }, - "episode_description_footer_html": { - "type": "string" - }, - "is_blocked": { - "type": "integer", - "format": "int32", - "enum": [0, 1], - "minLength": 1 - }, - "is_completed": { - "type": "integer", - "format": "int32", - "enum": [0, 1], - "minLength": 1 - }, - "is_locked": { - "type": "integer", - "format": "int32", - "enum": [0, 1], - "minLength": 1 - }, - "imported_feed_url": { - "type": "string", - "maxLength": 512 - }, - "new_feed_url": { - "type": "string", - "maxLength": 512 - }, - "payment_pointer": { - "type": "string", - "maxLength": 128 - }, - "location_name": { - "type": "string", - "maxLength": 128 - }, - "location_geo": { - "type": "string", - "maxLength": 32 - }, - "location_osm": { - "type": "string", - "maxLength": 12 - }, - "custom_rss": { - "type": "string" - }, - "is_published_on_hubs": { - "type": "integer", - "format": "int32", - "enum": [0, 1], - "minLength": 1 - }, - "partner_id": { - "type": "string", - "maxLength": 32 - }, - "partner_link_url": { - "type": "string", - "maxLength": 512 - }, - "partner_image_url": { - "type": "string", - "maxLength": 512 - }, - "created_by": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "updated_by": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "created_at": { - "type": "object", - "properties": { - "date": { - "type": "string", - "format": "date-time" - }, - "timezone_type": { - "type": "integer", - "format": "int32" - }, - "timezone": { - "type": "string" - } - } - }, - "updated_at": { - "type": "object", - "properties": { - "date": { - "type": "string", - "format": "date-time" - }, - "timezone_type": { - "type": "integer", - "format": "int32" - }, - "timezone": { - "type": "string" - } - } - }, - "feed_url": { - "type": "string" - } - } - }, - "Podcasts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Podcast" - } - }, - "Episode": { - "type": "object", - "required": [ - "id", - "title", - "slug", - "podcast_id", - "created_by", - "updated_by", - "created_at", - "updated_at" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "title": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "podcast_id": { - "type": "integer", - "format": "int64" - }, - "description_markdown": { - "type": "string" - }, - "description_html": { - "type": "string" - }, - "audio_url": { - "type": "string", - "format": "uri" - }, - "cover_url": { - "type": "string", - "format": "uri" - }, - "duration": { - "type": "integer", - "format": "int32" - }, - "published_at": { - "type": "string", - "format": "date-time" - }, - "created_by": { - "type": "integer", - "format": "int64" - }, - "updated_by": { - "type": "integer", - "format": "int64" - } - } - }, - "Episodes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Episode" - } - }, - "EpisodeCreateRequest": { - "type": "object", - "required": [ - "user_id", - "updated_by", - "title", - "slug", - "podcast_id", - "audio_file" - ], - "properties": { - "user_id": { - "type": "integer", - "format": "int64", - "description": "ID of the user creating the episode" - }, - "updated_by": { - "type": "integer", - "format": "int64", - "description": "ID of the user updating the episode" - }, - "title": { - "type": "string", - "description": "Title of the episode" - }, - "slug": { - "type": "string", - "description": "URL-friendly slug of the episode" - }, - "podcast_id": { - "type": "integer", - "format": "int64", - "description": "ID of the podcast the episode belongs to" - }, - "audio_file": { - "type": "string", - "format": "binary", - "description": "Audio file for the episode" - }, - "cover": { - "type": "string", - "format": "binary", - "description": "Cover image for the episode" - }, - "description": { - "type": "string", - "description": "Description of the episode" - }, - "location_name": { - "type": "string", - "description": "Location associated with the episode" - }, - "parental_advisory": { - "type": "string", - "enum": ["clean", "explicit"], - "description": "Parental advisory rating" - }, - "episode_number": { - "type": "integer", - "format": "int32", - "description": "Episode number (for serial podcasts)" - }, - "season_number": { - "type": "integer", - "format": "int32", - "description": "Season number (for serial podcasts)" - }, - "type": { - "type": "string", - "enum": ["full", "trailer", "bonus"], - "description": "Type of episode" - }, - "block": { - "type": "string", - "enum": ["yes", "no"], - "description": "Block episode from being published" - }, - "custom_rss": { - "type": "string", - "description": "Custom RSS content" - }, - "premium": { - "type": "string", - "enum": ["yes", "no"], - "description": "Mark episode as premium content" - }, - "transcript-choice": { - "type": "string", - "enum": ["upload-file", "remote-url"], - "description": "Transcript source choice" - }, - "transcript_file": { - "type": "string", - "format": "binary", - "description": "Transcript file" - }, - "transcript_remote_url": { - "type": "string", - "format": "uri", - "description": "Remote URL for transcript" - }, - "chapters-choice": { - "type": "string", - "enum": ["upload-file", "remote-url"], - "description": "Chapters source choice" - }, - "chapters_file": { - "type": "string", - "format": "binary", - "description": "Chapters file" - }, - "chapters_remote_url": { - "type": "string", - "format": "uri", - "description": "Remote URL for chapters" - } - } - }, - "EpisodePublishRequest": { - "type": "object", - "required": ["publication_method"], - "properties": { - "publication_method": { - "type": "string", - "enum": ["now", "schedule", "with_podcast"], - "description": "Method of publication" - }, - "scheduled_publication_date": { - "type": "string", - "format": "date-time", - "description": "Scheduled date and time for publication" - }, - "client_timezone": { - "type": "string", - "description": "Timezone of the client" - } - } - }, - "Error": { - "type": "object", - "properties": { - "status": { - "type": "integer", - "format": "int32" - }, - "error": { - "type": "integer", - "format": "int32" - }, - "messages": { - "type": "object", - "properties": { - "error": { - "type": "string" - } - } - } - } - } - } - } -} diff --git a/modules/Api/Rest/V1/schema.yaml b/modules/Api/Rest/V1/schema.yaml new file mode 100644 index 00000000..9c7a0220 --- /dev/null +++ b/modules/Api/Rest/V1/schema.yaml @@ -0,0 +1,530 @@ +openapi: 3.1.0 +info: + version: 1.0.0 + title: Castopod API + description: |- + The Castopod API offers you a programmatic way to integrate your Podcasts and + Episodes in your apps and help you automate creation and publishing. + + ⚠️ **The API is disabled by default.** + + You may add the following feature flag in your `.env` to activate it: + + ```ini + restapi.enabled=true + ``` + + Operations to add or publish episodes require you to setup basic authentication + in your `.env`: + + ```ini + restapi.basicAuth=true + restapi.basicAuthUsername="YOUR_BASIC_AUTH_USERNAME" + restapi.basicAuthPassword="YOUR_BASIC_AUTH_PASSWORD" + ``` + + With BasicAuth enabled, your requests must include the `Authorization` header + with the username and password you have set previously: + + ``` + "Authorization": "Basic username:password" + ``` + + license: + name: AGPL v3 + url: https://code.castopod.org/adaures/castopod/-/blob/develop/LICENSE.md +paths: + /podcasts: + get: + summary: Get all podcasts + operationId: get-all-podcasts + responses: + "200": + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Podcast" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /podcasts/{podcastId}: + parameters: + - name: podcastId + in: path + description: The id of the podcast to retrieve + required: true + schema: + type: integer + format: int64 + get: + summary: Get podcast by ID + description: Returns a single podcast + operationId: get-podcast-by-id + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Podcast" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /episodes: + get: + summary: Get all episodes + operationId: get-all-episodes + responses: + "200": + description: Object of episodes + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/Episode" + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Add a new episode + operationId: add-episode + requestBody: + description: Episode object that needs to be added + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/EpisodeCreateRequest" + responses: + "201": + description: Episode created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/Episode" + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /episodes/{episodeId}: + parameters: + - name: episodeId + in: path + description: The id of the episode to retrieve + required: true + schema: + type: integer + format: int64 + get: + summary: Get episode by ID + description: Returns a single episode + operationId: get-episode-by-id + responses: + "200": + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Episode" + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /episodes/{episodeId}/publish: + post: + summary: Publish an episode + operationId: publish-episode + parameters: + - name: episodeId + in: path + description: The id of the episode to publish + required: true + schema: + type: integer + format: int64 + requestBody: + description: Publish parameters + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/EpisodePublishRequest" + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Episode" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Podcast: + type: object + required: + - id + - guid + - actor_id + - handle + - title + - description_markdown + - description_html + - cover_id + - language_code + - category_id + - owner_name + - owner_email + - type + - is_blocked + - is_completed + - is_locked + - is_published_on_hubs + - created_by + - updated_by + - created_at + - updated_at + - feed_url + properties: + id: + type: integer + format: int64 + guid: + type: string + maxLength: 36 + actor_id: + type: integer + format: int64 + handle: + type: string + maxLength: 32 + title: + type: string + maxLength: 128 + description_markdown: + type: string + description_html: + type: string + cover_id: + type: integer + format: int64 + banner_id: + type: integer + format: int64 + language_code: + type: string + maxLength: 2 + category_id: + type: integer + format: int64 + minimum: 1 + parental_advisory: + type: string + enum: + - clean + - explicit + owner_name: + type: string + maxLength: 128 + owner_email: + type: string + maxLength: 255 + publisher: + type: string + maxLength: 128 + type: + type: string + enum: + - episodic + - serial + copyright: + type: string + maxLength: 128 + episode_description_footer_markdown: + type: string + episode_description_footer_html: + type: string + is_blocked: + type: integer + format: int32 + enum: + - 0 + - 1 + is_completed: + type: integer + format: int32 + enum: + - 0 + - 1 + is_locked: + type: integer + format: int32 + enum: + - 0 + - 1 + imported_feed_url: + type: string + maxLength: 512 + new_feed_url: + type: string + maxLength: 512 + location_name: + type: string + maxLength: 128 + location_geo: + type: string + maxLength: 32 + location_osm: + type: string + maxLength: 12 + custom_rss: + type: string + is_published_on_hubs: + type: integer + format: int32 + enum: + - 0 + - 1 + partner_id: + type: string + maxLength: 32 + partner_link_url: + type: string + maxLength: 512 + partner_image_url: + type: string + maxLength: 512 + created_by: + type: integer + format: int64 + updated_by: + type: integer + format: int64 + created_at: + type: object + properties: + date: + type: string + format: date-time + timezone_type: + type: integer + format: int32 + timezone: + type: string + updated_at: + type: object + properties: + date: + type: string + format: date-time + timezone_type: + type: integer + format: int32 + timezone: + type: string + feed_url: + type: string + Episode: + type: object + required: + - id + - title + - slug + - podcast_id + - created_by + - updated_by + - created_at + - updated_at + properties: + id: + type: integer + format: int64 + title: + type: string + slug: + type: string + podcast_id: + type: integer + format: int64 + description_markdown: + type: string + description_html: + type: string + audio_url: + type: string + format: uri + cover_url: + type: string + format: uri + duration: + type: integer + format: int32 + published_at: + type: string + format: date-time + created_by: + type: integer + format: int64 + updated_by: + type: integer + format: int64 + EpisodeCreateRequest: + type: object + required: + - user_id + - updated_by + - title + - slug + - podcast_id + - audio_file + properties: + user_id: + type: integer + format: int64 + description: ID of the user creating the episode + updated_by: + type: integer + format: int64 + description: ID of the user updating the episode + title: + type: string + description: Title of the episode + slug: + type: string + description: URL-friendly slug of the episode + podcast_id: + type: integer + format: int64 + description: ID of the podcast the episode belongs to + audio_file: + type: string + format: binary + description: Audio file for the episode + cover: + type: string + format: binary + description: Cover image for the episode + description: + type: string + description: Description of the episode + location_name: + type: string + description: Location associated with the episode + parental_advisory: + type: string + enum: + - clean + - explicit + description: Parental advisory rating + episode_number: + type: integer + format: int32 + description: Episode number (for serial podcasts) + season_number: + type: integer + format: int32 + description: Season number (for serial podcasts) + type: + type: string + enum: + - full + - trailer + - bonus + description: Type of episode + block: + type: string + enum: + - "yes" + - "no" + description: Block episode from being published + custom_rss: + type: string + description: Custom RSS content + premium: + type: string + enum: + - "yes" + - "no" + description: Mark episode as premium content + transcript-choice: + type: string + enum: + - upload-file + - remote-url + description: Transcript source choice + transcript_file: + type: string + format: binary + description: Transcript file + transcript_remote_url: + type: string + format: uri + description: Remote URL for transcript + chapters-choice: + type: string + enum: + - upload-file + - remote-url + description: Chapters source choice + chapters_file: + type: string + format: binary + description: Chapters file + chapters_remote_url: + type: string + format: uri + description: Remote URL for chapters + EpisodePublishRequest: + type: object + required: + - publication_method + properties: + publication_method: + type: string + enum: + - now + - schedule + - with_podcast + description: Method of publication + scheduled_publication_date: + type: string + format: date-time + description: Scheduled date and time for publication + client_timezone: + type: string + description: Timezone of the client + Error: + type: object + properties: + status: + type: integer + format: int32 + error: + type: integer + format: int32 + messages: + type: object + properties: + error: + type: string diff --git a/modules/Auth/Auth.php b/modules/Auth/Auth.php index 2e123609..83403106 100644 --- a/modules/Auth/Auth.php +++ b/modules/Auth/Auth.php @@ -6,6 +6,7 @@ namespace Modules\Auth; use CodeIgniter\Router\RouteCollection; use CodeIgniter\Shield\Auth as ShieldAuth; +use Override; class Auth extends ShieldAuth { @@ -19,6 +20,7 @@ class Auth extends ShieldAuth * * @param array{except?:list} $config */ + #[Override] public function routes(RouteCollection &$routes, array $config = []): void { $authRoutes = config('AuthRoutes') diff --git a/modules/Auth/Commands/RolesDoc.php b/modules/Auth/Commands/RolesDoc.php index 0677722a..84d7b941 100644 --- a/modules/Auth/Commands/RolesDoc.php +++ b/modules/Auth/Commands/RolesDoc.php @@ -11,13 +11,14 @@ use CodeIgniter\View\Table; use League\HTMLToMarkdown\Converter\TableConverter; use League\HTMLToMarkdown\HtmlConverter; use Modules\Auth\Config\AuthGroups; +use Override; class RolesDoc extends BaseCommand { /** * @var array */ - private const COMMENT_BLOCK_IDS = [ + private const array COMMENT_BLOCK_IDS = [ 'instance_roles' => 'AUTH-INSTANCE-ROLES-LIST', 'instance_permissions' => 'AUTH-INSTANCE-PERMISSIONS-LIST', 'podcast_roles' => 'AUTH-PODCAST-ROLES-LIST', @@ -39,6 +40,7 @@ class RolesDoc extends BaseCommand */ protected $description = 'Generates the html table references for roles and permissions in the docs.'; + #[Override] public function run(array $params): void { // loop over all files in path @@ -56,8 +58,8 @@ class RolesDoc extends BaseCommand foreach ($files as $file) { $locale = $this->detectLocaleFromPath($file); - $language = service('language'); - $language->setLocale($locale); + service('language') + ->setLocale($locale); $authGroups = new AuthGroups(); @@ -86,7 +88,7 @@ class RolesDoc extends BaseCommand $authGroups->instanceGroups, static function ($table, $key, array $value) use ($instanceMatrix): void { $table->addRow($value['title'], $value['description'], implode(', ', $instanceMatrix[$key])); - } + }, ); } @@ -99,7 +101,7 @@ class RolesDoc extends BaseCommand $authGroups->instancePermissions, static function ($table, $key, $value): void { $table->addRow($key, $value); - } + }, ); } @@ -113,7 +115,7 @@ class RolesDoc extends BaseCommand $authGroups->podcastGroups, static function ($table, $key, array $value) use ($podcastMatrix): void { $table->addRow($value['title'], $value['description'], implode(', ', $podcastMatrix[$key])); - } + }, ); } @@ -126,7 +128,7 @@ class RolesDoc extends BaseCommand $authGroups->podcastPermissions, static function ($table, $key, $value): void { $table->addRow($key, $value); - } + }, ); } @@ -139,7 +141,7 @@ class RolesDoc extends BaseCommand string $pattern, array $tableHeading, array $data, - Closure $callback + Closure $callback, ): string { // check if it has the start and end comments to insert roles table // looking for and @@ -167,7 +169,7 @@ class RolesDoc extends BaseCommand $newFileContents = preg_replace( $pattern, '${1}' . PHP_EOL . PHP_EOL . $markdownTable . PHP_EOL . PHP_EOL . '${2}', - $fileContents + $fileContents, ); if ($newFileContents === null) { @@ -182,7 +184,7 @@ class RolesDoc extends BaseCommand preg_match( '~docs\/src\/content\/docs\/(?:([a-z]{2}(?:-[A-Za-z]{2,})?)\/)getting-started\/auth\.mdx~', $fileKey, - $match + $match, ); if ($match === []) { diff --git a/modules/Auth/Config/Auth.php b/modules/Auth/Config/Auth.php index fd935745..f5fc2c68 100644 --- a/modules/Auth/Config/Auth.php +++ b/modules/Auth/Config/Auth.php @@ -10,6 +10,7 @@ use CodeIgniter\Shield\Authentication\AuthenticatorInterface; use CodeIgniter\Shield\Config\Auth as ShieldAuth; use CodeIgniter\Shield\Entities\User; use Modules\Auth\Models\UserModel; +use Override; class Auth extends ShieldAuth { @@ -148,6 +149,7 @@ class Auth extends ShieldAuth * * Redirects to the set-password form if magicLogin */ + #[Override] public function loginRedirect(): string { if (! session('magicLogin')) { diff --git a/modules/Auth/Config/AuthGroups.php b/modules/Auth/Config/AuthGroups.php index 06fe419b..5bb64b88 100644 --- a/modules/Auth/Config/AuthGroups.php +++ b/modules/Auth/Config/AuthGroups.php @@ -107,6 +107,7 @@ class AuthGroups extends ShieldAuthGroups public array $instanceBasePermissions = [ 'admin.access', 'admin.settings', + 'plugins.manage', 'users.manage', 'persons.manage', 'pages.manage', @@ -122,6 +123,7 @@ class AuthGroups extends ShieldAuthGroups public array $instanceMatrix = [ 'superadmin' => [ 'admin.*', + 'plugins.*', 'podcasts.*', 'users.manage', 'persons.manage', @@ -226,7 +228,8 @@ class AuthGroups extends ShieldAuthGroups * For each podcast, include podcast groups, permissions, and matrix into $groups, $permissions, and $matrix * attributes. */ - $podcasts = (new PodcastModel())->findAll(); + $podcasts = new PodcastModel() + ->findAll(); foreach ($podcasts as $podcast) { $this->generatePodcastAuthorizations($podcast->id); } diff --git a/modules/Auth/Config/Routes.php b/modules/Auth/Config/Routes.php index 85ede75a..0b386995 100644 --- a/modules/Auth/Config/Routes.php +++ b/modules/Auth/Config/Routes.php @@ -23,7 +23,7 @@ $routes->group( ]); $routes->post('magic-link-set-password', 'MagicLinkController::setPasswordAction'); - $routes->post('interact-as-actor', 'InteractController::attemptInteractAsActor', [ + $routes->post('interact-as-actor', 'InteractController::interactAsActorAction', [ 'as' => 'interact-as-actor', ]); @@ -33,11 +33,11 @@ $routes->group( 'as' => 'user-list', 'filter' => 'permission:users.manage', ]); - $routes->get('new', 'UserController::create', [ + $routes->get('new', 'UserController::createView', [ 'as' => 'user-create', 'filter' => 'permission:users.manage', ]); - $routes->post('new', 'UserController::attemptCreate', [ + $routes->post('new', 'UserController::createAction', [ 'filter' => 'permission:users.manage', ]); // User @@ -46,18 +46,18 @@ $routes->group( 'as' => 'user-view', 'filter' => 'permission:users.manage', ]); - $routes->get('edit', 'UserController::edit/$1', [ + $routes->get('edit', 'UserController::editView/$1', [ 'as' => 'user-edit', 'filter' => 'permission:users.manage', ]); - $routes->post('edit', 'UserController::attemptEdit/$1', [ + $routes->post('edit', 'UserController::editAction/$1', [ 'filter' => 'permission:users.manage', ]); $routes->get('delete', 'UserController::delete/$1', [ 'as' => 'user-delete', 'filter' => 'permission:users.manage', ]); - $routes->post('delete', 'UserController::attemptDelete/$1', [ + $routes->post('delete', 'UserController::deleteAction/$1', [ 'as' => 'user-delete', 'filter' => 'permission:users.manage', ]); @@ -71,63 +71,63 @@ $routes->group( $routes->get('change-password', 'MyAccountController::changePassword', [ 'as' => 'change-password', ],); - $routes->post('change-password', 'MyAccountController::attemptChange'); + $routes->post('change-password', 'MyAccountController::changeAction'); }); // Podcast contributors $routes->group('podcasts/(:num)/contributors', static function ($routes): void { $routes->get('/', 'ContributorController::list/$1', [ 'as' => 'contributor-list', - 'filter' => 'permission:podcast#.manage-contributors', + 'filter' => 'permission:podcast$1.manage-contributors', ]); - $routes->get('add', 'ContributorController::create/$1', [ + $routes->get('add', 'ContributorController::createView/$1', [ 'as' => 'contributor-add', - 'filter' => 'permission:podcast#.manage-contributors', + 'filter' => 'permission:podcast$1.manage-contributors', ]); $routes->post( 'add', - 'ContributorController::attemptCreate/$1', + 'ContributorController::createAction/$1', [ - 'filter' => 'permission:podcast#.manage-contributors', + 'filter' => 'permission:podcast$1.manage-contributors', ], ); // Contributor $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'ContributorController::view/$1/$2', [ 'as' => 'contributor-view', - 'filter' => 'permission:podcast#.manage-contributors', + 'filter' => 'permission:podcast$1.manage-contributors', ]); $routes->get( 'edit', - 'ContributorController::edit/$1/$2', + 'ContributorController::editView/$1/$2', [ 'as' => 'contributor-edit', - 'filter' => 'permission:podcast#.manage-contributors', + 'filter' => 'permission:podcast$1.manage-contributors', ], ); $routes->post( 'edit', - 'ContributorController::attemptEdit/$1/$2', + 'ContributorController::editAction/$1/$2', [ - 'filter' => 'permission:podcast#.manage-contributors', + 'filter' => 'permission:podcast$1.manage-contributors', ], ); $routes->get( 'remove', - 'ContributorController::remove/$1/$2', + 'ContributorController::removeView/$1/$2', [ 'as' => 'contributor-remove', - 'filter' => 'permission:podcast#.manage-contributors', + 'filter' => 'permission:podcast$1.manage-contributors', ], ); $routes->post( 'remove', - 'ContributorController::attemptRemove/$1/$2', + 'ContributorController::removeAction/$1/$2', [ - 'filter' => 'permission:podcast#.manage-contributors', + 'filter' => 'permission:podcast$1.manage-contributors', ], ); }); }); - } + }, ); diff --git a/modules/Auth/Controllers/ActionController.php b/modules/Auth/Controllers/ActionController.php index 5dfbaf5b..649005ee 100644 --- a/modules/Auth/Controllers/ActionController.php +++ b/modules/Auth/Controllers/ActionController.php @@ -7,6 +7,7 @@ namespace Modules\Auth\Controllers; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Controllers\ActionController as ShieldActionController; +use Override; use Psr\Log\LoggerInterface; use ViewThemes\Theme; @@ -15,10 +16,11 @@ use ViewThemes\Theme; */ class ActionController extends ShieldActionController { + #[Override] public function initController( RequestInterface $request, ResponseInterface $response, - LoggerInterface $logger + LoggerInterface $logger, ): void { parent::initController($request, $response, $logger); diff --git a/modules/Auth/Controllers/ContributorController.php b/modules/Auth/Controllers/ContributorController.php index 7876af20..119703bf 100644 --- a/modules/Auth/Controllers/ContributorController.php +++ b/modules/Auth/Controllers/ContributorController.php @@ -30,7 +30,7 @@ class ContributorController extends BaseController throw PageNotFoundException::forPageNotFound(); } - if (! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { throw PageNotFoundException::forPageNotFound(); } @@ -40,9 +40,9 @@ class ContributorController extends BaseController return $this->{$method}(); } - if (($this->contributor = (new UserModel())->getPodcastContributor( + if (($this->contributor = new UserModel()->getPodcastContributor( (int) $params[1], - (int) $params[0] + (int) $params[0], )) instanceof User) { return $this->{$method}(); } @@ -56,6 +56,7 @@ class ContributorController extends BaseController 'podcast' => $this->podcast, ]; + $this->setHtmlHead(lang('Contributor.podcast_contributors')); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, ]); @@ -66,9 +67,14 @@ class ContributorController extends BaseController { $data = [ 'podcast' => $this->podcast, - 'contributor' => (new UserModel())->getPodcastContributor($this->contributor->id, $this->podcast->id), + 'contributor' => new UserModel() + ->getPodcastContributor($this->contributor->id, $this->podcast->id), ]; + $this->setHtmlHead(lang('Contributor.view', [ + 'username' => esc($this->contributor->username), + 'podcastTitle' => esc($this->podcast->title), + ])); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, 1 => $this->contributor->username, @@ -76,15 +82,19 @@ class ContributorController extends BaseController return view('contributor/view', $data); } - public function create(): string + public function createView(): string { helper('form'); - $users = (new UserModel())->findAll(); + $users = new UserModel() + ->findAll(); $contributorOptions = array_reduce( $users, static function (array $result, User $user): array { - $result[$user->id] = $user->username; + $result[] = [ + 'value' => $user->id, + 'label' => $user->username, + ]; return $result; }, [], @@ -95,7 +105,10 @@ class ContributorController extends BaseController array_walk( $roles, static function (string $role, $key) use (&$roleOptions): array { - $roleOptions[$role] = lang('Auth.podcast_groups.' . $role . '.title'); + $roleOptions[] = [ + 'value' => $role, + 'label' => lang('Auth.podcast_groups.' . $role . '.title'), + ]; return $roleOptions; }, [], @@ -107,16 +120,18 @@ class ContributorController extends BaseController 'roleOptions' => $roleOptions, ]; + $this->setHtmlHead(lang('Contributor.add_contributor', [esc($this->podcast->title)])); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, ]); return view('contributor/create', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { /** @var User $user */ - $user = (new UserModel())->find((int) $this->request->getPost('user')); + $user = new UserModel() + ->find((int) $this->request->getPost('user')); if (get_podcast_group($user, $this->podcast->id)) { return redirect() @@ -130,7 +145,7 @@ class ContributorController extends BaseController return redirect()->route('contributor-list', [$this->podcast->id]); } - public function edit(): string|RedirectResponse + public function editView(): string|RedirectResponse { helper('form'); @@ -139,7 +154,10 @@ class ContributorController extends BaseController array_walk( $roles, static function (string $role) use (&$roleOptions): array { - $roleOptions[$role] = lang('Auth.podcast_groups.' . $role . '.title'); + $roleOptions[] = [ + 'value' => $role, + 'label' => lang('Auth.podcast_groups.' . $role . '.title'), + ]; return $roleOptions; }, [], @@ -161,6 +179,7 @@ class ContributorController extends BaseController 'roleOptions' => $roleOptions, ]; + $this->setHtmlHead(lang('Contributor.edit_role', [esc($this->contributor->username)])); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, 1 => $this->contributor->username, @@ -168,7 +187,7 @@ class ContributorController extends BaseController return view('contributor/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(): RedirectResponse { // forbid updating a podcast owner if ($this->podcast->created_by === $this->contributor->id) { @@ -186,11 +205,11 @@ class ContributorController extends BaseController return redirect()->route('contributor-list', [$this->podcast->id])->with( 'message', - lang('Contributor.messages.editSuccess') + lang('Contributor.messages.editSuccess'), ); } - public function remove(): string + public function removeView(): string { helper('form'); @@ -199,14 +218,17 @@ class ContributorController extends BaseController 'contributor' => $this->contributor, ]; + $this->setHtmlHead(lang('Contributor.delete_form.title', [ + 'contributor' => $this->contributor->username, + ])); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, 1 => $this->contributor->username, ]); - return view('contributor/delete', $data); + return view('contributor/remove', $data); } - public function attemptRemove(): RedirectResponse + public function removeAction(): RedirectResponse { if ($this->podcast->created_by === $this->contributor->id) { return redirect() diff --git a/modules/Auth/Controllers/InteractController.php b/modules/Auth/Controllers/InteractController.php index b760b9d7..d8281b62 100644 --- a/modules/Auth/Controllers/InteractController.php +++ b/modules/Auth/Controllers/InteractController.php @@ -12,7 +12,7 @@ use CodeIgniter\HTTP\RedirectResponse; */ class InteractController extends Controller { - public function attemptInteractAsActor(): RedirectResponse + public function interactAsActorAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|numeric', diff --git a/modules/Auth/Controllers/LoginController.php b/modules/Auth/Controllers/LoginController.php index 75163cd6..d4d7386e 100644 --- a/modules/Auth/Controllers/LoginController.php +++ b/modules/Auth/Controllers/LoginController.php @@ -7,15 +7,17 @@ namespace Modules\Auth\Controllers; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Controllers\LoginController as ShieldLoginController; +use Override; use Psr\Log\LoggerInterface; use ViewThemes\Theme; class LoginController extends ShieldLoginController { + #[Override] public function initController( RequestInterface $request, ResponseInterface $response, - LoggerInterface $logger + LoggerInterface $logger, ): void { parent::initController($request, $response, $logger); diff --git a/modules/Auth/Controllers/MagicLinkController.php b/modules/Auth/Controllers/MagicLinkController.php index 609aed15..5be3ff3b 100644 --- a/modules/Auth/Controllers/MagicLinkController.php +++ b/modules/Auth/Controllers/MagicLinkController.php @@ -9,6 +9,7 @@ use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Controllers\MagicLinkController as ShieldMagicLinkController; use CodeIgniter\Shield\Entities\User; +use Override; use Psr\Log\LoggerInterface; use ViewThemes\Theme; @@ -19,10 +20,11 @@ use ViewThemes\Theme; */ class MagicLinkController extends ShieldMagicLinkController { + #[Override] public function initController( RequestInterface $request, ResponseInterface $response, - LoggerInterface $logger + LoggerInterface $logger, ): void { parent::initController($request, $response, $logger); diff --git a/modules/Auth/Controllers/MyAccountController.php b/modules/Auth/Controllers/MyAccountController.php index 87cd97b1..844b0112 100644 --- a/modules/Auth/Controllers/MyAccountController.php +++ b/modules/Auth/Controllers/MyAccountController.php @@ -18,6 +18,7 @@ class MyAccountController extends BaseController { public function index(): string { + $this->setHtmlHead(lang('MyAccount.info')); return view('my_account/view'); } @@ -25,10 +26,11 @@ class MyAccountController extends BaseController { helper('form'); + $this->setHtmlHead(lang('MyAccount.changePassword')); return view('my_account/change_password'); } - public function attemptChange(): RedirectResponse + public function changeAction(): RedirectResponse { $rules = [ 'password' => 'required', diff --git a/modules/Auth/Controllers/RegisterController.php b/modules/Auth/Controllers/RegisterController.php index 6db353de..4a093f95 100644 --- a/modules/Auth/Controllers/RegisterController.php +++ b/modules/Auth/Controllers/RegisterController.php @@ -7,6 +7,7 @@ namespace Modules\Auth\Controllers; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Controllers\RegisterController as ShieldRegisterController; +use Override; use Psr\Log\LoggerInterface; use ViewThemes\Theme; @@ -15,10 +16,11 @@ use ViewThemes\Theme; */ class RegisterController extends ShieldRegisterController { + #[Override] public function initController( RequestInterface $request, ResponseInterface $response, - LoggerInterface $logger + LoggerInterface $logger, ): void { parent::initController($request, $response, $logger); diff --git a/modules/Auth/Controllers/UserController.php b/modules/Auth/Controllers/UserController.php index 98a07f3f..a0a5859e 100644 --- a/modules/Auth/Controllers/UserController.php +++ b/modules/Auth/Controllers/UserController.php @@ -29,7 +29,7 @@ class UserController extends BaseController return $this->{$method}(); } - if (($this->user = (new UserModel())->find($params[0])) instanceof User) { + if (($this->user = new UserModel()->find($params[0])) instanceof User) { return $this->{$method}(); } @@ -39,9 +39,11 @@ class UserController extends BaseController public function list(): string { $data = [ - 'users' => (new UserModel())->findAll(), + 'users' => new UserModel() + ->findAll(), ]; + $this->setHtmlHead(lang('User.all_users')); return view('user/list', $data); } @@ -51,13 +53,16 @@ class UserController extends BaseController 'user' => $this->user, ]; + $this->setHtmlHead(lang('User.view', [ + 'username' => esc($this->user->username), + ])); replace_breadcrumb_params([ 0 => $this->user->username, ]); return view('user/view', $data); } - public function create(): string + public function createView(): string { helper('form'); @@ -66,7 +71,10 @@ class UserController extends BaseController array_walk( $roles, static function (array $role, $key) use (&$roleOptions): array { - $roleOptions[$key] = $role['title']; + $roleOptions[] = [ + 'value' => $key, + 'label' => $role['title'], + ]; return $roleOptions; }, [], @@ -76,6 +84,7 @@ class UserController extends BaseController 'roleOptions' => $roleOptions, ]; + $this->setHtmlHead(lang('User.create')); return view('user/create', $data); } @@ -83,7 +92,7 @@ class UserController extends BaseController * Create the user with the provided username and email. The password is set as a random string and a magic link is * sent to the user to allow them setting their password. */ - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { helper(['text', 'email']); @@ -162,7 +171,7 @@ class UserController extends BaseController ])); } - public function edit(): string + public function editView(): string { helper('form'); @@ -171,7 +180,10 @@ class UserController extends BaseController array_walk( $roles, static function (array $role, $key) use (&$roleOptions): array { - $roleOptions[$key] = $role['title']; + $roleOptions[] = [ + 'value' => $key, + 'label' => $role['title'], + ]; return $roleOptions; }, [], @@ -182,13 +194,16 @@ class UserController extends BaseController 'roleOptions' => $roleOptions, ]; + $this->setHtmlHead(lang('User.edit_role', [ + 'username' => esc($this->user->username), + ])); replace_breadcrumb_params([ 0 => $this->user->username, ]); return view('user/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(): RedirectResponse { // The instance owner is a superadmin and the only user that cannot be demoted. if ((bool) $this->user->is_owner) { @@ -213,7 +228,7 @@ class UserController extends BaseController ])); } - public function delete(): string + public function deleteView(): string { helper(['form']); @@ -221,13 +236,16 @@ class UserController extends BaseController 'user' => $this->user, ]; + $this->setHtmlHead(lang('User.delete_form.title', [ + 'user' => $this->user->username, + ])); replace_breadcrumb_params([ 0 => $this->user->username, ]); return view('user/delete', $data); } - public function attemptDelete(): RedirectResponse + public function deleteAction(): RedirectResponse { // You cannot delete the instance owner. if ((bool) $this->user->is_owner) { @@ -263,7 +281,8 @@ class UserController extends BaseController ->with('errors', $this->validator->getErrors()); } - (new UserModel())->delete($this->user->id, true); + new UserModel() + ->delete($this->user->id, true); return redirect() ->route('user-list') diff --git a/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php b/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php index 0e12311f..7f0badee 100644 --- a/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php +++ b/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php @@ -4,8 +4,11 @@ declare(strict_types=1); namespace App\Database\Migrations; +use Override; + class AddIsOwnerToUsers extends BaseMigration { + #[Override] public function up(): void { $fields = [ @@ -20,6 +23,7 @@ class AddIsOwnerToUsers extends BaseMigration $this->forge->addColumn('users', $fields); } + #[Override] public function down(): void { $fields = ['is_owner']; diff --git a/modules/Auth/Filters/PermissionFilter.php b/modules/Auth/Filters/PermissionFilter.php index 6807b92c..982e9f64 100644 --- a/modules/Auth/Filters/PermissionFilter.php +++ b/modules/Auth/Filters/PermissionFilter.php @@ -9,6 +9,7 @@ use App\Models\PodcastModel; use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use Override; use RuntimeException; /** @@ -19,12 +20,13 @@ class PermissionFilter implements FilterInterface /** * @param string[]|null $arguments * - * @return RequestInterface|ResponseInterface|string|void + * @return RequestInterface|ResponseInterface|string|null */ + #[Override] public function before(RequestInterface $request, $arguments = null) { if ($arguments === null || $arguments === []) { - return; + return null; } if (! auth()->loggedIn()) { @@ -32,7 +34,7 @@ class PermissionFilter implements FilterInterface } if ($this->isAuthorized($arguments)) { - return; + return null; } throw new RuntimeException(lang('Auth.notEnoughPrivilege'), 403); @@ -40,10 +42,13 @@ class PermissionFilter implements FilterInterface /** * @param string[]|null $arguments - * @return ResponseInterface|void + * + * @return ResponseInterface|null */ + #[Override] public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { + return null; } /** @@ -58,23 +63,38 @@ class PermissionFilter implements FilterInterface foreach ($arguments as $permission) { // is permission specific to a podcast? - if (str_contains($permission, '#')) { + if (str_contains($permission, '$')) { $router = service('router'); $routerParams = $router->params(); + if (! preg_match('/\$(\d+)\./', $permission, $match)) { + throw new RuntimeException(sprintf( + 'Could not get podcast identifier from permission %s', + $permission, + ), 1); + } + + $paramKey = ((int) $match[1]) - 1; + if (! array_key_exists($paramKey, $routerParams)) { + throw new RuntimeException(sprintf('Router param does not exist at key %s', $match[1]), 1); + } + + $podcastParam = $routerParams[$paramKey]; + // get podcast id $podcastId = null; - if (is_numeric($routerParams[0])) { - $podcastId = (int) $routerParams[0]; + if (is_numeric($podcastParam)) { + $podcastId = (int) $podcastParam; } else { - $podcast = (new PodcastModel())->getPodcastByHandle($routerParams[0]); + $podcast = new PodcastModel() + ->getPodcastByHandle($podcastParam); if ($podcast instanceof Podcast) { $podcastId = $podcast->id; } } if ($podcastId !== null) { - $permission = str_replace('#', '#' . $podcastId, $permission); + $permission = str_replace('$' . $match[1], '#' . $podcastId, $permission); } } diff --git a/modules/Auth/Helpers/auth_helper.php b/modules/Auth/Helpers/auth_helper.php index 93e2032c..36b6a075 100644 --- a/modules/Auth/Helpers/auth_helper.php +++ b/modules/Auth/Helpers/auth_helper.php @@ -104,7 +104,7 @@ if (! function_exists('get_instance_group')) { { $instanceGroups = array_filter( $user->getGroups() ?? [], - static fn ($group): bool => ! str_starts_with((string) $group, 'podcast#') + static fn ($group): bool => ! str_starts_with((string) $group, 'podcast#'), ); if ($instanceGroups === []) { @@ -141,7 +141,7 @@ if (! function_exists('get_podcast_group')) { { $podcastGroups = array_filter( $user->getGroups() ?? [], - static fn ($group): bool => str_starts_with((string) $group, "podcast#{$podcastId}-") + static fn ($group): bool => str_starts_with((string) $group, "podcast#{$podcastId}-"), ); if ($podcastGroups === []) { @@ -184,7 +184,7 @@ if (! function_exists('get_podcast_groups')) { { $podcastGroups = array_filter( $user->getGroups() ?? [], - static fn ($group): bool => str_starts_with((string) $group, 'podcast#') + static fn ($group): bool => str_starts_with((string) $group, 'podcast#'), ); $userPodcastIds = []; @@ -214,7 +214,8 @@ if (! function_exists('get_user_podcasts')) { */ function get_user_podcasts(User $user): array { - return (new PodcastModel())->getUserPodcasts($user->id, get_user_podcast_ids($user)); + return new PodcastModel() + ->getUserPodcasts($user->id, get_user_podcast_ids($user)); } } @@ -224,7 +225,8 @@ if (! function_exists('get_podcasts_user_can_interact_with')) { */ function get_podcasts_user_can_interact_with(User $user): array { - $userPodcasts = (new PodcastModel())->getUserPodcasts($user->id, get_user_podcast_ids($user)); + $userPodcasts = new PodcastModel() + ->getUserPodcasts($user->id, get_user_podcast_ids($user)); $hasInteractAsPrivilege = interact_as_actor_id() === null; @@ -279,10 +281,8 @@ if (! function_exists('get_actor_ids_with_unread_notifications')) { return []; } - $unreadNotifications = (new NotificationModel())->whereIn( - 'target_actor_id', - array_column($userPodcasts, 'actor_id') - ) + $unreadNotifications = new NotificationModel() + ->whereIn('target_actor_id', array_column($userPodcasts, 'actor_id')) ->where('read_at') ->findAll(); diff --git a/modules/Auth/Language/.rsync-filter b/modules/Auth/Language/.rsync-filter deleted file mode 100644 index 38526af5..00000000 --- a/modules/Auth/Language/.rsync-filter +++ /dev/null @@ -1,14 +0,0 @@ -+ br/*** -+ ca/*** -+ cs/*** -+ de/*** -+ en/*** -+ es/*** -+ fr/*** -+ lt/*** -+ nn-no/*** -+ pl/*** -+ pt-br/*** -+ sr-latn/*** -+ zh-hans/*** -- ** diff --git a/modules/Auth/Language/cs/Auth.php b/modules/Auth/Language/cs/Auth.php deleted file mode 100644 index 8d7f2164..00000000 --- a/modules/Auth/Language/cs/Auth.php +++ /dev/null @@ -1,95 +0,0 @@ - [ - 'owner' => [ - 'title' => 'Vlastník instance', - 'description' => 'Vlastník Castopodu.', - ], - 'superadmin' => [ - 'title' => 'Super Admin', - 'description' => 'Má úplnou kontrolu nad Castopodem.', - ], - 'manager' => [ - 'title' => 'Manažer', - 'description' => 'Spravuje obsah Castopodu.', - ], - 'podcaster' => [ - 'title' => 'Podcaster', - 'description' => 'Obecní uživatelé Castopod.', - ], - ], - 'instance_permissions' => [ - 'admin.access' => 'Může se dostat do oblasti administrace Castopod.', - 'admin.settings' => 'Může přistupovat k nastavení Castopodu.', - 'users.manage' => 'Může spravovat uživatele Castopod.', - 'persons.manage' => 'Může řídit osoby.', - 'pages.manage' => 'Může spravovat stránky.', - 'podcasts.view' => 'Může zobrazit všechny podcasty.', - 'podcasts.create' => 'Může vytvářet nové podcasty.', - 'podcasts.import' => 'Může importovat podcasty.', - 'fediverse.manage-blocks' => 'Může blokovat fediverse aktéry/domény a jejch interakce s Castopodem.', - ], - 'podcast_groups' => [ - 'owner' => [ - 'title' => 'Vlastník podcastu', - 'description' => 'Vlastník podcastu.', - ], - 'admin' => [ - 'title' => 'Admin', - 'description' => 'Má úplnou kontrolu nad podcastem #{id}.', - ], - 'editor' => [ - 'title' => 'Editor', - 'description' => 'Spravuje obsah a publikace podcastu #{id}.', - ], - 'author' => [ - 'title' => 'Autor', - 'description' => 'Spravuje obsah kanálu #{id} , ale nemůže jej publikovat.', - ], - 'guest' => [ - 'title' => 'Návštěvník', - 'description' => 'Obecný přispěvatel podcastu #{id}.', - ], - ], - 'podcast_permissions' => [ - 'view' => 'Může prohlížet dashboard a analyzovat podcast #{id}.', - 'edit' => 'Může upravovat podcast #{id}.', - 'delete' => 'Může odstranit podcast #{id}.', - 'manage-import' => 'Může synchronizovat importovaný podcast #{id}.', - 'manage-persons' => 'Může spravovat odběry podcastu #{id}.', - 'manage-subscriptions' => 'Může spravovat odběry podcastu #{id}.', - 'manage-contributors' => 'Může spravovat přispěvatele podcastu #{id}.', - 'manage-platforms' => 'Může nastavit/odebrat odkazy na platformu podcastu #{id}.', - 'manage-publications' => 'Může publikovat podcast #{id}.', - 'manage-notifications' => 'Může zobrazovat a označovat oznámení jako přečtená pro podcast #{id}.', - 'interact-as' => 'Může komunikovat jako podcast #{id}, označovat jako oblíbené, sdílet nebo odpovědět na příspěvky.', - 'episodes' => [ - 'view' => 'Může prohlížet nástěnky a analyzovat epizody podcastu #{id}.', - 'create' => 'Může vytvářet epizody pro podcast #{id}.', - 'edit' => 'Může upravovat epizody vysílání #{id}.', - 'delete' => 'Může odstranit epizody podcastu #{id}.', - 'manage-persons' => 'Může spravovat osoby u epizod z podcastu #{id}.', - 'manage-clips' => 'Může spravovat videoklipy nebo úryvky podcastu #{id}.', - 'manage-publications' => 'Může publikovat/zrušit publikování epizod a příspěvků podcastu #{id}.', - 'manage-comments' => 'Může vytvářet nebo odebrat komentáře epizod podcastu #{id}.', - ], - ], - - // missing keys - 'code' => 'Váš 6-místný kód', - - 'set_password' => 'Natavte si heslo', - - // Welcome email - 'welcomeSubject' => 'Byli jste pozváni do {siteName}', - 'emailWelcomeMailBody' => 'Na {domain} byl pro Vás vytvořen účet, klikněte na přihlášení níže pro nastavení hesla. Odkaz je platný po {numberOfHours} hodin po zaslání tohoto e-mailu.', -]; diff --git a/modules/Auth/Language/cs/Contributor.php b/modules/Auth/Language/cs/Contributor.php deleted file mode 100644 index 2ee57044..00000000 --- a/modules/Auth/Language/cs/Contributor.php +++ /dev/null @@ -1,47 +0,0 @@ - 'Přispěvatelé podcastu', - 'view' => "Příspěvek {username} do {podcastTitle}", - 'add' => 'Přidat přispěvatele', - 'add_contributor' => 'Přidat přispěvatele pro {0}', - 'edit_role' => 'Aktualizovat roli pro {0}', - 'edit' => 'Upravit', - 'remove' => 'Odstranit', - 'list' => [ - 'username' => 'Uživatelské jméno', - 'role' => 'Role', - ], - 'form' => [ - 'user' => 'Uživatel', - 'user_placeholder' => 'Vyberte uživatele…', - 'role' => 'Role', - 'role_placeholder' => 'Vybrat roli…', - 'submit_add' => 'Přidat přispěvatele', - 'submit_edit' => 'Aktualizovat roli', - ], - 'delete_form' => [ - 'title' => 'Odebrat {contributor}', - 'disclaimer' => - 'Chystáte se odebrat {contributor} z přispěvatelů. Už nebudou mít přístup k "{podcastTitle}".', - 'understand' => 'Chápu, chci odstranit {contributor} z "{podcastTitle}"', - 'submit' => 'Odstranit', - ], - 'messages' => [ - 'editSuccess' => 'Role úspěšně změněna!', - 'editOwnerError' => "Vlastníka podcastu nelze upravovat!", - 'removeOwnerError' => "Vlastníka podcastu nelze odstranit!", - 'removeSuccess' => - 'Úspěšně jste odstranili {username} z {podcastTitle}', - 'alreadyAddedError' => - "Přispěvatel, který se pokoušíte přidat, již byl přidán!", - ], -]; diff --git a/modules/Auth/Language/cs/MyAccount.php b/modules/Auth/Language/cs/MyAccount.php deleted file mode 100644 index d6431fde..00000000 --- a/modules/Auth/Language/cs/MyAccount.php +++ /dev/null @@ -1,18 +0,0 @@ - 'Informace o mém účtu', - 'changePassword' => 'Změnit heslo', - 'messages' => [ - 'wrongPasswordError' => "Zadali jste špatné heslo, zkuste to znovu.", - 'passwordChangeSuccess' => 'Heslo bylo úspěšně změněno', - ], -]; diff --git a/modules/Auth/Language/cs/User.php b/modules/Auth/Language/cs/User.php deleted file mode 100644 index 4a624ebb..00000000 --- a/modules/Auth/Language/cs/User.php +++ /dev/null @@ -1,60 +0,0 @@ - "Upravit roli {username}", - 'ban' => 'Zabanovat', - 'unban' => 'Odbanovat', - 'delete' => 'Smazat', - 'create' => 'Nový uživatel', - 'view' => "Informace o {username}", - 'all_users' => 'Všichni uživatelé', - 'list' => [ - 'user' => 'Uživatel', - 'role' => 'Role', - 'banned' => 'Zabanován?', - ], - 'form' => [ - 'email' => 'E-mail', - 'username' => 'Uživatelské jméno', - 'password' => 'Heslo', - 'new_password' => 'Nové heslo', - 'role' => 'Role', - 'roles' => 'Role', - 'permissions' => 'Oprávnění', - 'submit_create' => 'Vytvořit uživatele', - 'submit_edit' => 'Uložit', - 'submit_password_change' => 'Změnit!', - ], - 'delete_form' => [ - 'title' => 'Odstranit {user}', - 'disclaimer' => - "Chystáte se odstranit {user} trvale. Už nebude mít přístup do administrátorské oblasti.", - 'understand' => 'Chápu, chci trvale odstranit {user}', - 'submit' => 'Smazat', - ], - 'messages' => [ - 'createSuccess' => - 'Uživatel byl úspěšně vytvořen! Uvítací e-mail byl odeslán {username} s odkazem pro přihlášení, bude požádán o obnovení hesla při prvním ověření.', - 'roleEditSuccess' => - "Role {username} byly úspěšně aktualizovány.", - 'banSuccess' => 'Uživatel {username} byl zabanován.', - 'unbanSuccess' => 'Uživatel {username} byl odblokován.', - 'editOwnerError' => - '{username} je vlastníkem instance, to přece nejde…', - 'banSuperAdminError' => - '{username} je superadmin, to se nesmí…', - 'deleteOwnerError' => - '{username} je vlastníkem instance, člověk prostě neodstraní vlastníka…', - 'deleteSuperAdminError' => - '{username} je superadmin, to neni dobrý nápad…', - 'deleteSuccess' => '{username} byl smazán.', - ], -]; diff --git a/modules/Auth/Language/lt/Auth.php b/modules/Auth/Language/lt/Auth.php deleted file mode 100644 index 6676f7c0..00000000 --- a/modules/Auth/Language/lt/Auth.php +++ /dev/null @@ -1,95 +0,0 @@ - [ - 'owner' => [ - 'title' => 'Serverio savininkas', - 'description' => '„Castopod“ serverio savininkas.', - ], - 'superadmin' => [ - 'title' => 'Superadministratorius', - 'description' => 'Turi visas įmanomas teises „Castopod“ serveryje.', - ], - 'manager' => [ - 'title' => 'Tvarkytojas', - 'description' => 'Tvarko „Castopod“ turinį.', - ], - 'podcaster' => [ - 'title' => 'Tinklalaidininkas', - 'description' => 'Įprastas „Castopod“ naudotojas.', - ], - ], - 'instance_permissions' => [ - 'admin.access' => 'Turi prieigą prie „Castopod“ administravimo aplinkos.', - 'admin.settings' => 'Turi prieigą prie „Castopod“ nuostatų.', - 'users.manage' => 'Gali tvarkyti „Castopod“ naudotojus.', - 'persons.manage' => 'Gali tvarkyti asmenis.', - 'pages.manage' => 'Gali tvarkyti tinklalapius.', - 'podcasts.view' => 'Gali peržiūrėti visas tinklalaides.', - 'podcasts.create' => 'Gali kurti naujas tinklalaides.', - 'podcasts.import' => 'Gali importuoti tinklalaides.', - 'fediverse.manage-blocks' => 'Gali blokuoti Fedivisatos paskyrų ir domenų sąveikavimą su „Castopod“.', - ], - 'podcast_groups' => [ - 'owner' => [ - 'title' => 'Tinklalaidės savininkas', - 'description' => 'Tinklalaidės savininkas.', - ], - 'admin' => [ - 'title' => 'Administratorius', - 'description' => 'Visapusiškai valdo tinklalaidę #{id}.', - ], - 'editor' => [ - 'title' => 'Redaktorius', - 'description' => 'Tvarko tinklalaidės #{id} turinį ir publikacijas.', - ], - 'author' => [ - 'title' => 'Autorius', - 'description' => 'Tvarko tinklalaidės #{id} turinį, bet negali jo skelbti.', - ], - 'guest' => [ - 'title' => 'Svečias', - 'description' => 'Paprastas tinklalaidės #{id} talkininkas.', - ], - ], - 'podcast_permissions' => [ - 'view' => 'Gali matyti tinklalaidės #{id} skydelį ir analitiką.', - 'edit' => 'Gali taisyti tinklalaidę #{id}.', - 'delete' => 'Gali pašalinti tinklalaidę #{id}.', - 'manage-import' => 'Gali sinchronizuoti importuojamą tinklalaidę #{id}.', - 'manage-persons' => 'Gali tvarkyti tinklalaidės #{id} prenumeratas.', - 'manage-subscriptions' => 'Gali tvarkyti tinklalaidės #{id} prenumeratas.', - 'manage-contributors' => 'Gali tvarkyti tinklalaidės #{id} talkininkus.', - 'manage-platforms' => 'Gali pridėti ir šalinti tinklalaidės #{id} platformų nuorodas.', - 'manage-publications' => 'Gali skelbti tinklalaidę #{id}.', - 'manage-notifications' => 'Gali matyti ir žymėti kaip matytus tinklalaidės #{id} pranešimus.', - 'interact-as' => 'Gali tinklalaidės #{id} vardu žymėti įrašus kaip patinkančius, dalintis jais ir rašyti į juos atsakymus.', - 'episodes' => [ - 'view' => 'Gali matyti tinklalaidės #{id} epizodų skydelius ir analitiką.', - 'create' => 'Gali kurti tinklalaidės #{id} epizodus.', - 'edit' => 'Gali taisyti tinklalaidės #{id} epizodus.', - 'delete' => 'Gali šalinti tinklalaidės #{id} epizodus.', - 'manage-persons' => 'Gali tvarkyti tinklalaidės #{id} epizodų asmenų sąrašus.', - 'manage-clips' => 'Gali tvarkyti tinklalaidės #{id} epizodų vaizdo įrašus ir garso ištraukas.', - 'manage-publications' => 'Gali skelbti tinklalaidės #{id} epizodus ir įrašus arba nutraukti jų skelbimą.', - 'manage-comments' => 'Gali rašyti ir šalinti tinklalaidės #{id} epizodų komentarus.', - ], - ], - - // missing keys - 'code' => 'Jūsų 6 skaitmenų kodas', - - 'set_password' => 'Nustatykite savo slaptažodį', - - // Welcome email - 'welcomeSubject' => 'Jus pakvietė į „{siteName}“', - 'emailWelcomeMailBody' => 'Serveryje {domain} jums sukurta naudotojo paskyra. Spustelėkite žemiau esančią nuorodą ir nustatykite paskyros slaptažodį. Ši nuoroda galioja {numberOfHours} val. nuo laiško išsiuntimo momento.', -]; diff --git a/modules/Auth/Language/lt/Contributor.php b/modules/Auth/Language/lt/Contributor.php deleted file mode 100644 index d17c7c0c..00000000 --- a/modules/Auth/Language/lt/Contributor.php +++ /dev/null @@ -1,47 +0,0 @@ - 'Tinklalaidės talkininkai', - 'view' => "{username} indėlis į „{podcastTitle}“", - 'add' => 'Pridėti talkininką', - 'add_contributor' => 'Pridėti „{0}“ talkininką', - 'edit_role' => 'Atnaujinti {0} rolę', - 'edit' => 'Taisyti', - 'remove' => 'Šalinti', - 'list' => [ - 'username' => 'Naudotojo vardas', - 'role' => 'Rolė', - ], - 'form' => [ - 'user' => 'Naudotojas', - 'user_placeholder' => 'Pasirinkite naudotoją…', - 'role' => 'Rolė', - 'role_placeholder' => 'Pasirinkite rolę…', - 'submit_add' => 'Pridėti talkininką', - 'submit_edit' => 'Atnaujinti rolę', - ], - 'delete_form' => [ - 'title' => 'Pašalinti {contributor}', - 'disclaimer' => - '{contributor} bus pašalinta(s) iš talkininkų sąrašo. Šis asmuo nebegalės pasiekti tinklalaidės „{podcastTitle}“.', - 'understand' => 'Suprantu, noriu pašalinti {contributor} iš „{podcastTitle}“', - 'submit' => 'Šalinti', - ], - 'messages' => [ - 'editSuccess' => 'Rolė sėkmingai pakeista!', - 'editOwnerError' => "Tinklalaidės savininko taisyti negalima!", - 'removeOwnerError' => "Tinklalaidės savininko pašalinti negalima!", - 'removeSuccess' => - '{username} sėkmingai pašalinta(s) iš „{podcastTitle}“', - 'alreadyAddedError' => - "Bandomas pridėti talkininkas jau ir taip pridėtas!", - ], -]; diff --git a/modules/Auth/Language/lt/MyAccount.php b/modules/Auth/Language/lt/MyAccount.php deleted file mode 100644 index fefc5fd6..00000000 --- a/modules/Auth/Language/lt/MyAccount.php +++ /dev/null @@ -1,18 +0,0 @@ - 'Mano paskyros duomenys', - 'changePassword' => 'Keisti mano slaptažodį', - 'messages' => [ - 'wrongPasswordError' => "Įvedėte blogą slaptažodį, pabandykite dar kartą.", - 'passwordChangeSuccess' => 'Slaptažodis sėkmingai pakeistas!', - ], -]; diff --git a/modules/Auth/Language/lt/User.php b/modules/Auth/Language/lt/User.php deleted file mode 100644 index ada71e97..00000000 --- a/modules/Auth/Language/lt/User.php +++ /dev/null @@ -1,60 +0,0 @@ - "Keisti {username} roles", - 'ban' => 'Blokuoti', - 'unban' => 'Atblokuoti', - 'delete' => 'Šalinti', - 'create' => 'Naujas naudotojas', - 'view' => "{username} duomenys", - 'all_users' => 'Visi naudotojai', - 'list' => [ - 'user' => 'Naudotojas', - 'role' => 'Rolė', - 'banned' => 'Užblokuotas?', - ], - 'form' => [ - 'email' => 'El. paštas', - 'username' => 'Naudotojo vardas', - 'password' => 'Slaptažodis', - 'new_password' => 'Naujas slaptažodis', - 'role' => 'Rolė', - 'roles' => 'Rolės', - 'permissions' => 'Leidimai', - 'submit_create' => 'Kurti naudotoją', - 'submit_edit' => 'Įrašyti', - 'submit_password_change' => 'Pakeisti!', - ], - 'delete_form' => [ - 'title' => 'Šalinti {user}', - 'disclaimer' => - "Ketinate visam laikui pašalinti {user} paskyrą. Šis asmuo nebegalės pasiekti administratoriaus srities.", - 'understand' => 'Suprantu, noriu pašalinti {user} visam laikui', - 'submit' => 'Šalinti', - ], - 'messages' => [ - 'createSuccess' => - 'Naudotojo paskyra {username} sėkmingai sukurta! Jos savininkui(-ei) nusiųstas el. laiškas su prisijungimo nuoroda. Pirmo prisijungimo metu jis (ji) turės pasikeisti slaptažodį.', - 'roleEditSuccess' => - "{username} rolės sėkmingai atnaujintos.", - 'banSuccess' => 'Paskyra {username} užblokuota.', - 'unbanSuccess' => 'Paskyra {username} atblokuota.', - 'editOwnerError' => - 'Paskyra {username} priklauso šio serverio savininkui, jos keisti negalite.', - 'banSuperAdminError' => - 'Paskyra {username} priklauso superadministratoriui. Jos užblokuoti negalite.', - 'deleteOwnerError' => - 'Paskyra {username} priklauso šio serverio savininkui, jos pašalinti negalite.', - 'deleteSuperAdminError' => - 'Paskyra {username} priklauso superadministratoriui, jos pašalinti negalite.', - 'deleteSuccess' => 'Paskyra {username} pašalinta.', - ], -]; diff --git a/modules/Fediverse/Commands/Broadcast.php b/modules/Fediverse/Commands/Broadcast.php index 6da11f57..3e096946 100644 --- a/modules/Fediverse/Commands/Broadcast.php +++ b/modules/Fediverse/Commands/Broadcast.php @@ -6,6 +6,7 @@ namespace Modules\Fediverse\Commands; use CodeIgniter\CLI\BaseCommand; use Exception; +use Override; class Broadcast extends BaseCommand { @@ -15,6 +16,7 @@ class Broadcast extends BaseCommand protected $description = 'Broadcasts new outgoing activity to followers.'; + #[Override] public function run(array $params): void { helper('fediverse'); @@ -40,7 +42,7 @@ class Broadcast extends BaseCommand send_activity_to_actor( $scheduledActivity->actor, $scheduledActivity->targetActor, - json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR) + json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR), ); } } else { diff --git a/modules/Fediverse/Config/Routes.php b/modules/Fediverse/Config/Routes.php index b4c469fc..1fb7fe0c 100644 --- a/modules/Fediverse/Config/Routes.php +++ b/modules/Fediverse/Config/Routes.php @@ -52,7 +52,7 @@ $routes->group('', [ 'as' => 'followers', 'filter' => 'fediverse::activity-stream', ]); - $routes->post('follow', 'ActorController::attemptFollow/$1', [ + $routes->post('follow', 'ActorController::followAction/$1', [ 'as' => 'attempt-follow', ]); $routes->get('activities/(:uuid)', 'ActorController::activity/$1/$2', [ @@ -60,7 +60,7 @@ $routes->group('', [ ]); }); // Post - $routes->post('posts/create', 'PostController::attemptCreate/$1', [ + $routes->post('posts/create', 'PostController::createAction/$1', [ 'as' => 'post-attempt-create', ]); $routes->get('posts/(:uuid)', 'PostController::index/$1', [ @@ -71,7 +71,7 @@ $routes->group('', [ ]); $routes->post( 'posts/(:uuid)/remote/(:postAction)', - 'PostController::attemptRemoteAction/$1/$2/$3', + 'PostController::remoteActionAction/$1/$2/$3', [ 'as' => 'post-attempt-remote-action', ], @@ -79,28 +79,28 @@ $routes->group('', [ // Blocking actors and domains $routes->post( 'fediverse-block-actor', - 'BlockController::attemptBlockActor', + 'BlockController::blockActorAction', [ 'as' => 'fediverse-attempt-block-actor', ], ); $routes->post( 'fediverse-block-domain', - 'BlockController::attemptBlockDomain', + 'BlockController::blockDomainAction', [ 'as' => 'fediverse-attempt-block-domain', ], ); $routes->post( 'fediverse-unblock-actor', - 'BlockController::attemptUnblockActor', + 'BlockController::unblockActorAction', [ 'as' => 'fediverse-attempt-unblock-actor', ], ); $routes->post( 'fediverse-unblock-domain', - 'BlockController::attemptUnblockDomain', + 'BlockController::unblockDomainAction', [ 'as' => 'fediverse-attempt-unblock-domain', ], diff --git a/modules/Fediverse/Controllers/ActorController.php b/modules/Fediverse/Controllers/ActorController.php index a0321ea2..a567436b 100644 --- a/modules/Fediverse/Controllers/ActorController.php +++ b/modules/Fediverse/Controllers/ActorController.php @@ -20,6 +20,8 @@ use Modules\Fediverse\Config\Fediverse; use Modules\Fediverse\Entities\Activity; use Modules\Fediverse\Entities\Actor; use Modules\Fediverse\Entities\Post; +use Modules\Fediverse\Models\ActivityModel; +use Modules\Fediverse\Models\ActorModel; use Modules\Fediverse\Objects\OrderedCollectionObject; use Modules\Fediverse\Objects\OrderedCollectionPage; @@ -262,6 +264,7 @@ class ActorController extends Controller public function outbox(): ResponseInterface { // get published activities by publication date + /** @var ActivityModel $actorActivity */ $actorActivity = model('ActivityModel', false) ->where('actor_id', $this->actor->id) ->where('`created_at` <= UTC_TIMESTAMP()', null, false) @@ -274,6 +277,7 @@ class ActorController extends Controller $pager = $actorActivity->pager; $collection = new OrderedCollectionObject(null, $pager); } else { + /** @var Activity[] $paginatedActivity */ $paginatedActivity = $actorActivity->paginate(12, 'default', $pageNumber); $pager = $actorActivity->pager; $orderedItems = []; @@ -292,6 +296,7 @@ class ActorController extends Controller public function followers(): ResponseInterface { // get followers for a specific actor + /** @var ActorModel $followers */ $followers = model('ActorModel', false) ->join('fediverse_follows', 'fediverse_follows.actor_id = id', 'inner') ->where('fediverse_follows.target_actor_id', $this->actor->id) @@ -304,6 +309,7 @@ class ActorController extends Controller $pager = $followers->pager; $followersCollection = new OrderedCollectionObject(null, $pager); } else { + /** @var Actor[] $paginatedFollowers */ $paginatedFollowers = $followers->paginate(12, 'default', $pageNumber); $pager = $followers->pager; @@ -320,7 +326,7 @@ class ActorController extends Controller ->setBody($followersCollection->toJSON()); } - public function attemptFollow(): RedirectResponse + public function followAction(): RedirectResponse { $rules = [ 'handle' => 'regex_match[/^@?(?P[\w\.\-]+)@(?P[\w\.\-]+)(?P:[\d]+)?$/]', diff --git a/modules/Fediverse/Controllers/BlockController.php b/modules/Fediverse/Controllers/BlockController.php index c279ce4e..e5304b0b 100644 --- a/modules/Fediverse/Controllers/BlockController.php +++ b/modules/Fediverse/Controllers/BlockController.php @@ -21,7 +21,7 @@ class BlockController extends Controller */ protected $helpers = ['fediverse']; - public function attemptBlockActor(): RedirectResponse + public function blockActorAction(): RedirectResponse { $rules = [ 'handle' => 'required|regex_match[/^@?([\w\.\-]+)@([\w\.\-]+)(:[\d]+)?$/]', @@ -58,7 +58,7 @@ class BlockController extends Controller ])); } - public function attemptUnblockActor(): RedirectResponse + public function unblockActorAction(): RedirectResponse { $rules = [ 'actor_id' => 'required', @@ -80,7 +80,7 @@ class BlockController extends Controller ->with('message', lang('Fediverse.messages.unblockActorSuccess')); } - public function attemptBlockDomain(): RedirectResponse + public function blockDomainAction(): RedirectResponse { $rules = [ 'domain' => 'required|regex_match[/^[\w\-\.]+[\w]+(:[\d]+)?/]', @@ -105,7 +105,7 @@ class BlockController extends Controller ])); } - public function attemptUnblockDomain(): RedirectResponse + public function unblockDomainAction(): RedirectResponse { $rules = [ 'domain' => 'required', diff --git a/modules/Fediverse/Controllers/PostController.php b/modules/Fediverse/Controllers/PostController.php index c67ed66c..8fdd4548 100644 --- a/modules/Fediverse/Controllers/PostController.php +++ b/modules/Fediverse/Controllers/PostController.php @@ -97,11 +97,9 @@ class PostController extends Controller $orderedItems = []; $noteObjectClass = $this->config->noteObject; - if ($paginatedReplies !== null) { - foreach ($paginatedReplies as $reply) { - $replyObject = new $noteObjectClass($reply); - $orderedItems[] = $replyObject->toArray(); - } + foreach ($paginatedReplies as $reply) { + $replyObject = new $noteObjectClass($reply); + $orderedItems[] = $replyObject->toArray(); } $collection = new OrderedCollectionPage($pager, $orderedItems); @@ -112,7 +110,7 @@ class PostController extends Controller ->setBody($collection->toJSON()); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|is_natural_no_zero', @@ -147,7 +145,7 @@ class PostController extends Controller return redirect()->back(); } - public function attemptFavourite(): RedirectResponse + public function favouriteAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|is_natural_no_zero', @@ -171,7 +169,7 @@ class PostController extends Controller return redirect()->back(); } - public function attemptReblog(): RedirectResponse + public function reblogAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|is_natural_no_zero', @@ -195,7 +193,7 @@ class PostController extends Controller return redirect()->back(); } - public function attemptReply(): RedirectResponse + public function replyAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|is_natural_no_zero', @@ -230,7 +228,7 @@ class PostController extends Controller return redirect()->back(); } - public function attemptRemoteAction(string $action): RedirectResponse | ResponseInterface + public function remoteActionAction(string $action): RedirectResponse | ResponseInterface { $rules = [ 'handle' => 'regex_match[/^@?(?P[\w\.\-]+)@(?P[\w\.\-]+)(?P:[\d]+)?$/]', @@ -277,21 +275,21 @@ class PostController extends Controller ); } - public function attemptBlockActor(): RedirectResponse + public function blockActorAction(): RedirectResponse { model('ActorModel', false)->blockActor($this->post->actor->id); return redirect()->back(); } - public function attemptBlockDomain(): RedirectResponse + public function blockDomainAction(): RedirectResponse { model('BlockedDomainModel', false)->blockDomain($this->post->actor->domain); return redirect()->back(); } - public function attemptDelete(): RedirectResponse + public function deleteAction(): RedirectResponse { model('PostModel', false)->removePost($this->post); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php b/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php index ddf5228c..dc323b9d 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddActors extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -116,6 +118,7 @@ class AddActors extends BaseMigration $this->forge->createTable('fediverse_actors'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_actors'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php b/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php index 903f8aec..c6bffce5 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddPosts extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -85,6 +87,7 @@ class AddPosts extends BaseMigration $this->forge->createTable('fediverse_posts'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_posts'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php index 18b94cdf..b497009b 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddActivities extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -65,6 +67,7 @@ class AddActivities extends BaseMigration $this->forge->createTable('fediverse_activities'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_activities'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php index 75ec8f21..be9b5755 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddFavourites extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -36,6 +38,7 @@ class AddFavourites extends BaseMigration $this->forge->createTable('fediverse_favourites'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_favourites'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php index 0b0273ba..945933f4 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddFollowers extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -38,6 +40,7 @@ class AddFollowers extends BaseMigration $this->forge->createTable('fediverse_follows'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_follows'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php index f0359070..479d7102 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddPreviewCards extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -78,6 +80,7 @@ class AddPreviewCards extends BaseMigration $this->forge->createTable('fediverse_preview_cards'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_preview_cards'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php b/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php index 2bd9d9ba..3d8d4d70 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddPostsPreviewCards extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -35,6 +37,7 @@ class AddPostsPreviewCards extends BaseMigration $this->forge->createTable('fediverse_posts_preview_cards'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_posts_preview_cards'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php b/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php index 044a0e0b..d17079a1 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddBlockedDomains extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -31,6 +33,7 @@ class AddBlockedDomains extends BaseMigration $this->forge->createTable('fediverse_blocked_domains'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_blocked_domains'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php b/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php index df979192..a1764482 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php @@ -12,8 +12,11 @@ declare(strict_types=1); namespace App\Database\Migrations; +use Override; + class AddNotifications extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -63,6 +66,7 @@ class AddNotifications extends BaseMigration $this->forge->createTable('fediverse_notifications'); } + #[Override] public function down(): void { $this->forge->dropTable('fediverse_notifications'); diff --git a/modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status copy.php b/modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status.php similarity index 97% rename from modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status copy.php rename to modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status.php index 4ff7f175..2d7adac0 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status copy.php +++ b/modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class UpdateActivitiesStatus extends BaseMigration { + #[Override] public function up(): void { $fields = [ diff --git a/modules/Fediverse/Entities/Notification.php b/modules/Fediverse/Entities/Notification.php index 869f1bea..0815c273 100644 --- a/modules/Fediverse/Entities/Notification.php +++ b/modules/Fediverse/Entities/Notification.php @@ -66,7 +66,8 @@ class Notification extends UuidEntity public function getActor(): ?Actor { if (! $this->actor instanceof Actor) { - $this->actor = (new ActorModel())->getActorById($this->actor_id); + $this->actor = new ActorModel() + ->getActorById($this->actor_id); } return $this->actor; @@ -75,7 +76,8 @@ class Notification extends UuidEntity public function getTargetActor(): ?Actor { if (! $this->target_actor instanceof Actor) { - $this->target_actor = (new ActorModel())->getActorById($this->target_actor_id); + $this->target_actor = new ActorModel() + ->getActorById($this->target_actor_id); } return $this->target_actor; @@ -88,7 +90,8 @@ class Notification extends UuidEntity } if (! $this->post instanceof Post) { - $this->post = (new PostModel())->getPostById($this->post_id); + $this->post = new PostModel() + ->getPostById($this->post_id); } return $this->post; diff --git a/modules/Fediverse/Filters/FediverseFilter.php b/modules/Fediverse/Filters/FediverseFilter.php index eec32679..2e9b32ba 100644 --- a/modules/Fediverse/Filters/FediverseFilter.php +++ b/modules/Fediverse/Filters/FediverseFilter.php @@ -11,22 +11,20 @@ use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\URI; use Exception; use Modules\Fediverse\HttpSignature; +use Override; class FediverseFilter implements FilterInterface { /** - * Do whatever processing this filter needs to do. By default it should not return anything during normal execution. - * However, when an abnormal state is found, it should return an instance of CodeIgniter\HTTP\Response. If it does, - * script execution will end and that Response will be sent back to the client, allowing for error pages, redirects, - * etc. + * @param string[]|null $params * - * @param string[]|null $params - * @return RequestInterface|ResponseInterface|string|void + * @return RequestInterface|ResponseInterface|string|null */ + #[Override] public function before(RequestInterface $request, $params = null) { if ($params === null) { - return; + return null; } if (in_array('verify-activitystream', $params, true)) { @@ -47,7 +45,8 @@ class FediverseFilter implements FilterInterface $payload = $request->getJSON(); $actorUri = $payload->actor; - $domain = (new URI($actorUri))->getHost(); + $domain = new URI($actorUri) + ->getHost(); // check first if domain is blocked if (model('BlockedDomainModel', false)->isDomainBlocked($domain)) { @@ -65,27 +64,26 @@ class FediverseFilter implements FilterInterface if (in_array('verify-signature', $params, true)) { try { // securityCheck: check activity signature before handling it - (new HttpSignature())->verify(); + new HttpSignature() + ->verify(); } catch (Exception) { // Invalid HttpSignature (401 = unauthorized) // TODO: show error message? return service('response')->setStatusCode(401); } } - } - //-------------------------------------------------------------------- + return null; + } /** - * Allows After filters to inspect and modify the response object as needed. This method does not allow any way to - * stop execution of other after filters, short of throwing an Exception or Error. + * @param string[]|null $arguments * - * @param string[]|null $arguments - * @return ResponseInterface|void + * @return ResponseInterface|null */ + #[Override] public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { + return null; } - - //-------------------------------------------------------------------- } diff --git a/modules/Fediverse/Helpers/fediverse_helper.php b/modules/Fediverse/Helpers/fediverse_helper.php index c842d8e5..391842a8 100644 --- a/modules/Fediverse/Helpers/fediverse_helper.php +++ b/modules/Fediverse/Helpers/fediverse_helper.php @@ -163,7 +163,7 @@ if (! function_exists('create_preview_card_from_url')) { if ($mediaData !== []) { $mediaUrl = array_key_first($mediaData); - $media = array_values($mediaData)[0]; + $media = array_first($mediaData); if (array_key_exists('title', $media)) { $typeMapping = [ diff --git a/modules/Fediverse/HttpSignature.php b/modules/Fediverse/HttpSignature.php index ac0791e4..145f0848 100644 --- a/modules/Fediverse/HttpSignature.php +++ b/modules/Fediverse/HttpSignature.php @@ -24,10 +24,7 @@ use phpseclib\Crypt\RSA; */ class HttpSignature { - /** - * @var string - */ - private const SIGNATURE_PATTERN = '/ + private const string SIGNATURE_PATTERN = '/ (?=.*(keyId="(?Phttps?:\/\/[\w\-\.]+[\w]+(:[\d]+)?[\w\-\.#\/@]+)")) (?=.*(signature="(?P[\w+\/]+={0,2})")) (?=.*(headers="\(request-target\)(?P[\w\\-\s]+)"))? @@ -36,7 +33,7 @@ class HttpSignature protected IncomingRequest $request; - public function __construct(IncomingRequest $request = null) + public function __construct(?IncomingRequest $request = null) { if (! $request instanceof IncomingRequest) { $request = service('request'); @@ -164,7 +161,7 @@ class HttpSignature string $publicKeyPem, string $data, string $signature, - string $algorithm = 'rsa-sha256' + string $algorithm = 'rsa-sha256', ): bool { if ($algorithm === 'rsa-sha512' || $algorithm === 'rsa-sha256') { $hash = substr($algorithm, strpos($algorithm, '-') + 1); diff --git a/modules/Fediverse/Models/ActivityModel.php b/modules/Fediverse/Models/ActivityModel.php index 548ee4a4..21cd5538 100644 --- a/modules/Fediverse/Models/ActivityModel.php +++ b/modules/Fediverse/Models/ActivityModel.php @@ -58,7 +58,7 @@ class ActivityModel extends UuidModel ]; /** - * @var string + * @var class-string */ protected $returnType = Activity::class; @@ -100,8 +100,8 @@ class ActivityModel extends UuidModel ?int $targetActorId, ?string $postId, string $payload, - DateTimeInterface $scheduledAt = null, - ?string $taskStatus = null + ?DateTimeInterface $scheduledAt = null, + ?string $taskStatus = null, ): BaseResult | int | string | false { return $this->insert( [ @@ -110,7 +110,7 @@ class ActivityModel extends UuidModel 'post_id' => $postId, 'type' => $type === 'Undo' ? $type . '_' . (json_decode( $payload, - true + true, ))['object']['type'] : $type, 'payload' => $payload, 'scheduled_at' => $scheduledAt, @@ -139,7 +139,8 @@ class ActivityModel extends UuidModel protected function notify(array $data): array { /** @var ?Activity $activity */ - $activity = (new self())->find(is_array($data['id']) ? $data['id'][0] : $data['id']); + $activity = new self() + ->find(is_array($data['id']) ? $data['id'][0] : $data['id']); if (! $activity instanceof Activity) { return $data; @@ -155,35 +156,39 @@ class ActivityModel extends UuidModel } if ($activity->type === 'Follow') { - (new NotificationModel())->insert([ - 'actor_id' => $activity->actor_id, - 'target_actor_id' => $activity->target_actor_id, - 'activity_id' => $activity->id, - 'type' => 'follow', - 'created_at' => $activity->created_at, - ]); + new NotificationModel() + ->insert([ + 'actor_id' => $activity->actor_id, + 'target_actor_id' => $activity->target_actor_id, + 'activity_id' => $activity->id, + 'type' => 'follow', + 'created_at' => $activity->created_at, + ]); } elseif ($activity->type === 'Undo_Follow') { - (new NotificationModel())->builder() + new NotificationModel() + ->builder() ->delete([ 'actor_id' => $activity->actor_id, 'target_actor_id' => $activity->target_actor_id, 'type' => 'follow', ]); } elseif (in_array($activity->type, ['Create', 'Like', 'Announce'], true) && $activity->post_id !== null) { - (new NotificationModel())->insert([ - 'actor_id' => $activity->actor_id, - 'target_actor_id' => $activity->target_actor_id, - 'post_id' => $activity->post_id, - 'activity_id' => $activity->id, - 'type' => match ($activity->type) { - 'Create' => 'reply', - 'Like' => 'like', - 'Announce' => 'share', - }, - 'created_at' => $activity->created_at, - ]); + new NotificationModel() + ->insert([ + 'actor_id' => $activity->actor_id, + 'target_actor_id' => $activity->target_actor_id, + 'post_id' => $activity->post_id, + 'activity_id' => $activity->id, + 'type' => match ($activity->type) { + 'Create' => 'reply', + 'Like' => 'like', + 'Announce' => 'share', + }, + 'created_at' => $activity->created_at, + ]); } elseif (in_array($activity->type, ['Undo_Like', 'Undo_Announce'], true) && $activity->post_id !== null) { - (new NotificationModel())->builder() + new NotificationModel() + ->builder() ->delete([ 'actor_id' => $activity->actor_id, 'target_actor_id' => $activity->target_actor_id, diff --git a/modules/Fediverse/Models/ActorModel.php b/modules/Fediverse/Models/ActorModel.php index 28079a62..c061e11d 100644 --- a/modules/Fediverse/Models/ActorModel.php +++ b/modules/Fediverse/Models/ActorModel.php @@ -46,7 +46,7 @@ class ActorModel extends Model ]; /** - * @var string + * @var class-string */ protected $returnType = Actor::class; @@ -231,24 +231,24 @@ class ActorModel extends Model ->join( $tablePrefix . 'fediverse_posts', $tablePrefix . 'fediverse_actors.id = ' . $tablePrefix . 'fediverse_posts.actor_id', - 'left outer' + 'left outer', ) ->join( $tablePrefix . 'fediverse_favourites', $tablePrefix . 'fediverse_actors.id = ' . $tablePrefix . 'fediverse_favourites.actor_id', - 'left outer' + 'left outer', ) ->where($tablePrefix . 'fediverse_actors.domain', get_current_domain()) ->groupStart() ->where( "`{$tablePrefix}fediverse_posts`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month", null, - false + false, ) ->orWhere( "`{$tablePrefix}fediverse_favourites`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month", null, - false + false, ) ->groupEnd() ->get() diff --git a/modules/Fediverse/Models/BlockedDomainModel.php b/modules/Fediverse/Models/BlockedDomainModel.php index e5444126..3b8acf3f 100644 --- a/modules/Fediverse/Models/BlockedDomainModel.php +++ b/modules/Fediverse/Models/BlockedDomainModel.php @@ -33,7 +33,7 @@ class BlockedDomainModel extends Model protected $allowedFields = ['name']; /** - * @var string + * @var class-string */ protected $returnType = BlockedDomain::class; diff --git a/modules/Fediverse/Models/FavouriteModel.php b/modules/Fediverse/Models/FavouriteModel.php index 315fa8e5..0c1dfc6f 100644 --- a/modules/Fediverse/Models/FavouriteModel.php +++ b/modules/Fediverse/Models/FavouriteModel.php @@ -36,7 +36,7 @@ class FavouriteModel extends UuidModel protected $allowedFields = ['actor_id', 'post_id']; /** - * @var string + * @var class-string */ protected $returnType = Favourite::class; diff --git a/modules/Fediverse/Models/FollowModel.php b/modules/Fediverse/Models/FollowModel.php index 93c6cf55..9541625a 100644 --- a/modules/Fediverse/Models/FollowModel.php +++ b/modules/Fediverse/Models/FollowModel.php @@ -32,7 +32,7 @@ class FollowModel extends Model protected $allowedFields = ['actor_id', 'target_actor_id']; /** - * @var string + * @var class-string */ protected $returnType = Follow::class; diff --git a/modules/Fediverse/Models/NotificationModel.php b/modules/Fediverse/Models/NotificationModel.php index 813313ac..b0993d6e 100644 --- a/modules/Fediverse/Models/NotificationModel.php +++ b/modules/Fediverse/Models/NotificationModel.php @@ -26,7 +26,7 @@ class NotificationModel extends UuidModel protected $primaryKey = 'id'; /** - * @var string + * @var class-string */ protected $returnType = Notification::class; diff --git a/modules/Fediverse/Models/PostModel.php b/modules/Fediverse/Models/PostModel.php index dd4edb19..e24000cc 100644 --- a/modules/Fediverse/Models/PostModel.php +++ b/modules/Fediverse/Models/PostModel.php @@ -60,7 +60,7 @@ class PostModel extends UuidModel ]; /** - * @var string + * @var class-string */ protected $returnType = Post::class; @@ -241,7 +241,7 @@ class PostModel extends UuidModel public function addPost( Post $post, bool $createPreviewCard = true, - bool $registerActivity = true + bool $registerActivity = true, ): bool|int|object|string { helper('fediverse'); @@ -445,7 +445,7 @@ class PostModel extends UuidModel public function addReply( Post $reply, bool $createPreviewCard = true, - bool $registerActivity = true + bool $registerActivity = true, ): string | false { if (! $reply->in_reply_to_id) { throw new Exception('Passed post is not a reply!'); diff --git a/modules/Fediverse/Models/PreviewCardModel.php b/modules/Fediverse/Models/PreviewCardModel.php index 917fe215..b0e64c96 100644 --- a/modules/Fediverse/Models/PreviewCardModel.php +++ b/modules/Fediverse/Models/PreviewCardModel.php @@ -39,7 +39,7 @@ class PreviewCardModel extends Model ]; /** - * @var string + * @var class-string */ protected $returnType = PreviewCard::class; diff --git a/modules/Fediverse/Objects/OrderedCollectionObject.php b/modules/Fediverse/Objects/OrderedCollectionObject.php index 11438361..339ffb03 100644 --- a/modules/Fediverse/Objects/OrderedCollectionObject.php +++ b/modules/Fediverse/Objects/OrderedCollectionObject.php @@ -28,11 +28,11 @@ class OrderedCollectionObject extends ObjectType protected ?string $last = null; /** - * @param ObjectType[]|null $orderedItems + * @param ObjectType[]|list|null $orderedItems */ public function __construct( protected ?array $orderedItems = null, - ?Pager $pager = null + ?Pager $pager = null, ) { $this->id = current_url(); diff --git a/modules/Fediverse/WebFinger.php b/modules/Fediverse/WebFinger.php index d93a48b9..e07f5115 100644 --- a/modules/Fediverse/WebFinger.php +++ b/modules/Fediverse/WebFinger.php @@ -15,10 +15,7 @@ use Modules\Fediverse\Entities\Actor; class WebFinger { - /** - * @var string - */ - private const RESOURCE_PATTERN = '/^acct:(?P([\w_]+))@(?P([\w\-\.]+[\w]+)(:[\d]+)?)$/x'; + private const string RESOURCE_PATTERN = '/^acct:(?P([\w_]+))@(?P([\w\-\.]+[\w]+)(:[\d]+)?)$/x'; protected string $username; @@ -39,7 +36,7 @@ class WebFinger protected array $links = []; public function __construct( - protected string $subject + protected string $subject, ) { // Split resource into its parts (username, domain) $parts = $this->splitResource($subject); diff --git a/modules/Install/Commands/CreateSuperadmin.php b/modules/Install/Commands/CreateSuperadmin.php index 799b36b8..0dfdad71 100644 --- a/modules/Install/Commands/CreateSuperadmin.php +++ b/modules/Install/Commands/CreateSuperadmin.php @@ -9,6 +9,7 @@ use CodeIgniter\Shield\Commands\Exceptions\BadInputException; use CodeIgniter\Shield\Commands\Exceptions\CancelException; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Validation\ValidationRules; +use Override; class CreateSuperadmin extends BaseCommand { @@ -34,6 +35,7 @@ class CreateSuperadmin extends BaseCommand */ private array $validationRules = []; + #[Override] public function run(array $params): void { // first, check that super admin does not exist @@ -72,7 +74,7 @@ class CreateSuperadmin extends BaseCommand $passwordConfirm = $this->prompt( 'Password confirmation', null, - $this->validationRules['password']['rules'] + $this->validationRules['password']['rules'], ); if ($password !== $passwordConfirm) { diff --git a/modules/Install/Commands/InitDatabase.php b/modules/Install/Commands/InitDatabase.php index 07cb87c8..67d8f61b 100644 --- a/modules/Install/Commands/InitDatabase.php +++ b/modules/Install/Commands/InitDatabase.php @@ -6,6 +6,7 @@ namespace Modules\Install\Commands; use CodeIgniter\CLI\BaseCommand; use Config\Database; +use Override; class InitDatabase extends BaseCommand { @@ -24,6 +25,7 @@ class InitDatabase extends BaseCommand */ protected $description = 'Runs all database migrations for Castopod.'; + #[Override] public function run(array $params): void { // Run all migrations diff --git a/modules/Install/Config/Routes.php b/modules/Install/Config/Routes.php index 42aec944..481d01fd 100644 --- a/modules/Install/Config/Routes.php +++ b/modules/Install/Config/Routes.php @@ -19,21 +19,21 @@ $routes->group( $routes->get('/', 'InstallController', [ 'as' => 'install', ]); - $routes->post('instance-config', 'InstallController::attemptInstanceConfig', [ + $routes->post('instance-config', 'InstallController::instanceConfigAction', [ 'as' => 'instance-config', ]); - $routes->post('database-config', 'InstallController::attemptDatabaseConfig', [ + $routes->post('database-config', 'InstallController::databaseConfigAction', [ 'as' => 'database-config', ]); - $routes->post('cache-config', 'InstallController::attemptCacheConfig', [ + $routes->post('cache-config', 'InstallController::cacheConfigAction', [ 'as' => 'cache-config', ]); $routes->post( 'create-superadmin', - 'InstallController::attemptCreateSuperAdmin', + 'InstallController::createSuperAdminAction', [ 'as' => 'create-superadmin', ], ); - } + }, ); diff --git a/modules/Install/Controllers/InstallController.php b/modules/Install/Controllers/InstallController.php index 89edd6ef..f6f875af 100644 --- a/modules/Install/Controllers/InstallController.php +++ b/modules/Install/Controllers/InstallController.php @@ -22,6 +22,7 @@ use Config\Database; use Dotenv\Dotenv; use Dotenv\Exception\ValidationException; use Modules\Auth\Models\UserModel; +use Override; use Psr\Log\LoggerInterface; use Throwable; use ViewThemes\Theme; @@ -33,10 +34,11 @@ class InstallController extends Controller */ protected $helpers = ['form', 'components', 'svg', 'misc', 'setting']; + #[Override] public function initController( RequestInterface $request, ResponseInterface $response, - LoggerInterface $logger + LoggerInterface $logger, ): void { // Do Not Edit This Line parent::initController($request, $response, $logger); @@ -72,7 +74,7 @@ class InstallController extends Controller $dotenv->required(['app.baseURL', 'analytics.salt', 'admin.gateway', 'auth.gateway']); } catch (ValidationException) { // form to input instance configuration - return $this->instanceConfig(); + return $this->instanceConfigView(); } try { @@ -84,13 +86,13 @@ class InstallController extends Controller 'database.default.DBPrefix', ]); } catch (ValidationException) { - return $this->databaseConfig(); + return $this->databaseConfigView(); } try { $dotenv->required('cache.handler'); } catch (ValidationException) { - return $this->cacheConfig(); + return $this->cacheConfigView(); } } else { try { @@ -115,7 +117,7 @@ class InstallController extends Controller $db = db_connect(); // Check if instance owner has been created, meaning install was completed - if ($db->tableExists('users') && (new UserModel())->where('is_owner', true) + if ($db->tableExists('users') && new UserModel()->where('is_owner', true) ->first() instanceof User ) { // if so, show a 404 page @@ -127,7 +129,7 @@ class InstallController extends Controller session() ->setFlashdata('error', lang('Install.messages.databaseConnectError')); - return $this->databaseConfig(); + return $this->databaseConfigView(); } // migrate if no user has been created @@ -136,15 +138,15 @@ class InstallController extends Controller // Check if all seeds have succeeded $this->seed(); - return $this->createSuperAdmin(); + return $this->createSuperAdminView(); } - public function instanceConfig(): string + public function instanceConfigView(): string { return view('instance_config'); } - public function attemptInstanceConfig(): RedirectResponse + public function instanceConfigAction(): RedirectResponse { $rules = [ 'hostname' => 'required|valid_url_strict', @@ -178,12 +180,12 @@ class InstallController extends Controller return redirect()->to(reduce_double_slashes($baseUrl . '/' . config('Install')->gateway)); } - public function databaseConfig(): string + public function databaseConfigView(): string { return view('database_config'); } - public function attemptDatabaseConfig(): RedirectResponse + public function databaseConfigAction(): RedirectResponse { $rules = [ 'db_hostname' => 'required', @@ -212,12 +214,12 @@ class InstallController extends Controller return redirect()->back(); } - public function cacheConfig(): string + public function cacheConfigView(): string { return view('cache_config'); } - public function attemptCacheConfig(): RedirectResponse + public function cacheConfigAction(): RedirectResponse { $rules = [ 'cache_handler' => 'required', @@ -264,7 +266,7 @@ class InstallController extends Controller /** * Returns the form to create a the first superadmin user for the instance. */ - public function createSuperAdmin(): string + public function createSuperAdminView(): string { return view('create_superadmin'); } @@ -274,7 +276,7 @@ class InstallController extends Controller * * After creation, user is redirected to login page to input its credentials. */ - public function attemptCreateSuperAdmin(): RedirectResponse + public function createSuperAdminAction(): RedirectResponse { // validate user password $rules = [ @@ -345,7 +347,7 @@ class InstallController extends Controller return $line; }, - $envData + $envData, ); if (! $replaced) { diff --git a/modules/Install/Language/.rsync-filter b/modules/Install/Language/.rsync-filter index c8d333dc..b802a93d 100644 --- a/modules/Install/Language/.rsync-filter +++ b/modules/Install/Language/.rsync-filter @@ -1,13 +1,12 @@ -+ br/*** -+ ca/*** -+ de/*** + en/*** -+ es/*** + fr/*** -+ lt/*** -+ nn-no/*** + pl/*** ++ de/*** + pt-br/*** -+ sr-latn/*** ++ nn-no/*** ++ es/*** + zh-hans/*** ++ ca/*** ++ br/*** ++ sr-latn/*** - ** diff --git a/modules/Install/Language/cs/Install.php b/modules/Install/Language/cs/Install.php deleted file mode 100644 index 6071cdc3..00000000 --- a/modules/Install/Language/cs/Install.php +++ /dev/null @@ -1,62 +0,0 @@ - 'Instalátor Castopod', - 'manual_config' => 'Ruční konfigurace', - 'manual_config_subtitle' => - 'Vytvořte soubor `.env` s vaším nastavením a obnovte stránku pro pokračování instalace.', - 'form' => [ - 'instance_config' => 'Konfigurace instance', - 'hostname' => 'Název hostitele', - 'media_base_url' => 'URL pro média', - 'media_base_url_hint' => - 'Pokud používáte CDN a/nebo externí analytickou službu, můžete je nastavit zde.', - 'admin_gateway' => 'Administrátorská brána', - 'admin_gateway_hint' => - 'Cesta pro přístup k administraci (např. https://example.com/cp-admin). Ve výchozím nastavení je nastaveno jako cp-admin, doporučujeme ji z bezpečnostních důvodů změnit.', - 'auth_gateway' => 'Ověřovací brána', - 'auth_gateway_hint' => - 'Cesta pro přístup k ověřovacím stránkám (např. https://example.com/cp-auth). Ve výchozím nastavení je nastaveno jako cp-auth, doporučujeme ji z bezpečnostních důvodů změnit.', - 'database_config' => 'Konfigurace databáze', - 'database_config_hint' => - 'Castopod se musí připojit k databázi MySQL (nebo MariaDB). Pokud nemáte tyto požadované informace, kontaktujte prosím správce serveru.', - 'db_hostname' => 'Hostname databáze', - 'db_name' => 'Název databáze', - 'db_username' => 'Uživatelské k databázi', - 'db_password' => 'Heslo k databázi', - 'db_prefix' => 'Předpona databáze', - 'db_prefix_hint' => - "Předpona Castopod tabulky, ponechejte beze změn pokud nevíte co to znamená.", - 'cache_config' => 'Nastavení mezipaměti', - 'cache_config_hint' => - 'Vyberte preferovaného zpracovatele mezipaměti. Ponechte výchozí hodnotu, pokud nemáte přehled o tom, co to znamená.', - 'cache_handler' => 'Zpracovatel mezipaměti', - 'cacheHandlerOptions' => [ - 'file' => 'Soubor', - 'redis' => 'Redis', - 'predis' => 'Predis', - ], - 'next' => 'Další', - 'submit' => 'Dokončit instalaci', - 'create_superadmin' => 'Vytvořte si svůj Super Admin účet', - 'email' => 'E-mail', - 'username' => 'Uživatelské jméno', - 'password' => 'Heslo', - ], - 'messages' => [ - 'createSuperAdminSuccess' => - 'Váš superadmin účet byl úspěšně vytvořen. Přihlaste se a začněte s podcastem!', - 'databaseConnectError' => - 'Castopod se nemohl připojit k databázi. Upravte konfiguraci databáze a zkuste to znovu.', - 'writeError' => - "Nelze vytvořit / zapsat soubor `.env`. Musíte jej vytvořit ručně podle šablony souboru `.env.example` v balíčku Castopod.", - ], -]; diff --git a/modules/Install/Language/lt/Install.php b/modules/Install/Language/lt/Install.php deleted file mode 100644 index 3a01029d..00000000 --- a/modules/Install/Language/lt/Install.php +++ /dev/null @@ -1,62 +0,0 @@ - '„Castopod“ diegyklė', - 'manual_config' => 'Rankinis konfigūravimas', - 'manual_config_subtitle' => - 'Sukurkite failą `.env` su naudotinais nustatymais ir įkelkite šį tinklalapį iš naujo diegimui pratęsti.', - 'form' => [ - 'instance_config' => 'Serverio konfigūracija', - 'hostname' => 'Serverio vardas', - 'media_base_url' => 'Daugialypės terpės failų bazinis URL', - 'media_base_url_hint' => - 'Jei naudojatės CDN ir/ar išorine srauto analizės tarnyba, galite tai nurodyti čia.', - 'admin_gateway' => 'Administratoriaus skydelio adresas', - 'admin_gateway_hint' => - 'Kelias administratoriaus skydeliui pasiekti (pvz., https://example.com/cp-admin). Numatytuoju atveju naudojamas kelias „cp-admin“, tačiau mes rekomenduojame jį pasikeisti saugumo sumetimais.', - 'auth_gateway' => 'Autentifikacijos tinklalapių adresas', - 'auth_gateway_hint' => - 'Kelias autentifikacijos tinklalapiams pasiekti (pvz., https://example.com/cp-auth). Numatytuoju atveju naudojamas kelias „cp-auth“, tačiau mes rekomenduojame jį pasikeisti saugumo sumetimais.', - 'database_config' => 'Duomenų bazės konfigūracija', - 'database_config_hint' => - '„Castopod“ reikia prisijungti prie jūsų „MySQL“ ar „MariaDB“ duomenų bazės. Jei neturite prisijungimo prie duomenų bazės duomenų, kreipkitės į savo serverio administratorių.', - 'db_hostname' => 'DB serveris', - 'db_name' => 'DB pavadinimas', - 'db_username' => 'DB naudotojo vardas', - 'db_password' => 'DB slaptažodis', - 'db_prefix' => 'DB prefiksas', - 'db_prefix_hint' => - "„Castopod“ lentelių pavadinimų prefiksas. Jei nežinote, kas tai – palikite kas įrašyta.", - 'cache_config' => 'Podėlio konfigūracija', - 'cache_config_hint' => - 'Pasirinkite ketinamą naudoti podėlio tipą. Jei nežinote, kas tai – palikite numatytąjį parinktį.', - 'cache_handler' => 'Podėlio tipas', - 'cacheHandlerOptions' => [ - 'file' => 'Failai', - 'redis' => '„Redis“', - 'predis' => '„Predis“', - ], - 'next' => 'Toliau', - 'submit' => 'Užbaigti diegimą', - 'create_superadmin' => 'Susikurkite savo superadministratoriaus paskyrą', - 'email' => 'El. paštas', - 'username' => 'Naudotojo vardas', - 'password' => 'Slaptažodis', - ], - 'messages' => [ - 'createSuperAdminSuccess' => - 'Jūsų superadministratoriaus paskyra sukurta sėkmingai. Prisijunkite ir kurkite savo pirmąją tinklalaidę!', - 'databaseConnectError' => - '„Castopod“ nepavyko prisijungti prie nurodytos duomenų bazės. Pakoreguokite DB konfigūraciją ir bandykite dar kartą.', - 'writeError' => - "Nepavyko sukurti/rašyti į jūsų `.env` failą. Užpildykite jį rankiniu būdu, pasinaudodami šabloniniu `.env.example` failu iš „Castopod“ paketo.", - ], -]; diff --git a/modules/Media/Database/Migrations/2021-05-29-120000_add_media.php b/modules/Media/Database/Migrations/2021-05-29-120000_add_media.php index f2e86573..d7409038 100644 --- a/modules/Media/Database/Migrations/2021-05-29-120000_add_media.php +++ b/modules/Media/Database/Migrations/2021-05-29-120000_add_media.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Media\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddMedia extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -78,6 +80,7 @@ class AddMedia extends BaseMigration $this->forge->createTable('media'); } + #[Override] public function down(): void { $this->forge->dropTable('media'); diff --git a/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php b/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php index 1cd9c196..14b54394 100644 --- a/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php +++ b/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Media\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class RenameMediafileKey extends BaseMigration { + #[Override] public function up(): void { $fields = [ @@ -26,6 +28,7 @@ class RenameMediafileKey extends BaseMigration $this->forge->modifyColumn('media', $fields); } + #[Override] public function down(): void { $fields = [ diff --git a/modules/Media/Entities/Audio.php b/modules/Media/Entities/Audio.php index 7637099d..2219a537 100644 --- a/modules/Media/Entities/Audio.php +++ b/modules/Media/Entities/Audio.php @@ -12,6 +12,7 @@ namespace Modules\Media\Entities; use CodeIgniter\Files\File; use JamesHeinrich\GetID3\GetID3; +use Override; /** * @property float $duration @@ -21,6 +22,7 @@ class Audio extends BaseMedia { protected string $type = 'audio'; + #[Override] public function initFileProperties(): void { parent::initFileProperties(); @@ -31,6 +33,7 @@ class Audio extends BaseMedia } } + #[Override] public function setFile(File $file): self { parent::setFile($file); diff --git a/modules/Media/Entities/BaseMedia.php b/modules/Media/Entities/BaseMedia.php index 4bd783a9..5eb1b118 100644 --- a/modules/Media/Entities/BaseMedia.php +++ b/modules/Media/Entities/BaseMedia.php @@ -13,6 +13,7 @@ namespace Modules\Media\Entities; use CodeIgniter\Entity\Entity; use CodeIgniter\Files\File; use Modules\Media\Models\MediaModel; +use Override; use RuntimeException; /** @@ -59,6 +60,7 @@ class BaseMedia extends Entity /** * @param array $data */ + #[Override] public function injectRawData(array $data): static { parent::injectRawData($data); @@ -114,12 +116,12 @@ class BaseMedia extends Entity public function rename(): bool { - $newFileKey = $this->file_directory . '/' . (new File(''))->getRandomName() . '.' . $this->file_extension; + $newFileKey = $this->file_directory . '/' . new File('')->getRandomName() . '.' . $this->file_extension; $db = db_connect(); $db->transStart(); - if (! (new MediaModel())->update($this->id, [ + if (! new MediaModel()->update($this->id, [ 'file_key' => $newFileKey, ])) { return false; diff --git a/modules/Media/Entities/Chapters.php b/modules/Media/Entities/Chapters.php index e3f2ef2e..44cbfc54 100644 --- a/modules/Media/Entities/Chapters.php +++ b/modules/Media/Entities/Chapters.php @@ -12,11 +12,13 @@ namespace Modules\Media\Entities; use CodeIgniter\Files\File; use Exception; +use Override; class Chapters extends BaseMedia { protected string $type = 'chapters'; + #[Override] public function initFileProperties(): void { parent::initFileProperties(); @@ -28,6 +30,7 @@ class Chapters extends BaseMedia } } + #[Override] public function setFile(File $file): self { parent::setFile($file); diff --git a/modules/Media/Entities/Image.php b/modules/Media/Entities/Image.php index 5a56c2a8..02319af1 100644 --- a/modules/Media/Entities/Image.php +++ b/modules/Media/Entities/Image.php @@ -12,6 +12,7 @@ namespace Modules\Media\Entities; use CodeIgniter\Files\File; use GdImage; +use Override; /** * @property array> $sizes @@ -25,6 +26,7 @@ class Image extends BaseMedia */ protected array $sizes = []; + #[Override] public function initFileProperties(): void { parent::initFileProperties(); @@ -42,10 +44,14 @@ class Image extends BaseMedia foreach ($this->sizes as $name => $size) { $extension = array_key_exists('extension', $size) ? $size['extension'] : $this->file_extension; $mimetype = array_key_exists('mimetype', $size) ? $size['mimetype'] : $this->file_mimetype; + $width = array_key_exists('width', $size) ? $size['width'] : 0; + $height = array_key_exists('height', $size) ? $size['height'] : 0; $this->{$name . '_key'} = change_file_path($this->file_key, '_' . $name, $extension); $this->{$name . '_url'} = service('file_manager')->getUrl($this->{$name . '_key'}); $this->{$name . '_mimetype'} = $mimetype; + $this->{$name . '_width'} = $width; + $this->{$name . '_height'} = $height; } return true; @@ -54,6 +60,7 @@ class Image extends BaseMedia /** * @param array $data */ + #[Override] public function injectRawData(array $data): static { parent::injectRawData($data); @@ -72,6 +79,7 @@ class Image extends BaseMedia return $this; } + #[Override] public function setFile(File $file): self { parent::setFile($file); @@ -79,7 +87,7 @@ class Image extends BaseMedia if ($this->file_mimetype === 'image/jpeg' && $metadata = @exif_read_data( $file->getRealPath(), null, - true + true, )) { $metadata['sizes'] = $this->attributes['sizes']; $this->attributes['file_size'] = $metadata['FILE']['FileSize']; @@ -94,6 +102,7 @@ class Image extends BaseMedia return $this; } + #[Override] public function saveFile(): void { if ($this->attributes['sizes'] !== []) { @@ -104,6 +113,7 @@ class Image extends BaseMedia parent::saveFile(); } + #[Override] public function deleteFile(): bool { if (parent::deleteFile()) { diff --git a/modules/Media/Entities/Transcript.php b/modules/Media/Entities/Transcript.php index 56302e21..160d9551 100644 --- a/modules/Media/Entities/Transcript.php +++ b/modules/Media/Entities/Transcript.php @@ -13,6 +13,7 @@ namespace Modules\Media\Entities; use CodeIgniter\Files\File; use Exception; use Modules\Media\TranscriptParser; +use Override; class Transcript extends BaseMedia { @@ -22,6 +23,7 @@ class Transcript extends BaseMedia protected string $type = 'transcript'; + #[Override] public function initFileProperties(): void { parent::initFileProperties(); @@ -35,6 +37,7 @@ class Transcript extends BaseMedia } } + #[Override] public function setFile(File $file): self { parent::setFile($file); @@ -58,6 +61,7 @@ class Transcript extends BaseMedia return $this; } + #[Override] public function saveFile(): void { $this->saveJsonTranscript(); @@ -65,6 +69,7 @@ class Transcript extends BaseMedia parent::saveFile(); } + #[Override] public function deleteFile(): bool { if (! parent::deleteFile()) { diff --git a/modules/Media/FileManagers/FS.php b/modules/Media/FileManagers/FS.php index 2d38f75e..8fbb918e 100644 --- a/modules/Media/FileManagers/FS.php +++ b/modules/Media/FileManagers/FS.php @@ -7,17 +7,19 @@ namespace Modules\Media\FileManagers; use CodeIgniter\Files\File; use Exception; use Modules\Media\Config\Media as MediaConfig; +use Override; class FS implements FileManagerInterface { public function __construct( - protected MediaConfig $config + protected MediaConfig $config, ) { } /** * Saves a file to the corresponding folder in `public/media` */ + #[Override] public function save(File $file, string $key): string { helper('media'); @@ -45,6 +47,7 @@ class FS implements FileManagerInterface return $key; } + #[Override] public function delete(string $key): bool { helper('media'); @@ -52,11 +55,13 @@ class FS implements FileManagerInterface return @unlink($this->media_path_absolute($key)); } + #[Override] public function getUrl(string $key): string { return media_url($this->config->root . '/' . $key); } + #[Override] public function rename(string $oldKey, string $newKey): bool { helper('media'); @@ -64,6 +69,7 @@ class FS implements FileManagerInterface return rename($this->media_path_absolute($oldKey), $this->media_path_absolute($newKey)); } + #[Override] public function getFileContents(string $key): string|false { helper('media'); @@ -71,6 +77,7 @@ class FS implements FileManagerInterface return file_get_contents($this->media_path_absolute($key)); } + #[Override] public function getFileInput(string $key): string { helper('media'); @@ -78,6 +85,7 @@ class FS implements FileManagerInterface return $this->media_path_absolute($key); } + #[Override] public function deletePodcastImageSizes(string $podcastHandle): bool { foreach (['jpg', 'jpeg', 'png', 'webp'] as $ext) { @@ -87,6 +95,7 @@ class FS implements FileManagerInterface return true; } + #[Override] public function deletePersonImagesSizes(): bool { foreach (['jpg', 'jpeg', 'png', 'webp'] as $ext) { @@ -96,6 +105,7 @@ class FS implements FileManagerInterface return true; } + #[Override] public function deleteAll(string $prefix, string $pattern = '*'): bool { helper('media'); @@ -122,6 +132,7 @@ class FS implements FileManagerInterface return true; } + #[Override] public function isHealthy(): bool { helper('media'); diff --git a/modules/Media/FileManagers/S3.php b/modules/Media/FileManagers/S3.php index d3cf7f92..f31a25c3 100644 --- a/modules/Media/FileManagers/S3.php +++ b/modules/Media/FileManagers/S3.php @@ -9,13 +9,14 @@ use Aws\S3\S3Client; use CodeIgniter\Files\File; use Exception; use Modules\Media\Config\Media as MediaConfig; +use Override; class S3 implements FileManagerInterface { public S3Client $s3; public function __construct( - protected MediaConfig $config + protected MediaConfig $config, ) { $this->s3 = new S3Client([ 'version' => 'latest', @@ -27,6 +28,7 @@ class S3 implements FileManagerInterface ]); } + #[Override] public function save(File $file, string $key): string { $this->s3->putObject([ @@ -44,6 +46,7 @@ class S3 implements FileManagerInterface return $key; } + #[Override] public function delete(string $key): bool { try { @@ -58,11 +61,13 @@ class S3 implements FileManagerInterface return true; } + #[Override] public function getUrl(string $key): string { return media_url($this->prefixKey($key)); } + #[Override] public function rename(string $oldKey, string $newKey): bool { try { @@ -81,6 +86,7 @@ class S3 implements FileManagerInterface return $this->delete($oldKey); } + #[Override] public function getFileContents(string $key): string|false { try { @@ -95,11 +101,13 @@ class S3 implements FileManagerInterface return (string) $result->get('Body'); } + #[Override] public function getFileInput(string $key): string { return $this->getUrl($key); } + #[Override] public function deletePodcastImageSizes(string $podcastHandle): bool { foreach (['jpg', 'jpeg', 'png', 'webp'] as $ext) { @@ -109,6 +117,7 @@ class S3 implements FileManagerInterface return true; } + #[Override] public function deletePersonImagesSizes(): bool { foreach (['jpg', 'jpeg', 'png', 'webp'] as $ext) { @@ -118,6 +127,7 @@ class S3 implements FileManagerInterface return true; } + #[Override] public function deleteAll(string $prefix, ?string $pattern = '*'): bool { $prefix = rtrim($this->prefixKey($prefix), '/') . '/'; // make sure that there is a trailing slash @@ -161,6 +171,7 @@ class S3 implements FileManagerInterface return true; } + #[Override] public function isHealthy(): bool { // check that bucket exists diff --git a/modules/Media/Helpers/media_helper.php b/modules/Media/Helpers/media_helper.php index 85bad253..31d196ae 100644 --- a/modules/Media/Helpers/media_helper.php +++ b/modules/Media/Helpers/media_helper.php @@ -38,7 +38,6 @@ if (! function_exists('download_file')) { curl_setopt($ch, CURLOPT_MAXREDIRS, 20); curl_exec($ch); - curl_close($ch); fclose($file); diff --git a/modules/Media/Helpers/url_helper.php b/modules/Media/Helpers/url_helper.php index aba33540..e9cbcdc2 100644 --- a/modules/Media/Helpers/url_helper.php +++ b/modules/Media/Helpers/url_helper.php @@ -24,7 +24,7 @@ if (! function_exists('media_url')) { $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), - $uri->getFragment() + $uri->getFragment(), ); } } diff --git a/modules/Media/Models/MediaModel.php b/modules/Media/Models/MediaModel.php index 78d1ce97..d6b948a6 100644 --- a/modules/Media/Models/MediaModel.php +++ b/modules/Media/Models/MediaModel.php @@ -81,8 +81,8 @@ class MediaModel extends Model */ public function __construct( protected string $fileType = 'document', - ConnectionInterface &$db = null, - ValidationInterface $validation = null + ?ConnectionInterface &$db = null, + ?ValidationInterface $validation = null, ) { $this->returnType = match ($fileType) { 'audio' => Audio::class, @@ -90,7 +90,7 @@ class MediaModel extends Model 'image' => Image::class, 'transcript' => Transcript::class, 'chapters' => Chapters::class, - default => Document::class + default => Document::class, }; parent::__construct($db, $validation); diff --git a/modules/Media/TranscriptParser.php b/modules/Media/TranscriptParser.php index c1828c3b..67ebba2f 100644 --- a/modules/Media/TranscriptParser.php +++ b/modules/Media/TranscriptParser.php @@ -133,7 +133,7 @@ class TranscriptParser $lines = explode(PHP_EOL, $this->transcriptContent); // add a newline as last item, if it isn't already a newline - if ($lines[array_key_last($lines)] !== '') { + if (array_last($lines) !== '') { $lines[] = PHP_EOL; } diff --git a/modules/MediaClipper/Commands/Generate.php b/modules/MediaClipper/Commands/Generate.php index 9ceb0825..656349ac 100644 --- a/modules/MediaClipper/Commands/Generate.php +++ b/modules/MediaClipper/Commands/Generate.php @@ -10,6 +10,7 @@ use CodeIgniter\Files\File; use CodeIgniter\I18n\Time; use Exception; use Modules\MediaClipper\VideoClipper; +use Override; class Generate extends BaseCommand { @@ -19,17 +20,20 @@ class Generate extends BaseCommand protected $description = 'Displays basic application information.'; + #[Override] public function run(array $params): void { // get number of running clips to prevent from having too much running in parallel // TODO: get the number of running ffmpeg processes directly from the machine? - $runningVideoClips = (new ClipModel())->getRunningVideoClipsCount(); + $runningVideoClips = new ClipModel() + ->getRunningVideoClipsCount(); if ($runningVideoClips >= config('Admin')->videoClipWorkers) { return; } // get all clips that haven't been processed yet - $scheduledClips = (new ClipModel())->getScheduledVideoClips(); + $scheduledClips = new ClipModel() + ->getScheduledVideoClips(); if ($scheduledClips === []) { return; @@ -43,13 +47,14 @@ class Generate extends BaseCommand ]; } - (new ClipModel())->updateBatch($data, 'id'); + new ClipModel() + ->updateBatch($data, 'id'); // Loop through clips to generate them foreach ($scheduledClips as $scheduledClip) { try { // set clip to pending - (new ClipModel()) + new ClipModel() ->update($scheduledClip->id, [ 'status' => 'running', 'job_started_at' => Time::now(), @@ -84,11 +89,12 @@ class Generate extends BaseCommand $clipModel->clearVideoClipCache($scheduledClip->id); } catch (Exception $exception) { - (new ClipModel())->update($scheduledClip->id, [ - 'status' => 'failed', - 'logs' => $exception, - 'job_ended_at' => Time::now(), - ]); + new ClipModel() + ->update($scheduledClip->id, [ + 'status' => 'failed', + 'logs' => $exception, + 'job_ended_at' => Time::now(), + ]); } } } diff --git a/modules/MediaClipper/VideoClipper.php b/modules/MediaClipper/VideoClipper.php index 7e380198..55e320fb 100644 --- a/modules/MediaClipper/VideoClipper.php +++ b/modules/MediaClipper/VideoClipper.php @@ -25,7 +25,7 @@ class VideoClipper /** * @var array */ - final public const FONTS = [ + final public const array FONTS = [ 'episodeTitle' => 'Rubik-Bold.ttf', 'podcastTitle' => 'Inter-Regular.otf', 'subtitles' => 'Inter-SemiBold', @@ -96,7 +96,7 @@ class VideoClipper if (! $tempFile) { throw new Exception( - 'Could not create temporary files, check for permissions on your ' . WRITEPATH . 'temp folder.' + 'Could not create temporary files, check for permissions on your ' . WRITEPATH . 'temp folder.', ); } @@ -233,11 +233,11 @@ class VideoClipper '[m][a]alphamerge[waves_t3]', "[waves_t3]scale={$this->dimensions['soundwaves']['rescaleWidth']}:{$this->dimensions['soundwaves']['rescaleHeight']},lutrgb=r='if(gt(val,100),{$this->colors['soundwaves'][0]},val)':g='if(gt(val,100),{$this->colors['soundwaves'][1]},val)':b='if(gt(val,100),{$this->colors['soundwaves'][2]},val)'[waves_final]", "[1:v][waves_final]overlay=x={$this->dimensions['soundwaves']['x']}:y={$this->dimensions['soundwaves']['y']}:shortest=1,drawtext=fontfile=" . $this->getFont( - 'timestamp' + 'timestamp', ) . ":text='%{pts\:gmtime\:{$this->start}\:%H\\\\\\\\\\:%M\\\\\\\\\\:%S\}':x={$this->dimensions['timestamp']['x']}:y={$this->dimensions['timestamp']['y']}:fontsize={$this->dimensions['timestamp']['fontsize']}:fontcolor=0x{$this->colors['timestampText']}:box=1:boxcolor=0x{$this->colors['timestampBg']}:boxborderw={$this->dimensions['timestamp']['padding']}[v3]", "color=c=0x{$this->colors['progressbar']}:s={$this->dimensions['width']}x{$this->dimensions['progressbar']['height']}[progressbar]", "[v3][progressbar]overlay=-w+(w/{$this->duration})*t:0:shortest=1:format=rgb,subtitles={$this->subtitlesClipOutput}:fontsdir=" . config( - 'MediaClipper' + 'MediaClipper', )->fontsFolder . ":force_style='Fontname=" . self::FONTS['subtitles'] . ",Alignment=5,Fontsize={$this->dimensions['subtitles']['fontsize']},PrimaryColour=&H{$this->colors['subtitles']}&,BorderStyle=1,Outline=0,Shadow=0,MarginL={$this->dimensions['subtitles']['marginL']},MarginR={$this->dimensions['subtitles']['marginR']},MarginV={$this->dimensions['subtitles']['marginV']}'[outv]", "[6:v]scale={$this->dimensions['watermark']['width']}:{$this->dimensions['watermark']['height']}[watermark]", "color=0x{$this->colors['watermarkBg']}:{$this->dimensions['watermark']['width']}x{$this->dimensions['watermark']['height']}[over]", @@ -313,7 +313,7 @@ class VideoClipper $scaledEpisodeCover = $this->scaleImage( $episodeCover, $this->dimensions['cover']['width'], - $this->dimensions['cover']['height'] + $this->dimensions['cover']['height'], ); if (! $scaledEpisodeCover) { @@ -332,7 +332,7 @@ class VideoClipper $this->dimensions['cover']['x'], $this->dimensions['cover']['y'], $this->dimensions['cover']['width'], - $this->dimensions['cover']['height'] + $this->dimensions['cover']['height'], ); if (! $isOverlaid) { @@ -357,13 +357,13 @@ class VideoClipper $this->dimensions['episodeTitle']['fontsize'], 0, $this->getFont('episodeTitle'), - $this->episode->title + $this->episode->title, ); $episodeNumberingBox = $this->calculateTextBox( $this->dimensions['episodeNumbering']['fontsize'], 0, $this->getFont('episodeNumbering'), - $this->episodeNumbering + $this->episodeNumbering, ); if (! $episodeTitleBox || ! $episodeNumberingBox) { return false; @@ -419,7 +419,7 @@ class VideoClipper $scaledQuotes = $this->scaleImage( $cleanedQuotes, $this->dimensions['quotes']['width'], - $this->dimensions['quotes']['height'] + $this->dimensions['quotes']['height'], ); if (! $scaledQuotes) { @@ -433,7 +433,7 @@ class VideoClipper $this->dimensions['quotes']['x'], $this->dimensions['quotes']['y'], $this->dimensions['quotes']['width'], - $this->dimensions['quotes']['height'] + $this->dimensions['quotes']['height'], ); // Save Image @@ -564,7 +564,6 @@ class VideoClipper imagefill($img, 0, 0, $alphacolor); imagecopyresampled($img, $src, 0, 0, 0, 0, $ns, $ns, $s, $s); - imagedestroy($src); imagearc($img, $radius - 1, $radius - 1, $radius * 2, $radius * 2, 180, 270, $alphacolor); imagefilltoborder($img, 0, 0, $alphacolor, $alphacolor); @@ -586,7 +585,6 @@ class VideoClipper imagealphablending($dest, false); imagefilledrectangle($dest, 0, 0, $s, $s, $alphacolor); imagecopyresampled($dest, $img, 0, 0, 0, 0, $s, $s, $ns, $ns); - imagedestroy($img); # output image imagealphablending($source, false); @@ -595,7 +593,6 @@ class VideoClipper imagecopy($source, $dest, $ws - $corner, $hs - $corner, $corner, $corner, $corner, $corner); imagecopy($source, $dest, 0, $hs - $corner, 0, $corner, $corner, $corner); imagealphablending($source, true); - imagedestroy($dest); return $source; } @@ -606,7 +603,7 @@ class VideoClipper int $x, int $y, int $width, - int $height + int $height, ): bool { return imagecopy($background, $foreground, $x, $y, 0, 0, $width, $height); } @@ -646,7 +643,7 @@ class VideoClipper $y + $fontsize + ($leading * $i), $textColor, $fontPath, - $line + $line, ); } diff --git a/modules/Platforms/Config/Routes.php b/modules/Platforms/Config/Routes.php index 3dfd6c6e..06284656 100644 --- a/modules/Platforms/Config/Routes.php +++ b/modules/Platforms/Config/Routes.php @@ -21,44 +21,44 @@ $routes->group( $routes->group('podcasts/(:num)/platforms', static function ($routes): void { $routes->get( '/', - 'PlatformController::platforms/$1/podcasting', + 'PlatformController::list/$1/podcasting', [ 'as' => 'platforms-podcasting', - 'filter' => 'permission:podcast#.manage-platforms', + 'filter' => 'permission:podcast$1.manage-platforms', ], ); $routes->get( 'social', - 'PlatformController::platforms/$1/social', + 'PlatformController::list/$1/social', [ 'as' => 'platforms-social', - 'filter' => 'permission:podcast#.manage-platforms', + 'filter' => 'permission:podcast$1.manage-platforms', ], ); $routes->get( 'funding', - 'PlatformController::platforms/$1/funding', + 'PlatformController::list/$1/funding', [ 'as' => 'platforms-funding', - 'filter' => 'permission:podcast#.manage-platforms', + 'filter' => 'permission:podcast$1.manage-platforms', ], ); $routes->post( 'save/(:platformType)', - 'PlatformController::attemptPlatformsUpdate/$1/$2', + 'PlatformController::updateAction/$1/$2', [ 'as' => 'platforms-save', - 'filter' => 'permission:podcast#.manage-platforms', + 'filter' => 'permission:podcast$1.manage-platforms', ], ); $routes->get( '(:platformType)/(:slug)/podcast-platform-remove', - 'PlatformController::removePlatform/$1/$2/$3', + 'PlatformController::removeAction/$1/$2/$3', [ 'as' => 'podcast-platform-remove', - 'filter' => 'permission:podcast#.manage-platforms', + 'filter' => 'permission:podcast$1.manage-platforms', ], ); }); - } + }, ); diff --git a/modules/Platforms/Controllers/PlatformController.php b/modules/Platforms/Controllers/PlatformController.php index 3d6b9939..20df5fef 100644 --- a/modules/Platforms/Controllers/PlatformController.php +++ b/modules/Platforms/Controllers/PlatformController.php @@ -28,7 +28,7 @@ class PlatformController extends BaseController } if ( - ! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast + ! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast ) { throw PageNotFoundException::forPageNotFound(); } @@ -44,24 +44,25 @@ class PlatformController extends BaseController return view('podcast/platforms/dashboard'); } - public function platforms(string $platformType): string + public function list(string $platformType): string { helper('form'); $data = [ 'podcast' => $this->podcast, 'platformType' => $platformType, - 'platforms' => (new PlatformModel())->getPlatformsWithData($this->podcast->id, $platformType), + 'platforms' => new PlatformModel() + ->getPlatformsWithData($this->podcast->id, $platformType), ]; + $this->setHtmlHead(lang("Platforms.title.{$platformType}")); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, ]); - return view('podcast/platforms', $data); } - public function attemptPlatformsUpdate(string $platformType): RedirectResponse + public function updateAction(string $platformType): RedirectResponse { $platformModel = new PlatformModel(); $validation = service('validation'); @@ -98,9 +99,10 @@ class PlatformController extends BaseController ->with('message', lang('Platforms.messages.updateSuccess')); } - public function removePlatform(string $platformType, string $platformSlug): RedirectResponse + public function removeAction(string $platformType, string $platformSlug): RedirectResponse { - (new PlatformModel())->removePlatform($this->podcast->id, $platformType, $platformSlug); + new PlatformModel() + ->removePlatform($this->podcast->id, $platformType, $platformSlug); return redirect() ->back() diff --git a/modules/Platforms/Models/PlatformModel.php b/modules/Platforms/Models/PlatformModel.php index c4d820eb..8ba8c89a 100644 --- a/modules/Platforms/Models/PlatformModel.php +++ b/modules/Platforms/Models/PlatformModel.php @@ -33,7 +33,7 @@ class PlatformModel extends Model protected $allowedFields = ['podcast_id', 'type', 'slug', 'link_url', 'account_id', 'is_visible']; /** - * @var string + * @var class-string */ protected $returnType = Platform::class; @@ -135,10 +135,12 @@ class PlatformModel extends Model { $this->clearCache($podcastId); - // delete all platforms given the type to overwrite them with data + $platforms = service('platforms'); + + $platformsData = $platforms->getPlatformsByType($platformType); + $this->builder() - ->where('podcast_id', $podcastId) - ->where('type', $platformType) + ->whereIn('slug', array_keys($platformsData)) ->delete(); if ($data === []) { diff --git a/modules/Platforms/Platforms.php b/modules/Platforms/Platforms.php index 72743d0c..13cf436c 100644 --- a/modules/Platforms/Platforms.php +++ b/modules/Platforms/Platforms.php @@ -17,26 +17,6 @@ class Platforms */ public const DATA = [ 'podcasting' => [ - 'podcastindex' => [ - 'label' => 'Podcast Index', - 'home_url' => 'https://podcastindex.org/', - 'submit_url' => 'https://podcastindex.org/add', - ], - 'apple' => [ - 'label' => 'Apple Podcasts', - 'home_url' => 'https://www.apple.com/itunes/podcasts/', - 'submit_url' => 'https://podcastsconnect.apple.com/my-podcasts/new-feed', - ], - 'spotify' => [ - 'label' => 'Spotify', - 'home_url' => 'https://www.spotify.com/', - 'submit_url' => 'https://podcasters.spotify.com/dash/submit', - ], - 'youtube-music' => [ - 'label' => 'YouTube Music', - 'home_url' => 'https://www.youtube.com/creators/podcasts/', - 'submit_url' => 'https://studio.youtube.com/channel/content/podcasts', - ], 'amazon' => [ 'label' => 'Amazon Music', 'home_url' => 'https://music.amazon.com/', @@ -47,11 +27,31 @@ class Platforms 'home_url' => 'https://antennapod.org/', 'submit_url' => 'https://antennapod.org/documentation/podcasters-hosters/add-on-antennapod', ], + 'anytime' => [ + 'label' => 'Anytime Podcast Player', + 'home_url' => 'https://anytimeplayer.app/', + 'submit_url' => null, + ], + 'apple' => [ + 'label' => 'Apple Podcasts', + 'home_url' => 'https://www.apple.com/itunes/podcasts/', + 'submit_url' => 'https://podcastsconnect.apple.com/my-podcasts/new-feed', + ], 'blubrry' => [ 'label' => 'Blubrry', 'home_url' => 'https://www.blubrry.com/', 'submit_url' => 'https://www.blubrry.com/addpodcast.php', ], + 'breez' => [ + 'label' => 'Breez', + 'home_url' => 'https://breez.technology/', + 'submit_url' => null, + ], + 'castamatic' => [ + 'label' => 'Castamatic', + 'home_url' => 'https://castamatic.com/', + 'submit_url' => null, + ], 'castbox' => [ 'label' => 'Castbox', 'home_url' => 'https://castbox.fm/', @@ -67,16 +67,36 @@ class Platforms 'home_url' => 'http://castro.fm/', 'submit_url' => 'https://castro.fm/support/link-to-your-podcast-in-castro', ], + 'curiocaster' => [ + 'label' => 'CurioCaster', + 'home_url' => 'https://curiocaster.com/', + 'submit_url' => null, + ], 'deezer' => [ 'label' => 'Deezer', 'home_url' => 'https://www.deezer.com/', 'submit_url' => 'https://podcasters.deezer.com/submission', ], + 'episodes-fm' => [ + 'label' => 'Episodes.fm', + 'home_url' => 'https://episodes.fm/', + 'submit_url' => 'https://podcastindex.org/add', + ], + 'fountain' => [ + 'label' => 'Fountain', + 'home_url' => 'https://www.fountain.fm/', + 'submit_url' => 'https://support.fountain.fm/article/56-how-to-claim-your-show-on-fountain', + ], 'fyyd' => [ 'label' => 'fyyd', 'home_url' => 'https://fyyd.de/', 'submit_url' => 'https://fyyd.de/add-feed', ], + 'gpodder' => [ + 'label' => 'gPodder', + 'home_url' => 'https://gpodder.org/', + 'submit_url' => null, + ], 'ivoox' => [ 'label' => 'Ivoox', 'home_url' => 'https://www.ivoox.com/', @@ -112,6 +132,11 @@ class Platforms 'home_url' => 'https://podcastaddict.com/', 'submit_url' => 'https://podcastaddict.com/submit', ], + 'podcastindex' => [ + 'label' => 'Podcast Index', + 'home_url' => 'https://podcastindex.org/', + 'submit_url' => 'https://podcastindex.org/add', + ], 'podchaser' => [ 'label' => 'Podchaser', 'home_url' => 'https://www.podchaser.com/', @@ -147,6 +172,11 @@ class Platforms 'home_url' => 'https://radiopublic.com/', 'submit_url' => 'https://podcasters.radiopublic.com/signup', ], + 'spotify' => [ + 'label' => 'Spotify', + 'home_url' => 'https://www.spotify.com/', + 'submit_url' => 'https://podcasters.spotify.com/dash/submit', + ], 'spreaker' => [ 'label' => 'Spreaker', 'home_url' => 'https://www.spreaker.com/', @@ -157,41 +187,6 @@ class Platforms 'home_url' => 'https://tunein.com/', 'submit_url' => 'https://help.tunein.com/contact/add-podcast-S19TR3Sdf', ], - 'anytime' => [ - 'label' => 'Anytime Podcast Player', - 'home_url' => 'https://anytimeplayer.app/', - 'submit_url' => null, - ], - 'breez' => [ - 'label' => 'Breez', - 'home_url' => 'https://breez.technology/', - 'submit_url' => null, - ], - 'castamatic' => [ - 'label' => 'Castamatic', - 'home_url' => 'https://castamatic.com/', - 'submit_url' => null, - ], - 'curiocaster' => [ - 'label' => 'CurioCaster', - 'home_url' => 'https://curiocaster.com/', - 'submit_url' => null, - ], - 'episodes-fm' => [ - 'label' => 'Episodes.fm', - 'home_url' => 'https://episodes.fm/', - 'submit_url' => 'https://podcastindex.org/add', - ], - 'fountain' => [ - 'label' => 'Fountain', - 'home_url' => 'https://www.fountain.fm/', - 'submit_url' => 'https://support.fountain.fm/article/56-how-to-claim-your-show-on-fountain', - ], - 'gpodder' => [ - 'label' => 'gPodder', - 'home_url' => 'https://gpodder.org/', - 'submit_url' => null, - ], 'hypercatcher' => [ 'label' => 'HyperCatcher', 'home_url' => 'https://hypercatcher.com/', @@ -262,6 +257,11 @@ class Platforms 'home_url' => 'https://www.tsacdop.app/', 'submit_url' => null, ], + 'youtube-music' => [ + 'label' => 'YouTube Music', + 'home_url' => 'https://www.youtube.com/creators/podcasts/', + 'submit_url' => 'https://studio.youtube.com/channel/content/podcasts', + ], ], 'social' => [ 'bluesky' => [ diff --git a/modules/Plugins/Commands/Add.php b/modules/Plugins/Commands/Add.php new file mode 100644 index 00000000..97e8aa28 --- /dev/null +++ b/modules/Plugins/Commands/Add.php @@ -0,0 +1,81 @@ + + */ + protected $arguments = [ + 'plugin' => 'The pluginKey and an optional version separated by an @. If version is not provided, the latest will be added by default.', + ]; + + /** + * Actually execute a command. + * + * @param array $params + */ + #[Override] + public function run(array $params): void + { + parent::run($params); + + $plugins = $this->parsePluginsParams($params); + + /** @var PluginsManager $cpm */ + $cpm = service('cpm'); + + $cpm->install($plugins); + } + + /** + * @param array $params + * @return array + */ + private function parsePluginsParams(array $params): array + { + $plugins = []; + foreach ($params as $param) { + preg_match( + '/^(?[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*)(@(?\S+))?\s*$/', + $param, + $matches, + ); + + if (array_key_exists('pluginKey', $matches)) { + $plugins[$matches['pluginKey']] = $matches['version'] ?? null; + } + } + + return $plugins; + } +} diff --git a/modules/Plugins/Commands/BaseCommand.php b/modules/Plugins/Commands/BaseCommand.php new file mode 100644 index 00000000..7cc27b01 --- /dev/null +++ b/modules/Plugins/Commands/BaseCommand.php @@ -0,0 +1,46 @@ + + */ + protected $options = [ + '--debug' => 'Get log trace to follow what is happening under the hood.', + ]; + + /** + * Actually execute a command. + * + * @param array $params + * + * @return int|void + */ + public function run(array $params) + { + if (CLI::getOption('debug')) { + PluginsManagerLogger::$formatter = CpmFormatterDebug::class; + } else { + PluginsManagerLogger::$formatter = CpmFormatter::class; + } + + return 0; + } +} diff --git a/modules/Plugins/Commands/CpmFormatter.php b/modules/Plugins/Commands/CpmFormatter.php new file mode 100644 index 00000000..e312824c --- /dev/null +++ b/modules/Plugins/Commands/CpmFormatter.php @@ -0,0 +1,60 @@ + CLI::write( + sprintf('• adding plugin %s%s', CLI::color( + $log['context']['pluginKey'], + 'white', + ), $log['context']['constraint'] === null ? '' : '@' . CLI::color( + $log['context']['constraint'], + 'light_yellow', + )), + ), + 'add.end' => CLI::write( + sprintf('+ %s@%s added%s', $log['context']['pluginKey'], $log['context']['version'], PHP_EOL), + 'light_green', + ), + 'update.start' => CLI::write(sprintf('• updating plugin %s…', $log['context']['pluginKey'])), + 'update.end' => CLI::write( + '✔ ' . sprintf( + '%s updated to version %s%s', + $log['context']['pluginKey'], + $log['context']['version'], + PHP_EOL, + ), + 'light_green', + ), + 'remove.start' => CLI::write(sprintf('• removing plugin %s…', $log['context']['pluginKey'])), + 'remove.end' => CLI::write( + '- ' . sprintf('%s was removed%s', $log['context']['pluginKey'], PHP_EOL), + 'light_red', + ), + default => null, + }; + + if ($level === LogLevel::Warning) { + CLI::write('⚠️ ' . $log['message'], 'light_yellow'); + } + + if ($level === LogLevel::Error) { + CLI::newLine(); + CLI::write(' error ', 'white', 'red'); + CLI::error($log['message']); + CLI::newLine(); + + exit(1); // exit with error, something wrong happened + } + } +} diff --git a/modules/Plugins/Commands/CpmFormatterDebug.php b/modules/Plugins/Commands/CpmFormatterDebug.php new file mode 100644 index 00000000..417576b1 --- /dev/null +++ b/modules/Plugins/Commands/CpmFormatterDebug.php @@ -0,0 +1,42 @@ + CLI::write( + sprintf('%s %s', CLI::color($level->name, 'white', 'green'), json_encode( + $log, + JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, + )), + ), + LogLevel::Warning => CLI::write( + sprintf('%s %s', CLI::color($level->name, 'white', 'yellow'), json_encode( + $log, + JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, + )), + ), + LogLevel::Error => CLI::write( + sprintf('%s %s', CLI::color($level->name, 'white', 'red'), json_encode( + $log, + JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, + )), + ), + default => CLI::write( + sprintf('%s %s', CLI::color($level->name, 'white', 'blue'), json_encode( + $log, + JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, + )), + ), + }; + } +} diff --git a/modules/Plugins/Commands/CreatePlugin.php b/modules/Plugins/Commands/CreatePlugin.php new file mode 100644 index 00000000..c0bcafa0 --- /dev/null +++ b/modules/Plugins/Commands/CreatePlugin.php @@ -0,0 +1,153 @@ + ['use App\Entities\Podcast;'], + 'rssAfterChannel' => ['use App\Entities\Podcast;', 'use App\Libraries\RssFeed;'], + 'rssBeforeItem' => ['use App\Entities\Episode;'], + 'rssAfterItem' => ['use App\Entities\Episode;', 'use App\Libraries\RssFeed;'], + 'siteHead' => ['use use App\Libraries\HtmlHead'], + ]; + + protected const HOOKS_METHODS = [ + 'rssBeforeChannel' => ' public function rssBeforeChannel(Podcast $podcast): void + { + // YOUR CODE HERE + }', + 'rssAfterChannel' => ' public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void + { + // YOUR CODE HERE + }', + 'rssBeforeItem' => ' public function rssBeforeItem(Episode $episode): void + { + // YOUR CODE HERE + }', + 'rssAfterItem' => ' public function rssAfterItem(Episode $episode, RssFeed $item): void + { + // YOUR CODE HERE + }', + 'siteHead' => ' public function siteHead(HtmlHead $head): void + { + // YOUR CODE HERE + }', + ]; + + /** + * @var string + */ + protected $name = 'plugins:create'; + + /** + * @var string + */ + protected $description = 'Generates a new plugin folder based on a template.'; + + /** + * Actually execute a command. + * + * @param list $params + */ + #[Override] + public function run(array $params): void + { + $pluginName = CLI::prompt( + 'Plugin name (/)', + 'acme/hello-world', + Manifest::$validation_rules['name'], + ); + CLI::newLine(); + $description = CLI::prompt('Description', '', Manifest::$validation_rules['description']); + CLI::newLine(); + $license = CLI::prompt('License', 'UNLICENSED', Manifest::$validation_rules['license']); + CLI::newLine(); + $hooks = CLI::promptByMultipleKeys('Which hooks do you want to implement?', Plugins::HOOKS); + + $nameParts = explode('/', $pluginName); + $vendor = $nameParts[0]; + $name = $nameParts[1]; + + /** @var PluginsConfig $pluginsConfig */ + $pluginsConfig = config('Plugins'); + + // 1. create plugin directory if not existent + $pluginDirectory = $pluginsConfig->folder . $vendor . DIRECTORY_SEPARATOR . $name; + if (! file_exists($pluginDirectory)) { + mkdir($pluginDirectory, 0755, true); + } + + // 2. get contents of templates + $manifestTemplate = file_get_contents(__DIR__ . '/plugin-template/manifest.tpl.json'); + + if (! $manifestTemplate) { + throw new Exception('Failed to get manifest template.'); + } + + $pluginClassTemplate = file_get_contents(__DIR__ . '/plugin-template/Plugin.tpl.php'); + + if (! $pluginClassTemplate) { + throw new Exception('Failed to get Plugin class template.'); + } + + // 3. edit templates' contents + $manifestContents = str_replace('"name": ""', '"name": "' . $pluginName . '"', $manifestTemplate); + $manifestContents = str_replace( + '"description": ""', + '"description": "' . $description . '"', + $manifestContents, + ); + $manifestContents = str_replace('"license": ""', '"license": "' . $license . '"', $manifestContents); + $manifestContents = str_replace( + '"hooks": []', + '"hooks": ["' . implode('", "', $hooks) . '"]', + $manifestContents, + ); + + $pluginClassName = str_replace( + ' ', + '', + ucwords(str_replace(['-', '_', '.'], ' ', $vendor . ' ' . $name)) . 'Plugin', + ); + $pluginClassContents = str_replace('class Plugin', 'class ' . $pluginClassName, $pluginClassTemplate); + + $allImports = []; + $allMethods = []; + foreach ($hooks as $hook) { + $allImports = [...$allImports, ...self::HOOKS_IMPORTS[$hook]]; + $allMethods = [...$allMethods, self::HOOKS_METHODS[$hook]]; + } + + $imports = implode(PHP_EOL, array_unique($allImports)); + $methods = implode(PHP_EOL . PHP_EOL, $allMethods); + $pluginClassContents = str_replace('// IMPORTS_HERE', $imports, $pluginClassContents); + $pluginClassContents = str_replace(' // HOOKS_HERE', $methods, $pluginClassContents); + + $manifest = $pluginDirectory . '/manifest.json'; + $pluginClass = $pluginDirectory . '/Plugin.php'; + + if (! file_put_contents($manifest, $manifestContents)) { + throw new Exception('Failed to create manifest.json file.'); + } + + if (! file_put_contents($pluginClass, $pluginClassContents)) { + throw new Exception('Failed to create Plugin class file.'); + } + + CLI::newLine(1); + CLI::write( + sprintf('Plugin %s created in %s', CLI::color($pluginName, 'white'), CLI::color($pluginDirectory, 'white')), + 'green', + ); + } +} diff --git a/modules/Plugins/Commands/Install.php b/modules/Plugins/Commands/Install.php new file mode 100644 index 00000000..11c80a03 --- /dev/null +++ b/modules/Plugins/Commands/Install.php @@ -0,0 +1,48 @@ + $params + */ + #[Override] + public function run(array $params): void + { + parent::run($params); + + /** @var PluginsManager $cpm */ + $cpm = service('cpm'); + + $cpm->installFromPluginsTxt(); + } +} diff --git a/modules/Plugins/Commands/Remove.php b/modules/Plugins/Commands/Remove.php new file mode 100644 index 00000000..6f54212f --- /dev/null +++ b/modules/Plugins/Commands/Remove.php @@ -0,0 +1,85 @@ + + */ + protected $arguments = [ + 'plugins' => 'One or more plugins as vendor/plugin', + ]; + + /** + * @param list $params + */ + #[Override] + public function run(array $params): int + { + parent::run($params); + + /** @var Plugins $plugins */ + $plugins = service('plugins'); + + /** @var PluginsManager $cpm */ + $cpm = service('cpm'); + + /** @var list $errors */ + $errors = []; + foreach ($params as $pluginKey) { + $plugin = $plugins->getPluginByKey($pluginKey); + + if ($plugin === null) { + $errors[] = sprintf('Plugin %s was not found.', $pluginKey); + continue; + } + + if (! $plugins->uninstall($plugin)) { + $errors[] = sprintf('Something happened when removing %s', $pluginKey); + break; + } + + // delete plugin folder + $cpm->remove($pluginKey); + } + + foreach ($errors as $error) { + CLI::write(' error ', 'white', 'red'); + CLI::error($error); + CLI::newLine(); + } + + return $errors === [] ? 0 : 1; + } +} diff --git a/modules/Plugins/Commands/Update.php b/modules/Plugins/Commands/Update.php new file mode 100644 index 00000000..08836bfe --- /dev/null +++ b/modules/Plugins/Commands/Update.php @@ -0,0 +1,65 @@ + + */ + protected $arguments = [ + 'plugin' => 'The pluginKey and an optional version separated by an @. If version is not provided, the latest will be added by default.', + ]; + + /** + * Actually execute a command. + * + * @param array $params + * + * @return int|void + */ + #[Override] + public function run(array $params) + { + parent::run($params); + + if ($params === []) { + CLI::error('Missing pluginKey argument.'); + return 1; + } + + /** @var PluginsManager $cpm */ + $cpm = service('cpm'); + + $cpm->update($params[0]); + } +} diff --git a/modules/Plugins/Commands/plugin-template/Plugin.tpl.php b/modules/Plugins/Commands/plugin-template/Plugin.tpl.php new file mode 100644 index 00000000..edf615e5 --- /dev/null +++ b/modules/Plugins/Commands/plugin-template/Plugin.tpl.php @@ -0,0 +1,11 @@ +group( + config('Admin') + ->gateway, + [ + 'namespace' => 'Modules\Plugins\Controllers', + ], + static function ($routes): void { + $routes->group('plugins', static function ($routes): void { + $routes->get('/', 'PluginController::installed', [ + 'as' => 'plugins-installed', + 'filter' => 'permission:plugins.manage', + ]); + $routes->get('(:segment)', 'PluginController::vendor/$1', [ + 'as' => 'plugins-vendor', + 'filter' => 'permission:plugins.manage', + ]); + $routes->group('(:segment)/(:segment)', static function ($routes): void { + $routes->get('/', 'PluginController::view/$1/$2', [ + 'as' => 'plugins-view', + 'filter' => 'permission:plugins.manage', + ]); + $routes->get('settings', 'PluginController::settingsView/$1/$2', [ + 'as' => 'plugins-settings-general', + 'filter' => 'permission:plugins.manage', + ]); + $routes->post('settings', 'PluginController::settingsAction/$1/$2', [ + 'as' => 'plugins-settings-general-action', + 'filter' => 'permission:plugins.manage', + ]); + $routes->get('(:num)', 'PluginController::settingsView/$1/$2/$3', [ + 'as' => 'plugins-settings-podcast', + 'filter' => 'permission:podcast$3.edit', + ]); + $routes->post('(:num)', 'PluginController::settingsAction/$1/$2/$3', [ + 'as' => 'plugins-settings-podcast-action', + 'filter' => 'permission:podcast$3.edit', + ]); + $routes->get('(:num)/(:num)', 'PluginController::settingsView/$1/$2/$3/$4', [ + 'as' => 'plugins-settings-episode', + 'filter' => 'permission:podcast$3.episodes.edit', + ]); + $routes->post('(:num)/(:num)', 'PluginController::settingsAction/$1/$2/$3/$4', [ + 'as' => 'plugins-settings-episode-action', + 'filter' => 'permission:podcast$3.episodes.edit', + ]); + $routes->post('activate', 'PluginController::activate/$1/$2', [ + 'as' => 'plugins-activate', + 'filter' => 'permission:plugins.manage', + ]); + $routes->post('deactivate', 'PluginController::deactivate/$1/$2', [ + 'as' => 'plugins-deactivate', + 'filter' => 'permission:plugins.manage', + ]); + $routes->get('uninstall', 'PluginController::uninstall/$1/$2', [ + 'as' => 'plugins-uninstall', + 'filter' => 'permission:plugins.manage', + ]); + }); + }); + }, +); diff --git a/modules/Plugins/Config/Services.php b/modules/Plugins/Config/Services.php new file mode 100644 index 00000000..ea91b4d7 --- /dev/null +++ b/modules/Plugins/Config/Services.php @@ -0,0 +1,37 @@ +repositoryUrl, WRITEPATH, $config->folder, WRITEPATH . 'temp'); + } +} diff --git a/modules/Plugins/Controllers/PluginController.php b/modules/Plugins/Controllers/PluginController.php new file mode 100644 index 00000000..2028cdec --- /dev/null +++ b/modules/Plugins/Controllers/PluginController.php @@ -0,0 +1,362 @@ +plugins = service('plugins'); + } + + public function installed(): string + { + $pager = service('pager'); + + $page = (int) ($this->request->getGet('page') ?? 1); + $perPage = 10; + $total = $this->plugins->getInstalledCount(); + + $pager_links = $pager->makeLinks($page, $perPage, $total); + + $this->setHtmlHead(lang('Plugins.installed')); + return view('plugins/installed', [ + 'total' => $total, + 'plugins' => $this->plugins->getPlugins($page, $perPage), + 'pager_links' => $pager_links, + ]); + } + + public function vendor(string $vendor): string + { + $vendorPlugins = $this->plugins->getVendorPlugins($vendor); + + $this->setHtmlHead(lang('Plugins.installed')); + replace_breadcrumb_params([ + $vendor => $vendor, + ]); + return view('plugins/installed', [ + 'total' => count($vendorPlugins), + 'plugins' => $vendorPlugins, + 'pager_links' => '', + ]); + } + + public function view(string $vendor, string $package): string + { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->setHtmlHead($plugin->getTitle()); + replace_breadcrumb_params([ + $vendor => $vendor, + $package => $package, + ]); + return view('plugins/view', [ + 'plugin' => $plugin, + ]); + } + + public function settingsView( + string $vendor, + string $package, + ?string $podcastId = null, + ?string $episodeId = null, + ): string { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $type = 'general'; + $context = null; + $breadcrumbReplacements = [ + $vendor => $vendor, + $package => $package, + ]; + $data = [ + 'plugin' => $plugin, + ]; + + if ($podcastId !== null) { + $podcast = new PodcastModel() + ->getPodcastById((int) $podcastId); + + if (! $podcast instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + $type = 'podcast'; + $context = ['podcast', (int) $podcastId]; + $breadcrumbReplacements[0] = $podcast->handle; + $data['podcast'] = $podcast; + } + + if ($episodeId !== null) { + $episode = new EpisodeModel() + ->getEpisodeById((int) $episodeId); + + if (! $episode instanceof Episode) { + throw PageNotFoundException::forPageNotFound(); + } + + $type = 'episode'; + $context = ['episode', (int) $episodeId]; + $breadcrumbReplacements[1] = $episode->title; + $data['episode'] = $episode; + } + + $fields = $plugin->getSettingsFields($type); + + if ($fields === []) { + throw PageNotFoundException::forPageNotFound(); + } + + $data['type'] = $type; + $data['context'] = $context; + $data['fields'] = $fields; + + helper('form'); + $this->setHtmlHead(lang('Plugins.settingsTitle', [ + 'pluginTitle' => $plugin->getTitle(), + 'type' => $type, + ])); + replace_breadcrumb_params($breadcrumbReplacements); + return view('plugins/settings', $data); + } + + public function settingsAction( + string $vendor, + string $package, + ?string $podcastId = null, + ?string $episodeId = null, + ): RedirectResponse { + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $type = 'general'; + $context = null; + if ($podcastId !== null) { + $type = 'podcast'; + $context = ['podcast', (int) $podcastId]; + } + + if ($episodeId !== null) { + $type = 'episode'; + $context = ['episode', (int) $episodeId]; + } + + // construct validation rules first + $rules = []; + foreach ($plugin->getSettingsFields($type) as $field) { + $typeRules = $this->plugins::FIELDS_VALIDATIONS[$field->type]; + if (! in_array('permit_empty', $typeRules, true)) { + $typeRules[] = $field->optional ? 'permit_empty' : 'required'; + } + + if ($field->multiple) { + if ($field instanceof Group) { + foreach ($field->fields as $subField) { + $typeRules = $this->plugins::FIELDS_VALIDATIONS[$subField->type]; + if (! in_array('permit_empty', $typeRules, true)) { + $typeRules[] = $subField->optional ? 'permit_empty' : 'required'; + } + + $rules[sprintf('%s.*.%s', $field->key, $subField->key)] = [ + ...$typeRules, + ...$subField->validationRules, + ]; + } + } else { + $rules[$field->key . '.*'] = [...$typeRules, ...$field->validationRules]; + } + } elseif ($field instanceof Group) { + foreach ($field->fields as $subField) { + $typeRules = $this->plugins::FIELDS_VALIDATIONS[$subField->type]; + if (! in_array('permit_empty', $typeRules, true)) { + $typeRules[] = $subField->optional ? 'permit_empty' : 'required'; + } + + $rules[sprintf('%s.%s', $field->key, $subField->key)] = [ + ...$typeRules, + ...$subField->validationRules, + ]; + } + } else { + $rules[$field->key] = [...$typeRules, ...$field->validationRules]; + } + } + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $validatedData = $this->validator->getValidated(); + + foreach ($plugin->getSettingsFields($type) as $field) { + $fieldValue = $validatedData[$field->key] ?? null; + + $this->plugins->setOption($plugin, $field->key, $this->castFieldValue($field, $fieldValue), $context); + } + + // clear cache after setting options + $plugin->clearCache(); + + return redirect()->back() + ->with('message', lang('Plugins.messages.saveSettingsSuccess', [ + 'pluginTitle' => $plugin->getTitle(), + ])); + } + + public function activate(string $vendor, string $package): RedirectResponse + { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->plugins->activate($plugin); + + // clear cache after activation + $plugin->clearCache(); + + return redirect()->back(); + } + + public function deactivate(string $vendor, string $package): RedirectResponse + { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->plugins->deactivate($plugin); + + // clear cache after deactivation + $plugin->clearCache(); + + return redirect()->back(); + } + + public function uninstall(string $vendor, string $package): RedirectResponse + { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->plugins->uninstall($plugin); + + return redirect()->back(); + } + + private function castFieldValue(Field $field, mixed $fieldValue): mixed + { + if ($fieldValue === '' || $fieldValue === null) { + return null; + } + + $value = null; + if ($field->multiple) { + $value = []; + foreach ($fieldValue as $key => $val) { + if ($val === '') { + continue; + } + + if ($field instanceof Group) { + foreach ($val as $subKey => $subVal) { + /** @var Field|false $subField */ + $subField = array_column($field->fields, null, 'key')[$subKey] ?? false; + if (! $subField) { + continue; + } + + $v = $this->castValue($subVal, $subField->type); + if ($v) { + $value[$key][$subKey] = $v; + } + } + } else { + $value[$key] = $this->castValue($val, $field->type); + } + } + } elseif ($field instanceof Group) { + foreach ($fieldValue as $subKey => $subVal) { + /** @var Field|false $subField */ + $subField = array_column($field->fields, null, 'key')[$subKey] ?? false; + if (! $subField) { + continue; + } + + $v = $this->castValue($subVal, $subField->type); + if ($v) { + $value[$subKey] = $v; + } + } + } else { + $value = $this->castValue($fieldValue, $field->type); + } + + return $value === [] ? null : $value; + } + + private function castValue(mixed $value, string $type): mixed + { + if ($value === '' || $value === null) { + return null; + } + + return match ($this->plugins::FIELDS_CASTS[$type] ?? 'text') { + 'bool' => $value === 'yes', + 'int' => (int) $value, + 'uri' => new URI($value), + 'datetime' => Time::createFromFormat( + 'Y-m-d H:i', + $value, + $this->request->getPost('client_timezone'), + )->setTimezone(app_timezone()), + 'markdown' => new Markdown($value), + 'rss' => new RSS($value), + default => $value, + }; + } +} diff --git a/modules/Plugins/Core/BasePlugin.php b/modules/Plugins/Core/BasePlugin.php new file mode 100644 index 00000000..995fa272 --- /dev/null +++ b/modules/Plugins/Core/BasePlugin.php @@ -0,0 +1,389 @@ +key = sprintf('%s/%s', $vendor, $package); + + // TODO: cache manifest data + $manifestPath = $directory . '/manifest.json'; + + $this->manifest = new Manifest($this->key); + $this->manifest->loadFromFile($manifestPath); + + // check compatibility with Castopod version + if ($this->manifest->minCastopodVersion !== null && version_compare( + CP_VERSION, + $this->manifest->minCastopodVersion, + '<', + )) { + $this->status = PluginStatus::INCOMPATIBLE; + } else { + $this->status = get_plugin_setting($this->key, 'active') ? PluginStatus::ACTIVE : PluginStatus::INACTIVE; + } + + $this->iconSrc = $this->loadIcon($directory . '/icon.svg'); + + $this->readmeHTML = $this->loadMarkdownAsHTML($directory . '/README.md'); + + $this->licenseHTML = $this->loadMarkdownAsHTML($directory . '/LICENSE.md'); + } + + /** + * @param list|string $value + */ + public function __set(string $name, array|string $value): void + { + $this->{$name} = $value; + } + + #[Override] + public function rssBeforeChannel(Podcast $podcast): void + { + } + + #[Override] + public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void + { + } + + #[Override] + public function rssBeforeItem(Episode $episode): void + { + } + + #[Override] + public function rssAfterItem(Episode $episode, RssFeed $item): void + { + } + + #[Override] + public function siteHead(HtmlHead $head): void + { + } + + final public function getGeneralSetting(string $key): mixed + { + return get_plugin_setting($this->key, $key); + } + + final public function getPodcastSetting(int $podcastId, string $key): mixed + { + return get_plugin_setting($this->key, $key, ['podcast', $podcastId]); + } + + final public function getEpisodeSetting(int $episodeId, string $key): mixed + { + return get_plugin_setting($this->key, $key, ['episode', $episodeId]); + } + + final public function clearCache(): void + { + foreach ($this->getHooks() as $hook) { + foreach (Plugins::CACHE_MAP[$hook] ?? [] as $cacheGlob) { + cache()->deleteMatching($cacheGlob); + } + } + } + + /** + * @return bool true on success, false on failure + */ + final public function activate(): bool + { + if ($this->status === PluginStatus::ACTIVE) { + return false; + } + + $this->setStatus(PluginStatus::ACTIVE); + + if ($this->status === PluginStatus::INACTIVE) { + return false; + } + + set_plugin_setting($this->key, 'active', true); + return true; + } + + final public function deactivate(): bool + { + if ($this->status !== PluginStatus::ACTIVE) { + return false; + } + + $this->setStatus(PluginStatus::INACTIVE); + set_plugin_setting($this->key, 'active', false); + + return true; + } + + final public function getStatus(): PluginStatus + { + return $this->status; + } + + final public function getDirectory(): string + { + return $this->directory; + } + + /** + * @return array + */ + final public function getManifestErrors(): array + { + return Manifest::getPluginErrors($this->key); + } + + final public function isHookDeclared(string $name): bool + { + return in_array($name, $this->manifest->hooks, true); + } + + final public function getVersion(): string + { + return $this->manifest->version; + } + + final public function getHomepage(): ?URI + { + return $this->manifest->homepage; + } + + final public function getRepository(): ?Repository + { + return $this->manifest->repository; + } + + /** + * @return list + */ + final public function getKeywords(): array + { + return $this->manifest->keywords; + } + + /** + * @return Person[] + */ + final public function getAuthors(): array + { + return $this->manifest->authors; + } + + final public function getIconSrc(): string + { + return $this->iconSrc; + } + + /** + * @return Field[] + */ + final public function getSettingsFields(string $type): array + { + $settings = $this->manifest->settings; + if (! $settings instanceof Settings) { + return []; + } + + return $settings->{$type}; + } + + final public function getMinCastopodVersion(): string + { + return $this->manifest->minCastopodVersion ?? ''; + } + + /** + * @return list + */ + final public function getHooks(): array + { + return $this->manifest->hooks; + } + + final public function getKey(): string + { + return $this->key; + } + + final public function getVendor(): string + { + return $this->vendor; + } + + final public function getPackage(): string + { + return $this->package; + } + + final public function getTitle(): string + { + $key = sprintf('Plugin.%s.title', $this->key); + /** @var string $title */ + $title = lang($key); + + if ($title === $key) { + return $this->manifest->name; + } + + return $title; + } + + final public function getDescription(): string + { + $key = sprintf('Plugin.%s.description', $this->key); + + /** @var string $description */ + $description = lang($key); + + if ($description === $key) { + return esc($this->manifest->description); + } + + return $description; + } + + final public function getReadmeHTML(): ?string + { + return $this->readmeHTML; + } + + final public function getLicense(): string + { + return $this->manifest->license ?? 'UNLICENSED'; + } + + final public function getLicenseHTML(): ?string + { + return $this->licenseHTML; + } + + /** + * @param PluginStatus::ACTIVE|PluginStatus::INACTIVE $value + */ + final protected function setStatus(PluginStatus $value): self + { + if ($this->getManifestErrors() !== []) { + $this->status = PluginStatus::INVALID; + + return $this; + } + + $this->status = $value; + + return $this; + } + + final protected function getOption(string $option): mixed + { + return get_plugin_setting($this->key, $option); + } + + final protected function setOption(string $option, mixed $value = null): void + { + set_plugin_setting($this->key, $option, $value); + } + + private function loadIcon(string $path): string + { + // TODO: cache icon + $svgIcon = @file_get_contents($path); + + if (! $svgIcon) { + return "data:image/svg+xml;utf8,%3Csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 64 64'%3E%3Cpath fill='%2300564A' d='M0 0h64v64H0z'%2F%3E%3Cpath fill='%23E7F9E4' d='M25.3 18.7a5 5 0 1 1 9.7 1.6h7c1 0 1.7.8 1.7 1.7v7a5 5 0 1 1 0 9.4v7c0 .9-.8 1.6-1.7 1.6H18.7c-1 0-1.7-.7-1.7-1.7V22c0-1 .7-1.7 1.7-1.7h7a5 5 0 0 1-.4-1.6Z'%2F%3E%3C%2Fsvg%3E"; + } + + $encodedIcon = rawurlencode(str_replace(["\r", "\n"], ' ', $svgIcon)); + return 'data:image/svg+xml;utf8,' . str_replace( + ['%20', '%22', '%27', '%3D'], + [' ', "'", "'", '='], + $encodedIcon, + ); + } + + private function loadMarkdownAsHTML(string $path): ?string + { + // TODO: cache readme + $readmeMD = @file_get_contents($path); + + if (! $readmeMD) { + return null; + } + + $environment = new Environment([ + 'html_input' => 'escape', + 'allow_unsafe_links' => false, + 'host' => new URI(base_url()) + ->getHost(), + ]); + $environment->addExtension(new CommonMarkCoreExtension()); + + $environment->addExtension(new GithubFlavoredMarkdownExtension()); + $environment->addExtension(new SmartPunctExtension()); + + $environment->addEventListener( + DocumentParsedEvent::class, + static function (DocumentParsedEvent $event): void { + new ExternalLinkProcessor() + ->onDocumentParsed($event); + }, + ); + $environment->addEventListener( + DocumentParsedEvent::class, + static function (DocumentParsedEvent $event): void { + new ExternalImageProcessor() + ->onDocumentParsed($event); + }, + ); + + $converter = new MarkdownConverter($environment); + + return $converter->convert($readmeMD) + ->getContent(); + } +} diff --git a/modules/Plugins/Core/Markdown.php b/modules/Plugins/Core/Markdown.php new file mode 100644 index 00000000..2e6b1af7 --- /dev/null +++ b/modules/Plugins/Core/Markdown.php @@ -0,0 +1,46 @@ +markdown; + } + + public function renderHTML(): string + { + $config = [ + 'html_input' => 'escape', + 'allow_unsafe_links' => false, + ]; + + $environment = new Environment($config); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new AutolinkExtension()); + $environment->addExtension(new SmartPunctExtension()); + $environment->addExtension(new DisallowedRawHtmlExtension()); + + $converter = new MarkdownConverter($environment); + + return (string) $converter->convert($this->markdown); + } +} diff --git a/modules/Plugins/Core/PluginInterface.php b/modules/Plugins/Core/PluginInterface.php new file mode 100644 index 00000000..bd30aad5 --- /dev/null +++ b/modules/Plugins/Core/PluginInterface.php @@ -0,0 +1,23 @@ + + */ + public const HOOKS = ['rssBeforeChannel', 'rssAfterChannel', 'rssBeforeItem', 'rssAfterItem', 'siteHead']; + + public const CACHE_MAP = [ + 'rssBeforeChannel' => ['podcast*feed*'], + 'rssAfterChannel' => ['podcast*feed*'], + 'rssBeforeItem' => ['podcast*feed*'], + 'rssAfterItem' => ['podcast*feed*'], + 'siteHead' => ['page*'], + ]; + + public const FIELDS_VALIDATIONS = [ + 'checkbox' => ['permit_empty'], + 'datetime' => ['valid_date[Y-m-d H:i]'], + 'email' => ['valid_email'], + 'group' => ['permit_empty', 'is_list'], + 'html' => ['string'], + 'markdown' => ['string'], + 'number' => ['integer'], + 'radio-group' => ['string'], + 'rss' => ['string'], + 'select' => ['string'], + 'select-multiple' => ['permit_empty', 'is_list'], + 'text' => ['string'], + 'textarea' => ['string'], + 'toggler' => ['permit_empty'], + 'url' => ['valid_url_strict'], + ]; + + public const FIELDS_CASTS = [ + 'checkbox' => 'bool', + 'datetime' => 'datetime', + 'markdown' => 'markdown', + 'number' => 'int', + 'rss' => 'rss', + 'toggler' => 'bool', + 'url' => 'uri', + ]; + + /** + * @var array + */ + protected static array $plugins = []; + + /** + * @var array + */ + protected static array $pluginsByVendor = []; + + protected static int $installedCount = 0; + + protected static int $activeCount = 0; + + public function __construct( + protected PluginsConfig $config, + ) { + helper('plugins'); + + $this->registerPlugins(); + } + + /** + * @param value-of $name + * @param array $arguments + */ + public function __call(string $name, array $arguments): void + { + if (! in_array($name, static::HOOKS, true)) { + return; + } + + $this->runHook($name, $arguments); + } + + /** + * @return array + */ + public function getPlugins(int $page = 1, int $perPage = 12): array + { + return array_slice(static::$plugins, (($page - 1) * $perPage), $perPage); + } + + /** + * @return array + */ + public function getAllPlugins(): array + { + return static::$plugins; + } + + /** + * @return array + */ + public function getActivePlugins(): array + { + $activePlugins = []; + foreach (static::$plugins as $plugin) { + if ($plugin->getStatus() === PluginStatus::ACTIVE) { + $activePlugins[] = $plugin; + } + } + + return $activePlugins; + } + + /** + * @return array + */ + public function getPluginsWithPodcastSettings(): array + { + $pluginsWithPodcastSettings = []; + foreach (static::$plugins as $plugin) { + if ($plugin->getStatus() !== PluginStatus::ACTIVE) { + continue; + } + + if ($plugin->getSettingsFields('podcast') === []) { + continue; + } + + $pluginsWithPodcastSettings[] = $plugin; + } + + return $pluginsWithPodcastSettings; + } + + /** + * @return array + */ + public function getPluginsWithEpisodeSettings(): array + { + $pluginsWithEpisodeSettings = []; + foreach (static::$plugins as $plugin) { + if ($plugin->getStatus() !== PluginStatus::ACTIVE) { + continue; + } + + if ($plugin->getSettingsFields('episode') === []) { + continue; + } + + $pluginsWithEpisodeSettings[] = $plugin; + } + + return $pluginsWithEpisodeSettings; + } + + /** + * @return array + */ + public function getVendorPlugins(string $vendor): array + { + return static::$pluginsByVendor[$vendor] ?? []; + } + + public function getPlugin(string $vendor, string $package): ?BasePlugin + { + foreach ($this->getVendorPlugins($vendor) as $plugin) { + if ($plugin->getPackage() === $package) { + return $plugin; + } + } + + return null; + } + + public function getPluginByKey(string $key): ?BasePlugin + { + if (! str_contains($key, '/')) { + return null; + } + + $keyArray = explode('/', $key); + return $this->getPlugin($keyArray[0], $keyArray[1]); + } + + /** + * @param value-of $name + * @param array $arguments + */ + public function runHook(string $name, array $arguments): void + { + foreach (static::$plugins as $plugin) { + // only run hook on active plugins + if ($plugin->getStatus() !== PluginStatus::ACTIVE) { + continue; + } + + if (! $plugin->isHookDeclared($name)) { + continue; + } + + $plugin->{$name}(...$arguments); + } + } + + public function activate(BasePlugin $plugin): void + { + if ($plugin->activate()) { + ++self::$activeCount; + } + } + + public function deactivate(BasePlugin $plugin): void + { + if ($plugin->deactivate()) { + --self::$activeCount; + } + } + + /** + * @param ?array{'podcast'|'episode',int} $additionalContext + */ + public function setOption(BasePlugin $plugin, string $name, mixed $value, ?array $additionalContext = null): void + { + set_plugin_setting($plugin->getKey(), $name, $value, $additionalContext); + } + + public function getInstalledCount(): int + { + return static::$installedCount; + } + + public function getActiveCount(): int + { + return static::$activeCount; + } + + public function uninstall(BasePlugin $plugin): bool + { + // remove all settings data + $db = Database::connect(); + $builder = $db->table('settings'); + + $db->transStart(); + $builder->where('class', self::class); + $builder->like('context', sprintf('plugin:%s', $plugin->getKey() . '%')); + + if (! $builder->delete()) { + $db->transRollback(); + return false; + } + + return $db->transCommit(); + } + + protected function registerPlugins(): void + { + // search for plugins in plugins folder + $pluginsDirectories = glob($this->config->folder . '*/*', GLOB_ONLYDIR); + + if ($pluginsDirectories === false || $pluginsDirectories === []) { + return; + } + + foreach ($pluginsDirectories as $pluginDirectory) { + $vendor = basename(dirname($pluginDirectory)); + $package = basename($pluginDirectory); + + if (preg_match('~' . PLUGINS_KEY_PATTERN . '~', $vendor . '/' . $package) === false) { + continue; + } + + $className = str_replace( + ' ', + '', + ucwords(str_replace(['-', '_', '.'], ' ', $vendor . ' ' . $package)) . 'Plugin', + ); + + $pluginFile = $pluginDirectory . DIRECTORY_SEPARATOR . 'Plugin.php'; + spl_autoload_register(static function ($class) use (&$className, &$pluginFile): void { + if ($class !== $className) { + return; + } + + if (! file_exists($pluginFile)) { + return; + } + + include_once $pluginFile; + }, true); + + if (! class_exists($className)) { + continue; + } + + $plugin = new $className($vendor, $package, $pluginDirectory); + if (! $plugin instanceof BasePlugin) { + continue; + } + + static::$plugins[] = $plugin; + static::$pluginsByVendor[$vendor][] = $plugin; + ++static::$installedCount; + + if ($plugin->getStatus() === PluginStatus::ACTIVE) { + ++static::$activeCount; + } + } + } +} diff --git a/modules/Plugins/Core/RSS.php b/modules/Plugins/Core/RSS.php new file mode 100644 index 00000000..36a96fe7 --- /dev/null +++ b/modules/Plugins/Core/RSS.php @@ -0,0 +1,43 @@ +rss; + } + + /** + * @return ?RssFeed[] + */ + public function toSimpleRSS(): ?array + { + try { + $rssFeed = new RssFeed("{$this->rss}"); + } catch (Exception) { + return null; + } + + return [ + ...$rssFeed->children(), + ...$rssFeed->children(RssFeed::ATOM_NS, true), + ...$rssFeed->children(RssFeed::ITUNES_NS, true), + ...$rssFeed->children(RssFeed::PODCAST_NS, true), + ]; + } +} diff --git a/modules/Plugins/ExternalImageProcessor.php b/modules/Plugins/ExternalImageProcessor.php new file mode 100644 index 00000000..f5947aa3 --- /dev/null +++ b/modules/Plugins/ExternalImageProcessor.php @@ -0,0 +1,45 @@ +getDocument(); + $walker = $document->walker(); + while ($event = $walker->next()) { + $node = $event->getNode(); + + // Only stop at Link nodes when we first encounter them + if (! ($node instanceof Image) || ! $event->isEntering()) { + continue; + } + + $url = $node->getUrl(); + if ($this->isUrlExternal($url)) { + $node->detach(); + } + } + } + + private function isUrlExternal(string $url): bool + { + // Only look at http and https URLs + if (! preg_match('/^https?:\/\//', $url)) { + return false; + } + + $host = parse_url($url, PHP_URL_HOST); + + // TODO: load from environment's config + return $host !== new URI(base_url()) + ->getHost(); + } +} diff --git a/modules/Plugins/ExternalLinkProcessor.php b/modules/Plugins/ExternalLinkProcessor.php new file mode 100644 index 00000000..0297609c --- /dev/null +++ b/modules/Plugins/ExternalLinkProcessor.php @@ -0,0 +1,46 @@ +getDocument(); + $walker = $document->walker(); + while ($event = $walker->next()) { + $node = $event->getNode(); + + // Only stop at Link nodes when we first encounter them + if (! ($node instanceof Link) || ! $event->isEntering()) { + continue; + } + + $url = $node->getUrl(); + if ($this->isUrlExternal($url)) { + $node->data->append('attributes/target', '_blank'); + $node->data->append('attributes/rel', 'noopener noreferrer'); + } + } + } + + private function isUrlExternal(string $url): bool + { + // Only look at http and https URLs + if (! preg_match('/^https?:\/\//', $url)) { + return false; + } + + $host = parse_url($url, PHP_URL_HOST); + + // TODO: load from environment's config + return $host !== new URI(base_url()) + ->getHost(); + } +} diff --git a/modules/Plugins/Helpers/plugins_helper.php b/modules/Plugins/Helpers/plugins_helper.php new file mode 100644 index 00000000..3a2c926d --- /dev/null +++ b/modules/Plugins/Helpers/plugins_helper.php @@ -0,0 +1,83 @@ +get($key, $context); + } +} + +if (! function_exists('set_plugin_setting')) { + /** + * @param ?array{'podcast'|'episode',int} $additionalContext + */ + function set_plugin_setting( + string $pluginKey, + string $option, + mixed $value = null, + ?array $additionalContext = null, + ): void { + $key = sprintf('Plugins.%s', $option); + $context = sprintf('plugin:%s', $pluginKey); + + if ($additionalContext !== null) { + $context .= sprintf('+%s:%d', ...$additionalContext); + } + + setting() + ->set($key, $value, $context); + } +} + +if (! function_exists('load_plugins_translations')) { + /** + * @return array + */ + function load_plugins_translations(string $locale): array + { + $allPlugins = plugins() + ->getAllPlugins(); + + $translations = []; + foreach ($allPlugins as $plugin) { + $file = $plugin->getDirectory() . DIRECTORY_SEPARATOR . 'i18n' . DIRECTORY_SEPARATOR . $locale . '.json'; + + $jsonContents = @file_get_contents($file); + + if (! $jsonContents) { + continue; + } + + $contents = json_decode($jsonContents, true); + + if (! $contents) { + continue; + } + + $translations[$plugin->getKey()] = $contents; + } + + return $translations; + } +} diff --git a/modules/Plugins/Language/br/Plugin.php b/modules/Plugins/Language/br/Plugin.php new file mode 100644 index 00000000..36a992fa --- /dev/null +++ b/modules/Plugins/Language/br/Plugin.php @@ -0,0 +1,8 @@ + 'Plugins installed', + 'about' => 'About', + 'website' => 'Website', + 'repository' => 'Code repository', + 'authors' => 'Authors', + 'author_email' => 'Email {authorName}', + 'author_homepage' => '{authorName} homepage', + 'declaredHooks' => 'Declared hooks', + 'settings' => 'Settings', + 'settingsTitle' => '{type, select, + podcast {{pluginTitle} podcast settings} + episode {{pluginTitle} episode settings} + other {{pluginTitle} general settings} + }', + 'view' => 'View', + 'activate' => 'Activate', + 'deactivate' => 'Deactivate', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'invalid' => 'Invalid', + 'incompatible' => 'Incompatible', + 'incompatible_hint' => 'Plugin requires Castopod v{minCastopodVersion} minimum.', + 'uninstall' => 'Uninstall', + 'keywords' => [ + 'podcasting20' => 'Podcasting 2.0', + 'seo' => 'SEO', + 'analytics' => 'Analytics', + 'accessibility' => 'Accessibility', + ], + 'noDescription' => 'No description', + 'noReadme' => 'No README file found.', + 'messages' => [ + 'saveSettingsSuccess' => '{pluginTitle} settings were successfully saved!', + ], + 'errors' => [ + 'manifestError' => 'Plugin manifest has errors', + 'manifestMissing' => 'Plugin manifest "{manifestPath}" is missing.', + 'manifestJsonInvalid' => 'Plugin manifest "{manifestPath}" is not a valid JSON.', + ], +]; diff --git a/modules/Plugins/Language/es/Plugin.php b/modules/Plugins/Language/es/Plugin.php new file mode 100644 index 00000000..36a992fa --- /dev/null +++ b/modules/Plugins/Language/es/Plugin.php @@ -0,0 +1,8 @@ + 'permit_empty|in_list[checkbox,datetime,email,group,html,markdown,number,radio-group,rss,select-multiple,select,text,textarea,toggler,url]', + 'key' => 'required|alpha_dash', + 'label' => 'required|string', + 'hint' => 'permit_empty|string', + 'helper' => 'permit_empty|string', + 'validationRules' => 'permit_empty|is_string_or_list', + 'optional' => 'permit_empty|is_boolean', + 'multiple' => 'permit_empty|is_boolean', + ]; + + protected string $type = 'text'; + + protected string $key; + + protected string $label; + + protected string $hint = ''; + + protected string $helper = ''; + + /** + * @var string[] + */ + protected array $validationRules = []; + + protected bool $optional = false; + + protected bool $multiple = false; + + public function getLabel(): string + { + return $this->getTranslated('label'); + } + + public function getHint(): string + { + return $this->getTranslated('hint'); + } + + public function getHelper(): string + { + return $this->getTranslated('helper'); + } + + /** + * @param string|list $values + */ + public function setValidationRules(string|array $values): void + { + $validationRules = []; + if (is_string($values)) { + $validationRules = explode('|', $values); + } + + $allowedRules = [ + 'alpha', + 'alpha_dash', + 'alpha_numeric', + 'alpha_numeric_punct', + 'alpha_numeric_space', + 'alpha_space', + 'decimal', + 'differs', + 'exact_length', + 'greater_than', + 'greater_than_equal_to', + 'hex', + 'in_list', + 'integer', + 'is_natural', + 'is_natural_no_zero', + 'less_than', + 'less_than_equal_to', + 'max_length', + 'min_length', + 'not_in_list', + 'regex_match', + 'valid_base64', + 'valid_date', + ]; + foreach ($validationRules as $rule) { + foreach ($allowedRules as $allowedRule) { + if (str_starts_with($rule, $allowedRule)) { + $this->validationRules[] = $rule; + } + } + } + } + + public function render(string $name, mixed $value, string $class = ''): string + { + throw new RuntimeException('Render function not defined in parent Field class'); + } + + private function getTranslated(string $property): string + { + $key = sprintf('Plugin.%s.settings.%s.%s.%s', $this->pluginKey, $this->type, $this->key, $property); + + /** @var string $i18nField */ + $i18nField = lang($key); + + if ($this->{$property} === '' || $i18nField === $key) { + return esc($this->{$property}); + } + + return esc($i18nField); + } +} diff --git a/modules/Plugins/Manifest/FieldInterface.php b/modules/Plugins/Manifest/FieldInterface.php new file mode 100644 index 00000000..2fcbaa23 --- /dev/null +++ b/modules/Plugins/Manifest/FieldInterface.php @@ -0,0 +1,10 @@ + 'permit_empty|is_boolean', + ]; + + protected bool $defaultValue = false; + + public function render(string $name, mixed $value, string $class = ''): string + { + $value = $value ? 'yes' : ''; + return <<{$this->label} + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Datetime.php b/modules/Plugins/Manifest/Fields/Datetime.php new file mode 100644 index 00000000..d7a5826f --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Datetime.php @@ -0,0 +1,37 @@ + 'permit_empty|valid_date', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Email.php b/modules/Plugins/Manifest/Fields/Email.php new file mode 100644 index 00000000..50f215f0 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Email.php @@ -0,0 +1,38 @@ + 'permit_empty|valid_email', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Group.php b/modules/Plugins/Manifest/Fields/Group.php new file mode 100644 index 00000000..2cb89f7d --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Group.php @@ -0,0 +1,36 @@ +injectRules(); + + parent::__construct($pluginKey); + } + + #[Override] + public function loadData(array $data): void + { + $data = $this->transformData($data); + + parent::loadData($data); + } + + public function render(string $name, mixed $value, string $class = ''): string + { + // TODO: render group, depending on multiple + throw new RuntimeException('Render function not defined in Group Field class'); + } +} diff --git a/modules/Plugins/Manifest/Fields/Html.php b/modules/Plugins/Manifest/Fields/Html.php new file mode 100644 index 00000000..f5f112cd --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Html.php @@ -0,0 +1,39 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $value = htmlspecialchars($value ?? ''); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Markdown.php b/modules/Plugins/Manifest/Fields/Markdown.php new file mode 100644 index 00000000..4105a628 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Markdown.php @@ -0,0 +1,37 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Number.php b/modules/Plugins/Manifest/Fields/Number.php new file mode 100644 index 00000000..949eea5a --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Number.php @@ -0,0 +1,38 @@ + 'permit_empty|numeric', + ]; + + protected ?int $defaultValue = null; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/RadioGroup.php b/modules/Plugins/Manifest/Fields/RadioGroup.php new file mode 100644 index 00000000..d7b38229 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/RadioGroup.php @@ -0,0 +1,57 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function __construct(string $pluginKey) + { + $this->injectRules(); + + parent::__construct($pluginKey); + } + + #[Override] + public function loadData(array $data): void + { + $data = $this->transformData($data); + + parent::loadData($data); + } + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $options = esc(json_encode($this->getOptionsArray())); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Rss.php b/modules/Plugins/Manifest/Fields/Rss.php new file mode 100644 index 00000000..3e085317 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Rss.php @@ -0,0 +1,40 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $value = htmlspecialchars((string) $value); + $defaultValue = esc($this->defaultValue); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Select.php b/modules/Plugins/Manifest/Fields/Select.php new file mode 100644 index 00000000..7fa8f9d2 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Select.php @@ -0,0 +1,64 @@ + 'permit_empty|string', + 'options' => 'is_list', + ]; + + protected array $casts = [ + 'options' => [Option::class], + ]; + + protected string $defaultValue = ''; + + public function __construct(string $pluginKey) + { + $this->injectRules(); + + parent::__construct($pluginKey); + } + + #[Override] + public function loadData(array $data): void + { + $data = $this->transformData($data); + + parent::loadData($data); + } + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $options = esc(json_encode($this->getOptionsArray())); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/SelectMultiple.php b/modules/Plugins/Manifest/Fields/SelectMultiple.php new file mode 100644 index 00000000..7a2e6e7f --- /dev/null +++ b/modules/Plugins/Manifest/Fields/SelectMultiple.php @@ -0,0 +1,63 @@ + $defaultValue + */ +class SelectMultiple extends Field +{ + use WithOptionsTrait; + + public static array $validation_rules = [ + 'defaultValue' => 'permit_empty|is_list', + ]; + + /** + * @var list + */ + protected array $defaultValue = []; + + public function __construct(string $pluginKey) + { + $this->injectRules(); + + parent::__construct($pluginKey); + } + + #[Override] + public function loadData(array $data): void + { + $data = $this->transformData($data); + + parent::loadData($data); + } + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $options = esc(json_encode($this->getOptionsArray())); + $value = esc(json_encode($value)); + $defaultValue = esc(json_encode($this->defaultValue)); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Text.php b/modules/Plugins/Manifest/Fields/Text.php new file mode 100644 index 00000000..67ee93aa --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Text.php @@ -0,0 +1,37 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Textarea.php b/modules/Plugins/Manifest/Fields/Textarea.php new file mode 100644 index 00000000..afb025a5 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Textarea.php @@ -0,0 +1,37 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Toggler.php b/modules/Plugins/Manifest/Fields/Toggler.php new file mode 100644 index 00000000..655dc8df --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Toggler.php @@ -0,0 +1,34 @@ + 'permit_empty|is_boolean', + ]; + + protected bool $defaultValue = false; + + public function render(string $name, mixed $value, string $class = ''): string + { + $value = $value ? 'yes' : ''; + return <<{$this->label} + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Url.php b/modules/Plugins/Manifest/Fields/Url.php new file mode 100644 index 00000000..3d712d62 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Url.php @@ -0,0 +1,39 @@ + 'permit_empty|valid_url_strict', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Manifest.php b/modules/Plugins/Manifest/Manifest.php new file mode 100644 index 00000000..6c098d6c --- /dev/null +++ b/modules/Plugins/Manifest/Manifest.php @@ -0,0 +1,84 @@ + $keywords + * @property ?string $minCastopodVersion + * @property list $hooks + * @property ?Settings $settings + * @property ?Repository $repository + */ +class Manifest extends ManifestObject +{ + public static array $validation_rules = [ + 'name' => 'required|max_length[128]|regex_match[/^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*$/]', + 'version' => 'required|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]', + 'description' => 'permit_empty|max_length[256]', + 'authors' => 'permit_empty|is_list', + 'homepage' => 'permit_empty|valid_url_strict', + 'license' => 'permit_empty|string', + 'private' => 'permit_empty|is_boolean', + 'submodule' => 'permit_empty|is_boolean', + 'keywords.*' => 'permit_empty', + 'minCastopodVersion' => 'permit_empty|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]', + 'hooks.*' => 'permit_empty|in_list[rssBeforeChannel,rssAfterChannel,rssBeforeItem,rssAfterItem,siteHead]', + 'settings' => 'permit_empty|is_list', + 'repository' => 'permit_empty|is_list', + ]; + + protected array $casts = [ + 'authors' => [Person::class], + 'homepage' => URI::class, + 'settings' => Settings::class, + 'repository' => Repository::class, + ]; + + protected ?string $name = '???'; + + protected ?string $version = 'X.Y.Z'; + + protected ?string $description = null; + + /** + * @var Person[] + */ + protected array $authors = []; + + protected ?URI $homepage = null; + + protected ?string $license = null; + + protected bool $private = false; + + protected bool $submodule = false; + + /** + * @var list + */ + protected array $keywords = []; + + protected ?string $minCastopodVersion = null; + + /** + * @var list + */ + protected array $hooks = []; + + protected ?Settings $settings = null; + + protected ?Repository $repository = null; +} diff --git a/modules/Plugins/Manifest/ManifestObject.php b/modules/Plugins/Manifest/ManifestObject.php new file mode 100644 index 00000000..4070d24f --- /dev/null +++ b/modules/Plugins/Manifest/ManifestObject.php @@ -0,0 +1,166 @@ + + */ + public static array $validation_rules = []; + + /** + * @var array + */ + protected array $casts = []; + + /** + * @var array> + */ + protected static array $errors = []; + + public function __construct( + protected readonly string $pluginKey, + ) { + self::$errors[$pluginKey] = []; + + $class = static::class; + $validation_rules = []; + $casts = []; + while ($class = get_parent_class($class)) { + $validation_rules = [...$validation_rules, ...get_class_vars($class)['validation_rules']]; + $casts = [...$casts, ...get_class_vars($class)['casts']]; + } + + $this::$validation_rules = [...$validation_rules, ...$this::$validation_rules]; + $this->casts = [...$casts, ...$this->casts]; + } + + public function __get(string $name): mixed + { + if (property_exists($this, $name)) { + // if a get method exists for this property, return that + $method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name))); + if (method_exists($this, $method)) { + return $this->{$method}(); + } + + return $this->{$name}; + } + + throw new Exception('Undefined object property ' . static::class . '::' . $name); + } + + public function __isset(string $property): bool + { + return property_exists($this, $property); + } + + public function loadFromFile(string $manifestPath): void + { + $manifestContents = @file_get_contents($manifestPath); + + if (! $manifestContents) { + $manifestContents = '{}'; + $this->addError('manifest', lang('Plugins.errors.manifestMissing', [ + 'manifestPath' => $manifestPath, + ])); + } + + /** @var array|null $manifestData */ + $manifestData = json_decode($manifestContents, true); + + if ($manifestData === null) { + $manifestData = []; + $this->addError('manifest', lang('Plugins.errors.manifestJsonInvalid', [ + 'manifestPath' => $manifestPath, + ])); + } + + $this->loadData($manifestData); + } + + /** + * @param array $data + */ + public function loadData(array $data): void + { + /** @var Validation $validation */ + $validation = service('validation'); + + $validation->setRules($this::$validation_rules); + + if (! $validation->run($data)) { + foreach ($validation->getErrors() as $key => $message) { + $this->addError($key, $message); + } + + $validation->reset(); + } + + foreach ($validation->getValidated() as $key => $value) { + $method = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))); + if (is_callable([$this, $method])) { + $this->{$method}($value); + continue; + } + + if (array_key_exists($key, $this->casts)) { + $cast = $this->casts[$key]; + if (is_array($cast) && is_array($value)) { + foreach ($value as $valueKey => $valueElement) { + if (is_subclass_of($cast[0], self::class)) { + $manifestClass = $cast[0] === Field::class ? $this->getFieldClass( + $valueElement, + ) : $cast[0]; + $value[$valueKey] = new $manifestClass($this->pluginKey); + $value[$valueKey]->loadData($valueElement); + } else { + $value[$valueKey] = new $cast[0]($valueElement); + } + } + } elseif (is_subclass_of($cast, self::class)) { + $manifestClass = $cast === Field::class ? $this->getFieldClass($value) : $cast; + $valueElement = $value; + $value = new $manifestClass($this->pluginKey); + $value->loadData($valueElement ?? []); + } else { + $value = new $cast($value ?? []); + } + } + + $this->{$key} = $value; + } + } + + /** + * @return array + */ + public static function getPluginErrors(string $pluginKey): array + { + return self::$errors[$pluginKey]; + } + + protected function addError(string $errorKey, string $errorMessage): void + { + self::$errors[$this->pluginKey][$errorKey] = $errorMessage; + } + + /** + * @param array $data + */ + private function getFieldClass(array $data): string + { + $fieldType = $data['type'] ?? 'text'; + return rtrim(Field::class, "\Field") . '\\Fields\\' . str_replace( + ' ', + '', + ucwords(str_replace('-', ' ', $fieldType)), + ); + } +} diff --git a/modules/Plugins/Manifest/Option.php b/modules/Plugins/Manifest/Option.php new file mode 100644 index 00000000..ff15180b --- /dev/null +++ b/modules/Plugins/Manifest/Option.php @@ -0,0 +1,39 @@ + 'required|string', + 'value' => 'required|alpha_numeric_punct', + 'description' => 'permit_empty|string', + ]; + + protected string $label; + + protected string $value; + + protected string $description = ''; + + public function getTranslated(string $i18nKey, string $property): string + { + $key = sprintf('%s.%s.%s', $i18nKey, $this->value, $property); + + /** @var string $i18nField */ + $i18nField = lang($key); + + if ($this->{$property} === '' || $i18nField === $key) { + return esc($this->{$property}); + } + + return esc($i18nField); + } +} diff --git a/modules/Plugins/Manifest/Person.php b/modules/Plugins/Manifest/Person.php new file mode 100644 index 00000000..1a11c931 --- /dev/null +++ b/modules/Plugins/Manifest/Person.php @@ -0,0 +1,58 @@ +[^<>()]*)\s*(<(?.*)>)?\s*(\((?.*)\))?$/'; + + public static array $validation_rules = [ + 'name' => 'required', + 'email' => 'permit_empty|valid_email', + 'url' => 'permit_empty|valid_url_strict', + ]; + + /** + * @var array + */ + protected array $casts = [ + 'url' => URI::class, + ]; + + protected string $name; + + protected ?string $email = null; + + protected ?URI $url = null; + + #[Override] + public function loadData(array|string $data): void + { + if (is_string($data)) { + $result = preg_match(self::AUTHOR_STRING_PATTERN, $data, $matches); + + if (! $result) { + throw new Exception('Author string is not valid.'); + } + + $data = [ + 'name' => $matches['name'], + 'email' => $matches['email'] ?? null, + 'url' => $matches['url'] ?? null, + ]; + } + + parent::loadData($data); + } +} diff --git a/modules/Plugins/Manifest/Repository.php b/modules/Plugins/Manifest/Repository.php new file mode 100644 index 00000000..9a50398e --- /dev/null +++ b/modules/Plugins/Manifest/Repository.php @@ -0,0 +1,34 @@ + 'permit_empty|in_list[git]', + 'url' => 'required|valid_url_strict', + 'directory' => 'permit_empty', + ]; + + /** + * @var array + */ + protected array $casts = [ + 'url' => URI::class, + ]; + + protected string $type = 'git'; + + protected URI $url; + + protected ?string $directory = null; +} diff --git a/modules/Plugins/Manifest/Settings.php b/modules/Plugins/Manifest/Settings.php new file mode 100644 index 00000000..a30f3635 --- /dev/null +++ b/modules/Plugins/Manifest/Settings.php @@ -0,0 +1,62 @@ + 'permit_empty|is_list', + 'podcast' => 'permit_empty|is_list', + 'episode' => 'permit_empty|is_list', + ]; + + /** + * @var array + */ + protected array $casts = [ + 'general' => [Field::class], + 'podcast' => [Field::class], + 'episode' => [Field::class], + ]; + + /** + * @var Field[] + */ + protected array $general = []; + + /** + * @var Field[] + */ + protected array $podcast = []; + + /** + * @var Field[] + */ + protected array $episode = []; + + #[Override] + public function loadData(array $data): void + { + $newData = []; + foreach ($data as $key => $fields) { + $newFields = []; + foreach ($fields as $fieldKey => $field) { + $field['key'] = $fieldKey; + $newFields[] = $field; + } + + $newData[$key] = $newFields; + } + + parent::loadData($newData); + } +} diff --git a/modules/Plugins/Manifest/WithFieldsTrait.php b/modules/Plugins/Manifest/WithFieldsTrait.php new file mode 100644 index 00000000..72ac75a2 --- /dev/null +++ b/modules/Plugins/Manifest/WithFieldsTrait.php @@ -0,0 +1,45 @@ + 'is_list', + ]]; + $this->casts = [...$this->casts, ...[ + 'fields' => [Field::class], + ]]; + } + + /** + * @param array $data + * @return array + */ + public function transformData(array $data): array + { + if (array_key_exists('fields', $data)) { + $newFields = []; + foreach ($data['fields'] as $key => $field) { + $field['key'] = $key; + $newFields[] = $field; + } + + $data['fields'] = $newFields; + } + + return $data; + } +} diff --git a/modules/Plugins/Manifest/WithOptionsTrait.php b/modules/Plugins/Manifest/WithOptionsTrait.php new file mode 100644 index 00000000..a88b0ad3 --- /dev/null +++ b/modules/Plugins/Manifest/WithOptionsTrait.php @@ -0,0 +1,69 @@ + 'is_list', + ]]; + } + + if (isset($this->casts)) { + $this->casts = [...$this->casts, ...[ + 'options' => [Option::class], + ]]; + } + } + + /** + * @param array $data + * @return array + */ + public function transformData(array $data): array + { + if (array_key_exists('options', $data)) { + $newOptions = []; + foreach ($data['options'] as $key => $option) { + $option['value'] = $key; + $newOptions[] = $option; + } + + $data['options'] = $newOptions; + } + + return $data; + } + + /** + * @return array{label:string,value:string,description:string}[] + */ + public function getOptionsArray(): array + { + $i18nKey = sprintf('%s.settings.%s.%s.options', $this->pluginKey, $this->type, $this->key); + + $optionsArray = []; + foreach ($this->options as $option) { + $optionsArray[] = [ + 'value' => $option->value, + 'label' => $option->getTranslated($i18nKey, 'label'), + 'description' => $option->getTranslated($i18nKey, 'description'), + ]; + } + + return $optionsArray; + } +} diff --git a/modules/Plugins/Manifest/manifest.schema.json b/modules/Plugins/Manifest/manifest.schema.json new file mode 100644 index 00000000..08ecd0eb --- /dev/null +++ b/modules/Plugins/Manifest/manifest.schema.json @@ -0,0 +1,409 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "/schemas/manifest.json", + "title": "JSON schema for Castopod Plugins's manifest.json files", + "description": "The Castopod plugin manifest defines both metadata and behavior of a plugin", + "type": "object", + "properties": { + "name": { + "description": "The plugin name, including 'vendor-name/' prefix", + "type": "string", + "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*$", + "examples": ["acme/hello-world"] + }, + "version": { + "description": "The plugin's semantic version. See https://semver.org/", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "examples": ["1.0.0"] + }, + "minCastopodVersion": { + "description": "The minimal version of Castopod for which the plugin is compatible with.", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "examples": ["2.0"] + }, + "description": { + "description": "This helps people discover your plugin as it's listed in repositories", + "type": "string" + }, + "authors": { + "type": "array", + "items": { + "$ref": "#/$defs/person" + } + }, + "homepage": { + "description": "The URL to the plugin homepage", + "type": "string", + "format": "uri" + }, + "license": { + "description": "You should specify a license for your plugin so that people know how they are permitted to use it, and any restrictions you're placing on it.", + "default": "UNLICENSED", + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "AGPL-3.0-only", + "AGPL-3.0-or-later", + "Apache-2.0", + "BSL-1.0", + "GPL-3.0-only", + "GPL-3.0-or-later", + "LGPL-3.0-only", + "LGPL-3.0-or-later", + "MIT", + "MPL-2.0", + "Unlicense", + "UNLICENSED" + ] + } + ] + }, + "private": { + "type": "boolean", + "description": "If set to true, then repositories should refuse to publish it.", + "default": false + }, + "submodule": { + "type": "boolean", + "description": "Set to `true` if the plugin is part of a monorepo (i.e., in the same Git repository as other plugins). Releases should be tagged as `@` to ensure proper version indexing by plugin repositories.", + "default": false + }, + "keywords": { + "description": "This helps people discover your plugin as it's listed in repositories", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "accessibility", + "analytics", + "monetization", + "podcasting2", + "privacy", + "productivity", + "seo" + ] + } + ] + }, + "uniqueItems": true + }, + "hooks": { + "description": "The hooks used by the plugin.", + "type": "array", + "items": { + "enum": [ + "rssBeforeChannel", + "rssAfterChannel", + "rssBeforeItem", + "rssAfterItem", + "siteHead" + ] + }, + "uniqueItems": true + }, + "settings": { + "type": "object", + "properties": { + "general": { + "$ref": "#/$defs/fields" + }, + "podcast": { + "$ref": "#/$defs/fields" + }, + "episode": { + "$ref": "#/$defs/fields" + } + } + }, + "files": { + "description": "List of files to include in your plugin package. If you include a folder in the array, all files inside it will also be included.", + "type": "array", + "items": { + "type": "string" + } + }, + "repository": { + "description": "Specify the place where your plugin code lives. This is helpful for people who want to contribute.", + "type": ["object", "string"], + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "directory": { + "type": "string" + } + } + } + }, + "required": ["name", "version", "minCastopodVersion"], + "additionalProperties": false, + "$defs": { + "person": { + "description": "A person who has been involved in creating or maintaining this plugin.", + "type": ["object", "string"], + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "url": { + "type": "string", + "format": "uri" + } + } + }, + "fields": { + "type": "object", + "patternProperties": { + "^[A-Za-z]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/field" } + }, + "additionalProperties": false + }, + "field": { + "type": "object", + "properties": { + "type": { + "enum": [ + "checkbox", + "datetime", + "email", + "group", + "html", + "markdown", + "number", + "radio-group", + "rss", + "select-multiple", + "select", + "text", + "textarea", + "toggler", + "url" + ], + "default": "text" + }, + "label": { + "type": "string" + }, + "hint": { + "type": "string" + }, + "helper": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "validationRules": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "multiple": { + "type": "boolean" + } + }, + "required": ["label"], + "unevaluatedProperties": false, + "allOf": [ + { "$ref": "#/$defs/field-multiple-implies-options-is-required" }, + { "$ref": "#/$defs/require-fields-for-group-type" }, + { "$ref": "#/$defs/default-value-based-on-type" } + ] + }, + "option": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": ["label"], + "additionalProperties": false + }, + "field-multiple-implies-options-is-required": { + "if": { + "properties": { + "type": { + "enum": ["radio-group", "select", "select-multiple"] + } + } + }, + "then": { + "properties": { + "options": { + "type": "object", + "patternProperties": { + "^[A-Za-z0-9]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/option" } + }, + "additionalProperties": false + } + }, + "required": ["options"] + } + }, + "require-fields-for-group-type": { + "if": { + "properties": { + "type": { + "const": "group" + } + } + }, + "then": { + "properties": { + "fields": { + "type": "object", + "patternProperties": { + "^[A-Za-z]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/field" } + }, + "additionalProperties": false + } + }, + "required": ["fields"] + } + }, + "default-value-based-on-type": { + "allOf": [ + { + "if": { + "properties": { + "type": { + "enum": [ + "html", + "markdown", + "radio-group", + "rss", + "select", + "text", + "textarea" + ] + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "string" } + } + } + }, + { + "if": { + "properties": { + "type": { + "enum": ["checkbox", "toggler"] + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "boolean" } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "datetime" + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "string", "format": "date-time" } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "email" + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "string", "format": "email" } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "number" + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "number" } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "select-multiple" + } + } + }, + "then": { + "properties": { + "defaultValue": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "url" + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "string", "format": "uri" } + } + } + } + ] + } + } +} diff --git a/modules/PodcastImport/Commands/PodcastImport.php b/modules/PodcastImport/Commands/PodcastImport.php index b21f1c81..63c835e1 100644 --- a/modules/PodcastImport/Commands/PodcastImport.php +++ b/modules/PodcastImport/Commands/PodcastImport.php @@ -22,6 +22,7 @@ use Modules\Auth\Models\UserModel; use Modules\Platforms\Models\PlatformModel; use Modules\PodcastImport\Entities\PodcastImportTask; use Modules\PodcastImport\Entities\TaskStatus; +use Override; use PodcastFeed\PodcastFeed; use PodcastFeed\Tags\Podcast\PodcastPerson; use PodcastFeed\Tags\RSS\Channel; @@ -43,7 +44,7 @@ class PodcastImport extends BaseCommand protected ?Podcast $podcast = null; - public function init(): bool + public function init(): void { helper('podcast_import'); @@ -52,8 +53,8 @@ class PodcastImport extends BaseCommand $currentImport = current( array_filter( $importQueue, - static fn (PodcastImportTask $task): bool => $task->status === TaskStatus::Running - ) + static fn (PodcastImportTask $task): bool => $task->status === TaskStatus::Running, + ), ); if ($currentImport instanceof PodcastImportTask) { @@ -70,19 +71,20 @@ class PodcastImport extends BaseCommand // Get the next queued import $queuedImports = array_filter( $importQueue, - static fn (PodcastImportTask $task): bool => $task->status === TaskStatus::Queued + static fn (PodcastImportTask $task): bool => $task->status === TaskStatus::Queued, ); $nextImport = end($queuedImports); if (! $nextImport instanceof PodcastImportTask) { - // no queued import task, nothing to init - return false; + // no queued import task, stop process. + exit(0); } $this->importTask = $nextImport; // retrieve user who created import task - $user = (new UserModel())->find($this->importTask->created_by); + $user = new UserModel() + ->find($this->importTask->created_by); if (! $user instanceof User) { throw new Exception('Could not retrieve user with ID: ' . $this->importTask->created_by); @@ -94,10 +96,9 @@ class PodcastImport extends BaseCommand ini_set('user_agent', 'Castopod/' . CP_VERSION); $this->podcastFeed = new PodcastFeed($this->importTask->feed_url); - - return true; } + #[Override] public function run(array $params): void { // FIXME: getting named routes doesn't work from v4.3 anymore, so loading all routes before importing @@ -105,14 +106,12 @@ class PodcastImport extends BaseCommand ->loadRoutes(); try { - if (! $this->init()) { - return; - } + $this->init(); CLI::write('All good! Feed was parsed successfully!'); CLI::write( - 'Starting import for @' . $this->importTask->handle . ' using feed: ' . $this->importTask->feed_url + 'Starting import for @' . $this->importTask->handle . ' using feed: ' . $this->importTask->feed_url, ); // --- START IMPORT TASK --- @@ -129,10 +128,12 @@ class PodcastImport extends BaseCommand // check if podcast to be imported already exists by guid if exists or handle otherwise $podcastGuid = $this->podcastFeed->channel->podcast_guid->getValue(); if ($podcastGuid !== null) { - $podcast = (new PodcastModel())->where('guid', $podcastGuid) + $podcast = new PodcastModel() + ->where('guid', $podcastGuid) ->first(); } else { - $podcast = (new PodcastModel())->where('handle', $this->importTask->handle) + $podcast = new PodcastModel() + ->where('handle', $this->importTask->handle) ->first(); } @@ -163,7 +164,7 @@ class PodcastImport extends BaseCommand // set podcast publication date to the first ever published episode $this->podcast->published_at = $this->getOldestEpisodePublicationDate( - $this->podcast->id + $this->podcast->id, ) ?? $this->podcast->created_at; $podcastModel = new PodcastModel(); @@ -179,14 +180,14 @@ class PodcastImport extends BaseCommand $this->error($exception->getMessage()); log_message( 'critical', - 'Error when importing ' . $this->importTask?->feed_url . PHP_EOL . $exception->getMessage() . PHP_EOL . $exception->getTraceAsString() + 'Error when importing ' . $this->importTask?->feed_url . PHP_EOL . $exception->getMessage() . PHP_EOL . $exception->getTraceAsString(), ); } } private function getOldestEpisodePublicationDate(int $podcastId): ?Time { - $result = (new EpisodeModel()) + $result = new EpisodeModel() ->builder() ->selectMax('published_at', 'oldest_published_at') ->where('podcast_id', $podcastId) @@ -221,13 +222,13 @@ class PodcastImport extends BaseCommand if (($ownerName = $this->podcastFeed->channel->itunes_owner->itunes_name->getValue()) === null) { throw new Exception( - 'Missing podcast owner name. Please include an tag inside the tag.' + 'Missing podcast owner name. Please include an tag inside the tag.', ); } if (($ownerEmail = $this->podcastFeed->channel->itunes_owner->itunes_email->getValue()) === null) { throw new Exception( - 'Missing podcast owner email. Please include an tag inside the tag.' + 'Missing podcast owner email. Please include an tag inside the tag.', ); } @@ -359,7 +360,7 @@ class PodcastImport extends BaseCommand $this->podcast->id, $newPersonId, $personGroupSlug, - $personRoleSlug + $personRoleSlug, )) { throw new Exception(print_r($podcastPersonModel->errors(), true)); } @@ -482,12 +483,12 @@ class PodcastImport extends BaseCommand 'podcast_id' => $this->podcast->id, 'title' => $item->title->getValue(), 'slug' => slugify((string) $item->title->getValue(), 120) . '-' . strtolower( - random_string('alnum', 5) + random_string('alnum', 5), ), 'guid' => $item->guid->getValue(), 'audio' => download_file( $item->enclosure->getAttribute('url'), - $item->enclosure->getAttribute('type') + $item->enclosure->getAttribute('type'), ), 'description_markdown' => $htmlConverter->convert($showNotes), 'description_html' => $showNotes, @@ -522,7 +523,7 @@ class PodcastImport extends BaseCommand */ private function getImportedGUIDs(int $podcastId): array { - $result = (new EpisodeModel()) + $result = new EpisodeModel() ->builder() ->select('guid') ->where('podcast_id', $podcastId) @@ -580,7 +581,7 @@ class PodcastImport extends BaseCommand $episodeId, $newPersonId, $personGroupSlug, - $personRoleSlug + $personRoleSlug, )) { throw new Exception(print_r($episodePersonModel->errors(), true)); } diff --git a/modules/PodcastImport/Config/Routes.php b/modules/PodcastImport/Config/Routes.php index 7ba1da89..ede5be52 100644 --- a/modules/PodcastImport/Config/Routes.php +++ b/modules/PodcastImport/Config/Routes.php @@ -35,16 +35,16 @@ $routes->group( $routes->group('podcasts/(:num)', static function ($routes): void { $routes->get('imports', 'PodcastImportController::podcastList/$1', [ 'as' => 'podcast-imports', - 'filter' => 'permission:podcast#.manage-import', + 'filter' => 'permission:podcast$1.manage-import', ]); $routes->get('sync-feeds', 'PodcastImportController::syncImport/$1', [ 'as' => 'podcast-imports-sync', - 'filter' => 'permission:podcast#.manage-import', + 'filter' => 'permission:podcast$1.manage-import', ]); $routes->post('sync-feeds', 'PodcastImportController::syncImportAttempt/$1', [ 'as' => 'podcast-imports-sync', - 'filter' => 'permission:podcast#.manage-import', + 'filter' => 'permission:podcast$1.manage-import', ]); }); - } + }, ); diff --git a/modules/PodcastImport/Controllers/PodcastImportController.php b/modules/PodcastImport/Controllers/PodcastImportController.php index 3eb0f652..d15037d2 100644 --- a/modules/PodcastImport/Controllers/PodcastImportController.php +++ b/modules/PodcastImport/Controllers/PodcastImportController.php @@ -28,6 +28,7 @@ class PodcastImportController extends BaseController { helper('podcast_import'); + $this->setHtmlHead(lang('Podcast.all_imports')); return view('import/queue', [ 'podcastImportsQueue' => get_import_tasks(), ]); @@ -35,12 +36,13 @@ class PodcastImportController extends BaseController public function podcastList(int $podcastId): string { - if (! ($podcast = (new PodcastModel())->getPodcastById($podcastId)) instanceof Podcast) { + if (! ($podcast = new PodcastModel()->getPodcastById($podcastId)) instanceof Podcast) { throw PageNotFoundException::forPageNotFound(); } helper('podcast_import'); + $this->setHtmlHead(lang('Podcast.all_imports')); replace_breadcrumb_params([ 0 => $podcast->at_handle, ]); @@ -54,8 +56,10 @@ class PodcastImportController extends BaseController { helper(['form', 'misc']); - $languageOptions = (new LanguageModel())->getLanguageOptions(); - $categoryOptions = (new CategoryModel())->getCategoryOptions(); + $languageOptions = new LanguageModel() + ->getLanguageOptions(); + $categoryOptions = new CategoryModel() + ->getCategoryOptions(); $data = [ 'languageOptions' => $languageOptions, @@ -63,6 +67,7 @@ class PodcastImportController extends BaseController 'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')), ]; + $this->setHtmlHead(lang('Podcast.import')); return view('import/add_to_queue', $data); } @@ -106,12 +111,13 @@ class PodcastImportController extends BaseController public function syncImport(int $podcastId): string { - if (! ($podcast = (new PodcastModel())->getPodcastById($podcastId)) instanceof Podcast) { + if (! ($podcast = new PodcastModel()->getPodcastById($podcastId)) instanceof Podcast) { throw PageNotFoundException::forPageNotFound(); } helper('form'); + $this->setHtmlHead(lang('PodcastImport.syncForm.title')); replace_breadcrumb_params([ 0 => $podcast->at_handle, ]); @@ -122,7 +128,7 @@ class PodcastImportController extends BaseController public function syncImportAttempt(int $podcastId): RedirectResponse { - if (! ($podcast = (new PodcastModel())->getPodcastById($podcastId)) instanceof Podcast) { + if (! ($podcast = new PodcastModel()->getPodcastById($podcastId)) instanceof Podcast) { throw PageNotFoundException::forPageNotFound(); } diff --git a/modules/PodcastImport/Entities/PodcastImportTask.php b/modules/PodcastImport/Entities/PodcastImportTask.php index d3e92767..b0de2f95 100644 --- a/modules/PodcastImport/Entities/PodcastImportTask.php +++ b/modules/PodcastImport/Entities/PodcastImportTask.php @@ -27,7 +27,7 @@ use Exception; * @property int $episodes_imported * @property ?int $episodes_count * @property int $progress - * @property int $duration + * @property ?int $duration * * @property ?int $process_id * @@ -100,7 +100,7 @@ class PodcastImportTask extends Entity public function getDuration(): int { - if ($this->duration === null && $this->started_at && $this->ended_at) { + if ($this->duration === null && $this->started_at !== null && $this->ended_at !== null) { $this->duration = ($this->started_at->difference($this->ended_at)) ->getSeconds(); } diff --git a/modules/PodcastImport/Helpers/podcast_import_helper.php b/modules/PodcastImport/Helpers/podcast_import_helper.php index 02ca9416..6fa22039 100644 --- a/modules/PodcastImport/Helpers/podcast_import_helper.php +++ b/modules/PodcastImport/Helpers/podcast_import_helper.php @@ -21,14 +21,10 @@ if (! function_exists('get_import_tasks')) { $podcastImportsQueue = service('settings') ->get('Import.queue') ?? []; - if ($podcastImportsQueue === []) { - return []; - } - if ($podcastHandle !== null) { $podcastImportsQueue = array_filter( $podcastImportsQueue, - static fn (PodcastImportTask $importTask): bool => $importTask->handle === $podcastHandle + static fn (PodcastImportTask $importTask): bool => $importTask->handle === $podcastHandle, ); } diff --git a/modules/PodcastImport/Language/.rsync-filter b/modules/PodcastImport/Language/.rsync-filter deleted file mode 100644 index 38526af5..00000000 --- a/modules/PodcastImport/Language/.rsync-filter +++ /dev/null @@ -1,14 +0,0 @@ -+ br/*** -+ ca/*** -+ cs/*** -+ de/*** -+ en/*** -+ es/*** -+ fr/*** -+ lt/*** -+ nn-no/*** -+ pl/*** -+ pt-br/*** -+ sr-latn/*** -+ zh-hans/*** -- ** diff --git a/modules/PodcastImport/Language/ar/PodcastImport.php b/modules/PodcastImport/Language/ar/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/ar/PodcastImport.php +++ b/modules/PodcastImport/Language/ar/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/ca/PodcastImport.php b/modules/PodcastImport/Language/ca/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/ca/PodcastImport.php +++ b/modules/PodcastImport/Language/ca/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/cs/PodcastImport.php b/modules/PodcastImport/Language/cs/PodcastImport.php deleted file mode 100644 index f1ada831..00000000 --- a/modules/PodcastImport/Language/cs/PodcastImport.php +++ /dev/null @@ -1,66 +0,0 @@ - [ - 'disclaimer' => 'Importuje', - 'text' => '{podcastTitle} je nyní importován.', - 'cta' => 'Zobrazit stav importu', - ], - 'old_podcast_section_title' => 'Podcast k importu', - 'old_podcast_legal_disclaimer_title' => 'Právní prohlášení', - 'old_podcast_legal_disclaimer' => - 'Ujistěte se, že vlastníte práva pro tento kanál před jeho importem. Kopírování a vysílání bez řádných práv je pirátství a podléhá stíhání.', - 'imported_feed_url' => 'Adresa kanálu', - 'imported_feed_url_hint' => 'Zdroj musí být ve formátu XML nebo RSS.', - 'new_podcast_section_title' => 'Nový kanál', - 'lock_import' => - 'Tento kanál je chráněn. Nemůžete jej importovat. Pokud jste vlastník, odemkněte jej na zdrojové platformě.', - 'submit' => 'Přidat import do fronty', - 'queue' => [ - 'status' => [ - 'label' => 'Stav', - 'queued' => 've frontě', - 'queued_hint' => 'Import čeká na zpracování.', - 'canceled' => 'zrušeno', - 'canceled_hint' => 'Import byl zrušen.', - 'running' => 'běží', - 'running_hint' => 'Probíhá zpracování importu.', - 'failed' => 'selhalo', - 'failed_hint' => 'Import nemohl být dokončen: skript selhal.', - 'passed' => 'prošel', - 'passed_hint' => 'Import úspěšně dokončen!', - ], - 'feed' => 'Kanál', - 'duration' => 'Doba trvání importu', - 'imported_episodes' => 'Importované epizody', - 'imported_episodes_hint' => '{newlyImportedCount} nově importováno, {alreadyImportedCount} již importováno.', - 'actions' => [ - 'cancel' => 'Zrušit', - 'retry' => 'Opakovat', - 'delete' => 'Smazat', - ], - ], - 'syncForm' => [ - 'title' => 'Synchronizovat kanály', - 'feed_url' => 'Adresa kanálu', - 'feed_url_hint' => 'URL kanálu, který chcete synchronizovat s aktuálním podcastem.', - 'submit' => 'Přidat do fronty', - ], - 'messages' => [ - 'canceled' => 'Import byl úspěšně zrušen!', - 'notRunning' => 'Nelze zrušit import, protože není spuštěn.', - 'alreadyRunning' => 'Import je již spuštěn. Před dalším pokusem jej můžete zrušit.', - 'retried' => 'Import byl zařazen do fronty, brzy bude znova spuštěn!', - 'deleted' => 'Import byl úspěšně smazán!', - 'importTaskQueued' => 'Nový úkol byl ve frontě, import brzy začne!', - 'syncTaskQueued' => 'Nový úkol importu byl ve frontě, synchronizace brzy začne!', - ], -]; diff --git a/modules/PodcastImport/Language/da/PodcastImport.php b/modules/PodcastImport/Language/da/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/da/PodcastImport.php +++ b/modules/PodcastImport/Language/da/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/de/PodcastImport.php b/modules/PodcastImport/Language/de/PodcastImport.php index 0037a0c0..0cfa75f6 100644 --- a/modules/PodcastImport/Language/de/PodcastImport.php +++ b/modules/PodcastImport/Language/de/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/el/PodcastImport.php b/modules/PodcastImport/Language/el/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/el/PodcastImport.php +++ b/modules/PodcastImport/Language/el/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/en/PodcastImport.php b/modules/PodcastImport/Language/en/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/en/PodcastImport.php +++ b/modules/PodcastImport/Language/en/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/es/PodcastImport.php b/modules/PodcastImport/Language/es/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/es/PodcastImport.php +++ b/modules/PodcastImport/Language/es/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/eu/PodcastImport.php b/modules/PodcastImport/Language/eu/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/eu/PodcastImport.php +++ b/modules/PodcastImport/Language/eu/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/fr-ca/PodcastImport.php b/modules/PodcastImport/Language/fr-ca/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/fr-ca/PodcastImport.php +++ b/modules/PodcastImport/Language/fr-ca/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/fr2/PodcastImport.php b/modules/PodcastImport/Language/fr2/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/fr2/PodcastImport.php +++ b/modules/PodcastImport/Language/fr2/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/gd/PodcastImport.php b/modules/PodcastImport/Language/gd/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/gd/PodcastImport.php +++ b/modules/PodcastImport/Language/gd/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/gl/PodcastImport.php b/modules/PodcastImport/Language/gl/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/gl/PodcastImport.php +++ b/modules/PodcastImport/Language/gl/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/id/PodcastImport.php b/modules/PodcastImport/Language/id/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/id/PodcastImport.php +++ b/modules/PodcastImport/Language/id/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/it/PodcastImport.php b/modules/PodcastImport/Language/it/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/it/PodcastImport.php +++ b/modules/PodcastImport/Language/it/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/ja/PodcastImport.php b/modules/PodcastImport/Language/ja/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/ja/PodcastImport.php +++ b/modules/PodcastImport/Language/ja/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/kk/PodcastImport.php b/modules/PodcastImport/Language/kk/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/kk/PodcastImport.php +++ b/modules/PodcastImport/Language/kk/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/ko/PodcastImport.php b/modules/PodcastImport/Language/ko/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/ko/PodcastImport.php +++ b/modules/PodcastImport/Language/ko/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/lt/PodcastImport.php b/modules/PodcastImport/Language/lt/PodcastImport.php deleted file mode 100644 index 89767f23..00000000 --- a/modules/PodcastImport/Language/lt/PodcastImport.php +++ /dev/null @@ -1,74 +0,0 @@ - [ - 'disclaimer' => 'Importuojama', - 'text' => 'Tinklalaidė „{podcastTitle}“ šiuo metu importuojama.', - 'cta' => 'Rodyti importo būseną', - ], - 'old_podcast_section_title' => 'Importuotina tinklalaidė', - 'old_podcast_legal_disclaimer_title' => 'Atsakomybės išsižadėjimas', - 'old_podcast_legal_disclaimer' => - 'Prieš importuodami šią tinklalaidę, įsitikinkite, jog turite teisę tai daryti. Kopijuojant ir retransliuojant tinklalaidę, neturint tam reikiamų teisių, yra piratavimas, už tai gali būti baudžiama.', - 'imported_feed_url' => 'Sklaidos kanalo URL', - 'imported_feed_url_hint' => 'Sklaidos kanalas turi būti XML arba RSS formatu.', - 'new_podcast_section_title' => 'Naujoji tinklalaidė', - 'lock_import' => - 'Šis sklaidos kanalas apsaugotas. Jo importuoti negalite. Jei esate savininkas, atjunkite jo apsaugą dabartinėje platformoje.', - 'submit' => 'Įtraukti importą į eilę', - 'queue' => [ - 'status' => [ - 'label' => 'Būsena', - 'queued' => 'eilėje', - 'queued_hint' => 'Importo užduotis laukia apdorojimo.', - 'canceled' => 'atsisakyta', - 'canceled_hint' => 'Importo užduotis atšaukta.', - 'running' => 'vykdoma', - 'running_hint' => 'Importo užduotis šiuo metu apdorojama.', - 'failed' => 'nepavyko', - 'failed_hint' => 'Importo užduotis nepayko: scenarijaus klaida.', - 'passed' => 'atlikta', - 'passed_hint' => 'Importo užduotis užbaigta sėkmingai!', - ], - 'feed' => 'Sklaidos kanalas', - 'duration' => 'Importo trukmė', - 'imported_episodes' => 'Importuota epizodų', - 'imported_episodes_hint' => '{newlyImportedCount, plural, - one {# importuotas naujai} - few {# importuoti naujai} - other {# importuota naujai} - }, {alreadyImportedCount, plural, - one {# jau buvo importuotas} - few {# jau buvo importuoti} - other {# jau buvo importuota} - }.', - 'actions' => [ - 'cancel' => 'Atsisakyti', - 'retry' => 'Bandyti dar kartą', - 'delete' => 'Šalinti', - ], - ], - 'syncForm' => [ - 'title' => 'Sinchronizuoti sklaidos kanalus', - 'feed_url' => 'Sklaidos kanalo URL', - 'feed_url_hint' => 'Sklaidos kanalo, kurį norite sinchronizuoti su šia tinklalaide, URL.', - 'submit' => 'Įtraukti į eilę', - ], - 'messages' => [ - 'canceled' => 'Importo užduotis sėkmingai atšaukta!', - 'notRunning' => 'Importo užduoties atšaukti nepavyko, nes ji nevykdoma.', - 'alreadyRunning' => 'Importo užduotis jau vykdoma. Prieš bandydami iš naujo, galite ją atšaukti.', - 'retried' => 'Importo užduotis įtraukta į eilę, netrukus bus ją bus bandoma vykdyti iš naujo!', - 'deleted' => 'Importo užduotis sėkmingai pašalinta!', - 'importTaskQueued' => 'Nauja importo užduotis įtraukta į eilę, importas bus pradėtas netrukus!', - 'syncTaskQueued' => 'Nauja importo užduotis įtraukta į eilę, sinchronizavimas bus pradėtas netrukus!', - ], -]; diff --git a/modules/PodcastImport/Language/oc/PodcastImport.php b/modules/PodcastImport/Language/oc/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/oc/PodcastImport.php +++ b/modules/PodcastImport/Language/oc/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/pt/PodcastImport.php b/modules/PodcastImport/Language/pt/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/pt/PodcastImport.php +++ b/modules/PodcastImport/Language/pt/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/ro/PodcastImport.php b/modules/PodcastImport/Language/ro/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/ro/PodcastImport.php +++ b/modules/PodcastImport/Language/ro/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/ru/PodcastImport.php b/modules/PodcastImport/Language/ru/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/ru/PodcastImport.php +++ b/modules/PodcastImport/Language/ru/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/sk/PodcastImport.php b/modules/PodcastImport/Language/sk/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/sk/PodcastImport.php +++ b/modules/PodcastImport/Language/sk/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/sv/PodcastImport.php b/modules/PodcastImport/Language/sv/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/sv/PodcastImport.php +++ b/modules/PodcastImport/Language/sv/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/uk/PodcastImport.php b/modules/PodcastImport/Language/uk/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/uk/PodcastImport.php +++ b/modules/PodcastImport/Language/uk/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PodcastImport/Language/zh-hant/PodcastImport.php b/modules/PodcastImport/Language/zh-hant/PodcastImport.php index 8bf494d3..cac52509 100644 --- a/modules/PodcastImport/Language/zh-hant/PodcastImport.php +++ b/modules/PodcastImport/Language/zh-hant/PodcastImport.php @@ -60,7 +60,7 @@ return [ 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', 'retried' => 'Import task has been queued, it will be retried shortly!', 'deleted' => 'Import task has been successfully deleted!', - 'importTaskQueued' => 'An new task has been queued, import will start shortly!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', ], ]; diff --git a/modules/PremiumPodcasts/Config/Routes.php b/modules/PremiumPodcasts/Config/Routes.php index e7906067..80d19586 100644 --- a/modules/PremiumPodcasts/Config/Routes.php +++ b/modules/PremiumPodcasts/Config/Routes.php @@ -21,43 +21,43 @@ $routes->group( $routes->group('podcasts/(:num)/subscriptions', static function ($routes): void { $routes->get('/', 'SubscriptionController::list/$1', [ 'as' => 'subscription-list', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ]); - $routes->get('new', 'SubscriptionController::create/$1', [ + $routes->get('new', 'SubscriptionController::createView/$1', [ 'as' => 'subscription-create', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ]); $routes->post( 'new', - 'SubscriptionController::attemptCreate/$1', + 'SubscriptionController::createAction/$1', [ - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); - $routes->post('save-link', 'SubscriptionController::attemptLinkSave/$1', [ + $routes->post('save-link', 'SubscriptionController::linkSaveAction/$1', [ 'as' => 'subscription-link-save', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ]); // Subscription $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'SubscriptionController::view/$1/$2', [ 'as' => 'subscription-view', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ]); $routes->get( 'edit', - 'SubscriptionController::edit/$1/$2', + 'SubscriptionController::editView/$1/$2', [ 'as' => 'subscription-edit', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->post( 'edit', - 'SubscriptionController::attemptEdit/$1/$2', + 'SubscriptionController::editAction/$1/$2', [ 'as' => 'subscription-edit', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->get( @@ -65,22 +65,22 @@ $routes->group( 'SubscriptionController::regenerateToken/$1/$2', [ 'as' => 'subscription-regenerate-token', - 'filter' => 'permission:podcast#.manage-subscriptions', - ] + 'filter' => 'permission:podcast$1.manage-subscriptions', + ], ); $routes->get( 'suspend', 'SubscriptionController::suspend/$1/$2', [ 'as' => 'subscription-suspend', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->post( 'suspend', - 'SubscriptionController::attemptSuspend/$1/$2', + 'SubscriptionController::suspendAction/$1/$2', [ - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->get( @@ -88,7 +88,7 @@ $routes->group( 'SubscriptionController::resume/$1/$2', [ 'as' => 'subscription-resume', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->get( @@ -96,19 +96,19 @@ $routes->group( 'SubscriptionController::delete/$1/$2', [ 'as' => 'subscription-delete', - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->post( 'delete', - 'SubscriptionController::attemptDelete/$1/$2', + 'SubscriptionController::deleteAction/$1/$2', [ - 'filter' => 'permission:podcast#.manage-subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); }); }); - } + }, ); $routes->group( @@ -120,11 +120,11 @@ $routes->group( $routes->get('unlock', 'LockController::index/$1', [ 'as' => 'premium-podcast-unlock', ]); - $routes->post('unlock', 'LockController::attemptUnlock/$1', [ + $routes->post('unlock', 'LockController::unlockAction/$1', [ 'as' => 'premium-podcast-unlock', ]); - $routes->get('lock', 'LockController::attemptLock/$1', [ + $routes->get('lock', 'LockController::lockAction/$1', [ 'as' => 'premium-podcast-lock', ]); - } + }, ); diff --git a/modules/PremiumPodcasts/Config/Services.php b/modules/PremiumPodcasts/Config/Services.php index fb9ec7c0..9ca49df7 100644 --- a/modules/PremiumPodcasts/Config/Services.php +++ b/modules/PremiumPodcasts/Config/Services.php @@ -12,7 +12,7 @@ class Services extends BaseService { public static function premium_podcasts( ?SubscriptionModel $subscriptionModel = null, - bool $getShared = true + bool $getShared = true, ): PremiumPodcasts { if ($getShared) { return self::getSharedInstance('premium_podcasts', $subscriptionModel); diff --git a/modules/PremiumPodcasts/Controllers/LockController.php b/modules/PremiumPodcasts/Controllers/LockController.php index bcd53e22..b06efd09 100644 --- a/modules/PremiumPodcasts/Controllers/LockController.php +++ b/modules/PremiumPodcasts/Controllers/LockController.php @@ -34,7 +34,7 @@ class LockController extends BaseController throw PageNotFoundException::forPageNotFound(); } - if (! ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) instanceof Podcast) { + if (! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast) { throw PageNotFoundException::forPageNotFound(); } @@ -58,7 +58,7 @@ class LockController extends BaseController return view('podcast/unlock', $data); } - public function attemptUnlock(): RedirectResponse + public function unlockAction(): RedirectResponse { $rules = [ 'token' => 'required', @@ -91,7 +91,7 @@ class LockController extends BaseController ->with('message', lang('PremiumPodcasts.messages.unlockSuccess')); } - public function attemptLock(): RedirectResponse + public function lockAction(): RedirectResponse { $this->premiumPodcasts->lock($this->podcast->handle); diff --git a/modules/PremiumPodcasts/Controllers/SubscriptionController.php b/modules/PremiumPodcasts/Controllers/SubscriptionController.php index afece7fd..7ddf669a 100644 --- a/modules/PremiumPodcasts/Controllers/SubscriptionController.php +++ b/modules/PremiumPodcasts/Controllers/SubscriptionController.php @@ -32,7 +32,7 @@ class SubscriptionController extends BaseController throw PageNotFoundException::forPageNotFound(); } - if (! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { throw PageNotFoundException::forPageNotFound(); } @@ -42,8 +42,8 @@ class SubscriptionController extends BaseController return $this->{$method}(); } - if (! ($this->subscription = (new SubscriptionModel())->getSubscriptionById( - (int) $params[1] + if (! ($this->subscription = new SubscriptionModel()->getSubscriptionById( + (int) $params[1], )) instanceof Subscription) { throw PageNotFoundException::forPageNotFound(); } @@ -59,13 +59,14 @@ class SubscriptionController extends BaseController helper('form'); + $this->setHtmlHead(lang('Subscription.podcast_subscriptions')); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, ]); return view('subscription/list', $data); } - public function attemptLinkSave(): RedirectResponse + public function linkSaveAction(): RedirectResponse { $rules = [ 'subscription_link' => 'valid_url_strict|permit_empty', @@ -85,7 +86,7 @@ class SubscriptionController extends BaseController return redirect()->route('subscription-list', [$this->podcast->id])->with( 'message', - lang('Subscription.messages.linkRemoveSuccess') + lang('Subscription.messages.linkRemoveSuccess'), ); } @@ -98,7 +99,7 @@ class SubscriptionController extends BaseController return redirect()->route('subscription-list', [$this->podcast->id])->with( 'message', - lang('Subscription.messages.linkSaveSuccess') + lang('Subscription.messages.linkSaveSuccess'), ); } @@ -109,6 +110,7 @@ class SubscriptionController extends BaseController 'subscription' => $this->subscription, ]; + $this->setHtmlHead(lang('Subscription.view', [$this->subscription->id])); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, @@ -116,7 +118,7 @@ class SubscriptionController extends BaseController return view('subscription/view', $data); } - public function create(): string + public function createView(): string { helper('form'); @@ -124,13 +126,14 @@ class SubscriptionController extends BaseController 'podcast' => $this->podcast, ]; + $this->setHtmlHead(lang('Subscription.add', [esc($this->podcast->title)])); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, ]); return view('subscription/create', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { helper('text'); @@ -180,7 +183,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.addError'), $email->printDebugger([])] + [lang('Subscription.messages.addError'), $email->printDebugger([])], ); } @@ -190,7 +193,7 @@ class SubscriptionController extends BaseController 'message', lang('Subscription.messages.addSuccess', [ 'subscriber' => $newSubscription->email, - ]) + ]), ); } @@ -227,7 +230,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.regenerateTokenError'), $email->printDebugger([])] + [lang('Subscription.messages.regenerateTokenError'), $email->printDebugger([])], ); } @@ -237,11 +240,11 @@ class SubscriptionController extends BaseController 'message', lang('Subscription.messages.regenerateTokenSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } - public function edit(): string + public function editView(): string { helper('form'); @@ -250,6 +253,7 @@ class SubscriptionController extends BaseController 'subscription' => $this->subscription, ]; + $this->setHtmlHead(lang('Subscription.edit', [esc($this->podcast->title)])); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, @@ -257,7 +261,7 @@ class SubscriptionController extends BaseController return view('subscription/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(): RedirectResponse { $expiresAt = null; $expirationDate = $this->request->getPost('expiration_date'); @@ -295,7 +299,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.editError'), $email->printDebugger([])] + [lang('Subscription.messages.editError'), $email->printDebugger([])], ); } @@ -305,7 +309,7 @@ class SubscriptionController extends BaseController 'message', lang('Subscription.messages.editSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } @@ -318,6 +322,7 @@ class SubscriptionController extends BaseController 'subscription' => $this->subscription, ]; + $this->setHtmlHead(lang('Subscription.suspend')); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, @@ -325,7 +330,7 @@ class SubscriptionController extends BaseController return view('subscription/suspend', $data); } - public function attemptSuspend(): RedirectResponse + public function suspendAction(): RedirectResponse { $db = db_connect(); $db->transStart(); @@ -350,7 +355,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.suspendError'), $email->printDebugger([])] + [lang('Subscription.messages.suspendError'), $email->printDebugger([])], ); } @@ -360,7 +365,7 @@ class SubscriptionController extends BaseController 'messages', lang('Subscription.messages.suspendSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } @@ -390,7 +395,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.resumeError'), $email->printDebugger([])] + [lang('Subscription.messages.resumeError'), $email->printDebugger([])], ); } @@ -400,7 +405,7 @@ class SubscriptionController extends BaseController 'message', lang('Subscription.messages.resumeSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } @@ -413,6 +418,7 @@ class SubscriptionController extends BaseController 'subscription' => $this->subscription, ]; + $this->setHtmlHead(lang('Subscription.delete')); replace_breadcrumb_params([ 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, @@ -420,12 +426,13 @@ class SubscriptionController extends BaseController return view('subscription/delete', $data); } - public function attemptDelete(): RedirectResponse + public function deleteAction(): RedirectResponse { $db = db_connect(); $db->transStart(); - (new SubscriptionModel())->delete($this->subscription->id); + new SubscriptionModel() + ->delete($this->subscription->id); /** @var Email $email */ $email = service('email'); @@ -439,7 +446,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.deleteError'), $email->printDebugger([])] + [lang('Subscription.messages.deleteError'), $email->printDebugger([])], ); } @@ -449,7 +456,7 @@ class SubscriptionController extends BaseController 'messages', lang('Subscription.messages.deleteSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } } diff --git a/modules/PremiumPodcasts/Database/Migrations/2022-07-07-120000_add_subscriptions.php b/modules/PremiumPodcasts/Database/Migrations/2022-07-07-120000_add_subscriptions.php index 5f7abaaa..57fa8dfd 100644 --- a/modules/PremiumPodcasts/Database/Migrations/2022-07-07-120000_add_subscriptions.php +++ b/modules/PremiumPodcasts/Database/Migrations/2022-07-07-120000_add_subscriptions.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Modules\PremiumPodcasts\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddSubscriptions extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ @@ -73,6 +75,7 @@ class AddSubscriptions extends BaseMigration $this->forge->createTable('subscriptions'); } + #[Override] public function down(): void { $this->forge->dropTable('subscriptions'); diff --git a/modules/PremiumPodcasts/Entities/Subscription.php b/modules/PremiumPodcasts/Entities/Subscription.php index a292635a..aa8e32be 100644 --- a/modules/PremiumPodcasts/Entities/Subscription.php +++ b/modules/PremiumPodcasts/Entities/Subscription.php @@ -24,7 +24,7 @@ use Modules\Analytics\Models\AnalyticsPodcastBySubscriptionModel; * @property string $token * @property string $status * @property string|null $status_message - * @property Time|null $expires_at + * @property Time $expires_at * @property int $downloads_last_3_months * * @property int $created_by @@ -57,9 +57,7 @@ class Subscription extends Entity public function getStatus(): string { - return ($this->expires_at instanceof Time && $this->expires_at->isBefore( - Time::now() - )) ? 'expired' : $this->attributes['status']; + return $this->expires_at->isBefore(Time::now()) ? 'expired' : $this->attributes['status']; } /** @@ -102,7 +100,8 @@ class Subscription extends Entity public function getPodcast(): ?Podcast { if (! $this->podcast instanceof Podcast) { - $this->podcast = (new PodcastModel())->getPodcastById($this->podcast_id); + $this->podcast = new PodcastModel() + ->getPodcastById($this->podcast_id); } return $this->podcast; @@ -110,9 +109,7 @@ class Subscription extends Entity public function getDownloadsLast3Months(): int { - return (new AnalyticsPodcastBySubscriptionModel())->getNumberOfDownloadsLast3Months( - $this->podcast_id, - $this->id - ); + return new AnalyticsPodcastBySubscriptionModel() + ->getNumberOfDownloadsLast3Months($this->podcast_id, $this->id); } } diff --git a/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php b/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php index 185341bc..2f08a68b 100644 --- a/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php +++ b/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php @@ -11,6 +11,7 @@ use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Router\Router; use Modules\PremiumPodcasts\PremiumPodcasts; +use Override; class PodcastUnlockFilter implements FilterInterface { @@ -19,8 +20,9 @@ class PodcastUnlockFilter implements FilterInterface * * @param string[]|null $arguments * - * @return RequestInterface|ResponseInterface|string|void + * @return RequestInterface|ResponseInterface|string|null */ + #[Override] public function before(RequestInterface $request, $arguments = null) { if (! function_exists('is_unlocked')) { @@ -32,56 +34,62 @@ class PodcastUnlockFilter implements FilterInterface $routerParams = $router->params(); if ($routerParams === []) { - return; + return null; } // no need to go through the unlock form if user is connected if (auth()->loggedIn()) { - return; + return null; } // Make sure this isn't already a premium podcast route if (url_is((string) route_to('premium-podcast-unlock', $routerParams[0]))) { - return; + return null; } // expect 2 parameters (podcast handle and episode slug) if (count($routerParams) < 2) { - return; + return null; } - $episode = (new EpisodeModel())->getEpisodeBySlug($routerParams[0], $routerParams[1]); + $episode = new EpisodeModel() + ->getEpisodeBySlug($routerParams[0], $routerParams[1]); if (! $episode instanceof Episode) { - return; + return null; } // Make sure that public episodes are still accessible if (! $episode->is_premium) { - return; + return null; } // Episode should be embeddable even if it is premium if (url_is((string) route_to('embed', $episode->podcast->handle, $episode->slug))) { - return; + return null; } - // if podcast is locked then send to the unlock form /** @var PremiumPodcasts $premiumPodcasts */ $premiumPodcasts = service('premium_podcasts'); - if (! $premiumPodcasts->check($routerParams[0])) { - session()->set('redirect_url', current_url()); - - return redirect()->route('premium-podcast-unlock', [$routerParams[0]]); + if ($premiumPodcasts->check($routerParams[0])) { + return null; } + + // podcast is locked, send to the unlock form + session() + ->set('redirect_url', current_url()); + + return redirect()->route('premium-podcast-unlock', [$routerParams[0]]); } /** - * @param string[]|null $arguments + * @param list|null $arguments * - * @return ResponseInterface|void + * @return ResponseInterface|null */ + #[Override] public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { + return null; } } diff --git a/modules/PremiumPodcasts/Language/.rsync-filter b/modules/PremiumPodcasts/Language/.rsync-filter deleted file mode 100644 index 38526af5..00000000 --- a/modules/PremiumPodcasts/Language/.rsync-filter +++ /dev/null @@ -1,14 +0,0 @@ -+ br/*** -+ ca/*** -+ cs/*** -+ de/*** -+ en/*** -+ es/*** -+ fr/*** -+ lt/*** -+ nn-no/*** -+ pl/*** -+ pt-br/*** -+ sr-latn/*** -+ zh-hans/*** -- ** diff --git a/modules/PremiumPodcasts/Language/cs/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/cs/PremiumPodcasts.php deleted file mode 100644 index e85134b4..00000000 --- a/modules/PremiumPodcasts/Language/cs/PremiumPodcasts.php +++ /dev/null @@ -1,34 +0,0 @@ - 'Podcast obsahuje prémiové epizody', - 'episode_is_premium' => 'Epizoda je prémiová, dostupná pouze pro prémiové odběratele', - 'unlock_episode' => 'Tato epizoda je pouze pro prémiové odběratele. Klepnutím ji odemknete!', - 'banner_unlock' => 'Tento podcast obsahuje prémiové epizody, které jsou dostupné pouze pro prémiové odběratele.', - 'banner_lock' => 'Podcast je odemčen, užijte si prémiové epizody!', - 'subscribe' => 'Odebírat', - 'lock' => 'Uzamknout', - 'unlock' => 'Odemknout', - 'unlock_form' => [ - 'title' => 'Prémiový obsah', - 'subtitle' => 'Tento podcast obsahuje uzamčené prémiové epizody! Máte klíč k jejich odemčení?', - 'token' => 'Zadejte svůj klíč', - 'token_hint' => 'Pokud jste přihlášeni k odběru {podcastTitle}, můžete zkopírovat klíč, který vám byl odeslán prostřednictvím e-mailu a vložit jej zde.', - 'submit' => 'Odemknout všechny epizody!', - 'call_to_action' => 'Odemknout všechny epizody {podcastTitle}:', - 'subscribe_cta' => 'Přihlásit se k odběru nyní!', - ], - 'messages' => [ - 'unlockSuccess' => 'Podcast byl úspěšně odemčen! Užijte si prémiové epizody!', - 'unlockBadAttempt' => 'Zdá se, že váš klíč nefunguje…', - 'lockSuccess' => 'Podcast byl úspěšně uzamčen!', - ], -]; diff --git a/modules/PremiumPodcasts/Language/cs/Subscription.php b/modules/PremiumPodcasts/Language/cs/Subscription.php deleted file mode 100644 index a53a8c5b..00000000 --- a/modules/PremiumPodcasts/Language/cs/Subscription.php +++ /dev/null @@ -1,100 +0,0 @@ - 'Odběry podcastu', - 'add' => 'Nový odběr', - 'view' => 'Zobrazit odběry', - 'edit' => 'Upravit odebírání', - 'regenerate_token' => 'Znovu vygenerovat token', - 'suspend' => 'Pozastavit odběr', - 'resume' => 'Pokračovat v odběru', - 'delete' => 'Smazat odběr', - 'status' => [ - 'active' => 'Aktivní', - 'suspended' => 'Pozastaveno', - 'expired' => 'Vypršeno', - ], - 'list' => [ - 'number' => 'Číslo', - 'email' => 'E-mail', - 'expiration_date' => 'Datum vypršení', - 'unlimited' => 'Neomezené', - 'downloads' => 'Stažení', - 'status' => 'Stav', - ], - 'form' => [ - 'email' => 'E-mail', - 'expiration_date' => 'Datum vypršení', - 'expiration_date_hint' => 'Datum a čas, kdy vyprší odběr. Ponechte prázdné pro neomezený odběr.', - 'submit_create' => 'Vytvořit odběr', - 'submit_edit' => 'Upravit odebírání', - ], - 'form_link_add' => [ - 'link' => 'Odkaz na stránku odběru', - 'link_hint' => 'Tímto přidáte výzvu k akci na webových stránkách, které vyzývají posluchače k odběru podcastu.', - 'submit' => 'Uložit odkaz', - ], - 'suspend_form' => [ - 'disclaimer' => 'Pozastavení odběru omezí přístup k prémiovému obsahu. Později budete moci pozastavení zrušit.', - 'reason' => 'Důvod', - 'reason_placeholder' => 'Proč pozastavujete odběr?', - "submit" => 'Pozastavit odběr', - ], - 'delete_form' => [ - 'disclaimer' => 'Smazáním odběru {subscriber} odstraníte všechna analytická data, která jsou s ním spojena.', - 'understand' => 'Chápu, trvale odebrat odběr', - 'submit' => 'Odebrat odběr', - ], - 'messages' => [ - 'addSuccess' => 'Nový odběr přidán! Uvítací e-mail byl odeslán {subscriber}.', - 'addError' => 'Odběr nelze přidat.', - 'editSuccess' => 'Datum vypršení platnosti odběru bylo aktualizováno! E-mail byl odeslán {subscriber}.', - 'editError' => 'Odběr se nepodařilo smazat.', - 'regenerateTokenSuccess' => 'Token vygenerován! {subscriber} byl odeslán e-mail s novým tokenem.', - 'regenerateTokenError' => 'Token nelze obnovit.', - 'deleteSuccess' => 'Odběr byl odstraněn! {subscriber} byl odeslán e-mail .', - 'deleteError' => 'Odběr nelze odstranit.', - 'suspendSuccess' => 'Odběr byl pozastaven! E-mail byl odeslán {subscriber}.', - 'suspendError' => 'Odběr nemohl být pozastaven.', - 'resumeSuccess' => 'Odběr byl obnoven! E-mail byl odeslán {subscriber}.', - 'resumeError' => 'Odběr nelze obnovit.', - 'linkSaveSuccess' => 'Odkaz na odběr byl úspěšně uložen! Zobrazí se na webové stránce jako výzva k akci!', - 'linkRemoveSuccess' => 'Odkaz na odběr byl úspěšně odstraněn!', - ], - 'emails' => [ - 'greeting' => 'Ahoj,', - 'token' => 'Váš token: {0}', - 'unique_feed_link' => 'Váš unikátní odkaz na kanál: {0}', - 'how_to_use' => 'Návod k použití', - 'two_ways' => 'Máte dva způsoby, jak odemknout prémiové epizody:', - 'import_into_app' => 'Zkopírujte jedinečnou URL kanálu do vaší oblíbené podcast aplikace (importujte jej jako soukromý kanál, abyste zabránili odhalení vašich údajů).', - 'go_to_website' => 'Přejděte na web {podcastWebsite} a odemkněte podcast pomocí Vašeho tokenu.', - 'welcome_subject' => 'Vítejte v {podcastTitle}', - 'welcome' => 'Přihlásili jste k odběru {podcastTitle}, děkujeme a vítejte na palubě!', - 'welcome_token_title' => 'Zde jsou vaše přihlašovací údaje pro odemknutí prémiových epizod:', - 'welcome_expires' => 'Váš odběr byl nastaven na platnost do {0}.', - 'welcome_never_expires' => 'Váš odběr byl nastaven tak, že nikdy nevyprší.', - 'reset_subject' => 'Váš token byl obnoven!', - 'reset_token' => 'Váš přístup k {podcastTitle} byl obnoven!', - 'reset_token_title' => 'Nové přihlašovací údaje byly vygenerovány pro odemknutí prémiových epizod podcastu:', - 'edited_subject' => 'Váš odběr byl aktualizován!', - 'edited_expires' => 'Váš odběr {podcastTitle} byl nastaven na platnost {expiresAt}.', - 'edited_never_expires' => 'Váš odběr {podcastTitle} byl nastaven tak, aby nikdy neskončil!', - 'suspended_subject' => 'Váš odběr byl pozastaven!', - 'suspended' => 'Váš odběr {podcastTitle} byl pozastaven! Již nemůžete přistupovat k prémiovým epizodám podcastu.', - 'suspended_reason' => 'To je z následujícího důvodu: {0}', - 'resumed_subject' => 'Váš odběr byl obnoven!', - 'resumed' => 'Váš odběr {podcastTitle} byl obnoven! Můžete znovu přistupovat k prémiovým epizodám podcastu.', - 'deleted_subject' => 'Váš odběr byl odstraněn!', - 'deleted' => 'Váš odběr {podcastTitle} byl odebrán! Již nemáte přístup k prémiovým epizodám podcastu.', - 'footer' => '{castopod} je hostován na {host}', - ], -]; diff --git a/modules/PremiumPodcasts/Language/lt/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/lt/PremiumPodcasts.php deleted file mode 100644 index dec15f7a..00000000 --- a/modules/PremiumPodcasts/Language/lt/PremiumPodcasts.php +++ /dev/null @@ -1,34 +0,0 @@ - 'Tinklalaidėje yra premium epizodų', - 'episode_is_premium' => 'Šis epizodas yra premium, jis pasiekiamas tik premium prenumeratoriams', - 'unlock_episode' => 'Šis epizodas skirtas tik premium prenumeratoriams. Spustelėkite jam atrakinti!', - 'banner_unlock' => 'Šioje tinklalaidėje yra premium epizodų, pasiekiamų tik premium prenumeratoriams.', - 'banner_lock' => 'Tinklalaidė atrakinta, mėgaukitės premium epizodu!', - 'subscribe' => 'Prenumeruoti', - 'lock' => 'Užrakinti', - 'unlock' => 'Atrakinti', - 'unlock_form' => [ - 'title' => 'Premium turinys', - 'subtitle' => 'Šioje tinklalaidėje yra užrakintų premium epizodų! Ar turite raktą jiems atrakinti?', - 'token' => 'Įveskite savo raktą', - 'token_hint' => 'Jei esate užsiprenumeravę „{podcastTitle}“, raktą galite nusikopijuoti iš mūsų jums siųsto el. laiško ir įdėti čia.', - 'submit' => 'Atrakinti visus epizodus!', - 'call_to_action' => 'Atrakinkite visus „{podcastTitle}“ epizodus:', - 'subscribe_cta' => 'Prenumeruokite dabar!', - ], - 'messages' => [ - 'unlockSuccess' => 'Tinklalaidė sėkmingai atrakinta! Mėgaukitės premium epizodais!', - 'unlockBadAttempt' => 'Panašu, kad jūsų raktas netinkamas…', - 'lockSuccess' => 'Tinklalaidė sėkmingai užrakinta!', - ], -]; diff --git a/modules/PremiumPodcasts/Language/lt/Subscription.php b/modules/PremiumPodcasts/Language/lt/Subscription.php deleted file mode 100644 index 69274605..00000000 --- a/modules/PremiumPodcasts/Language/lt/Subscription.php +++ /dev/null @@ -1,100 +0,0 @@ - 'Tinklalaidžių prenumeratos', - 'add' => 'Nauja prenumerata', - 'view' => 'Peržiūrėti prenumeratą', - 'edit' => 'Keisti prenumeratą', - 'regenerate_token' => 'Perkurti prieigos raktą', - 'suspend' => 'Pristabdyti prenumeratą', - 'resume' => 'Atstatyti prenumeratą', - 'delete' => 'Šalinti prenumeratą', - 'status' => [ - 'active' => 'Aktyvi', - 'suspended' => 'Pristabdyta', - 'expired' => 'Nebegalioja', - ], - 'list' => [ - 'number' => 'Numeris', - 'email' => 'El. paštas', - 'expiration_date' => 'Galiojimo pabaigos data', - 'unlimited' => 'Neribota', - 'downloads' => 'Parsisiuntimai', - 'status' => 'Būsena', - ], - 'form' => [ - 'email' => 'El. paštas', - 'expiration_date' => 'Galiojimo pabaigos data', - 'expiration_date_hint' => 'Data ir laikas, iki kada prenumerata galioja. Palikite lauką tuščią neribotai prenumeratai.', - 'submit_create' => 'Kurti prenumeratą', - 'submit_edit' => 'Taisyti prenumeratą', - ], - 'form_link_add' => [ - 'link' => 'Prenumeratos tinklalapio adresas', - 'link_hint' => 'Užpildžius šią formą, svetainėje bus pridėta raginimo šią tinklalaidę prenumeruoti forma.', - 'submit' => 'Įrašyti nuorodą', - ], - 'suspend_form' => [ - 'disclaimer' => 'Pristabdžius šią prenumeratą, bus apribota prenumeratoriaus prieiga prie premium turinio. Prenumeratą galėsite atstatyti.', - 'reason' => 'Priežastis', - 'reason_placeholder' => 'Kodėl pristabdote šią prenumeratą?', - "submit" => 'Pristabdyti prenumeratą', - ], - 'delete_form' => [ - 'disclaimer' => 'Pašalinus {subscriber} prenumeratą, bus pašalinti ir visi su ja susiję analitikos duomenys.', - 'understand' => 'Suprantu, noriu visam laikui pašalinti prenumeratą', - 'submit' => 'Pašalinti prenumeratą', - ], - 'messages' => [ - 'addSuccess' => 'Nauja prenumerata pridėta! {subscriber} turėtų netrukus gauti pasisveikinimo el. laišką.', - 'addError' => 'Prenumeratos pridėti nepavyko.', - 'editSuccess' => 'Prenumeratos galiojimo pabaigos data atnaujinta! {subscriber} netrukus turėtų gauti el. laišką.', - 'editError' => 'Prenumeratos pakeisti nepavyko.', - 'regenerateTokenSuccess' => 'Prieigos raktas perkurtas! {subscriber} turėtų netrukus gauti el. laišką su naujuoju prieigos raktu.', - 'regenerateTokenError' => 'Prieigos rakto perkurti nepavyko.', - 'deleteSuccess' => 'Prenumerata pašalinta! {subscriber} turėtų netrukus gauti el. laišką.', - 'deleteError' => 'Prenumeratos pašalinti nepavyko.', - 'suspendSuccess' => 'Prenumerata pristabdyta! {subscriber} turėtų netrukus gauti el. laišką.', - 'suspendError' => 'Prenumeratos pristabdyti nepavyko.', - 'resumeSuccess' => 'Prenumerata atstatyta! {subscriber} turėtų netrukus gauti el. laišką.', - 'resumeError' => 'Prenumeratos atstatyti nepavyko.', - 'linkSaveSuccess' => 'Prenumeratos nuoroda įrašyta sėkmingai! Ji bus rodoma svetainėje kaip raginimas veikti!', - 'linkRemoveSuccess' => 'Prenumeratos nuoroda pašalinta sėkmingai!', - ], - 'emails' => [ - 'greeting' => 'Sveiki,', - 'token' => 'Jūsų prieigos raktas: {0}', - 'unique_feed_link' => 'Jūsų asmeninio sklaidos kanalo adresas: {0}', - 'how_to_use' => 'Kaip naudotis?', - 'two_ways' => 'Yra du būdai premium epizodams atrakinti:', - 'import_into_app' => 'Galite nukopijuoti savo asmeninio sklaidos kanalo URL į mėgstamą tinklalaidžių klausymosi programą. Nepamirškite pasirinkti, jog tai privatus sklaidos kanalas, kad neatskleistumėte savo prisijungimo duomenų.', - 'go_to_website' => 'Galite atverti „{podcastWebsite}“ svetainę ir atrakinti tinklalaidę, naudodamiesi prieigos raktu.', - 'welcome_subject' => 'Jus sveikina „{podcastTitle}“', - 'welcome' => 'Jūs užsiprenumeravote tinklalaidę „{podcastTitle}“. Dėkojame ir sveikiname prisijungus!', - 'welcome_token_title' => 'Žemiau pateikiame jūsų prisijungimo duomenis tinklalaidės premium epizodams atrakinti:', - 'welcome_expires' => 'Jūsų prenumerata galioja iki {0}.', - 'welcome_never_expires' => 'Jūsų prenumerata galioja neterminuotai.', - 'reset_subject' => 'Jūsų prieigos raktas perkurtas!', - 'reset_token' => 'Jūsų prieigos prie tinklalaidės „{podcastTitle}“ raktas perkurtas!', - 'reset_token_title' => 'Jums sugeneruoti nauji prisijungimo duomenys šios tinklalaidės premium epizodams atrakinti:', - 'edited_subject' => 'Jūsų prenumerata pakoreguota!', - 'edited_expires' => 'Jūsų tinklalaidės „{podcastTitle}“ prenumerata galios iki {expiresAt}.', - 'edited_never_expires' => 'Jūsų tinklalaidės „{podcastTitle}“ prenumerata galios neterminuotai!', - 'suspended_subject' => 'Jūsų prenumerata pristabdyta!', - 'suspended' => 'Jūsų tinklalaidės „{podcastTitle}“ prenumerata pristabdyta! Šios tinklalaidės premium epizodų pasiekti nebegalėsite.', - 'suspended_reason' => 'Tai įvyko dėl šios priežasties: {0}', - 'resumed_subject' => 'Jūsų prenumerata atstatyta!', - 'resumed' => 'Jūsų tinklalaidės „{podcastTitle}“ prenumerata atstatyta! Jūs vėl galte pasiekti šios tinklalaidės premium epizodus.', - 'deleted_subject' => 'Jūsų prenumerata atšaukta!', - 'deleted' => 'Jūsų tinklalaidės „{podcastTitle}“ prenumerata atšaukta! Šios tinklalaidės premium epizodų pasiekti nebegalėsite.', - 'footer' => '{castopod}, veikianti serveryje {host}', - ], -]; diff --git a/modules/PremiumPodcasts/Models/SubscriptionModel.php b/modules/PremiumPodcasts/Models/SubscriptionModel.php index 1fc17622..32619b19 100644 --- a/modules/PremiumPodcasts/Models/SubscriptionModel.php +++ b/modules/PremiumPodcasts/Models/SubscriptionModel.php @@ -134,7 +134,8 @@ class SubscriptionModel extends Model protected function clearCache(array $data): array { /** @var ?Subscription */ - $subscription = (new self())->find(is_array($data['id']) ? $data['id'][0] : $data['id']); + $subscription = new self() + ->find(is_array($data['id']) ? $data['id'][0] : $data['id']); if (! $subscription instanceof Subscription) { return $data; diff --git a/modules/PremiumPodcasts/PremiumPodcasts.php b/modules/PremiumPodcasts/PremiumPodcasts.php index e1469d97..036b6f05 100644 --- a/modules/PremiumPodcasts/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/PremiumPodcasts.php @@ -63,7 +63,7 @@ class PremiumPodcasts { if (array_key_exists( $podcastHandle, - $this->subscriptions + $this->subscriptions, ) && ($this->subscriptions[$podcastHandle] instanceof Subscription)) { return true; } @@ -86,7 +86,7 @@ class PremiumPodcasts // Store the current subscription object $this->subscriptions[$podcastHandle] = $this->subscriptionModel->getSubscriptionById( - $this->subscriptions[$podcastHandle]->id + $this->subscriptions[$podcastHandle]->id, ); if (! $this->subscriptions[$podcastHandle] instanceof Subscription) { diff --git a/modules/Update/Commands/DatabaseUpdate.php b/modules/Update/Commands/DatabaseUpdate.php index 85bc78bc..380f5305 100644 --- a/modules/Update/Commands/DatabaseUpdate.php +++ b/modules/Update/Commands/DatabaseUpdate.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Modules\Update\Commands; use CodeIgniter\CLI\BaseCommand; +use Override; class DatabaseUpdate extends BaseCommand { @@ -23,6 +24,7 @@ class DatabaseUpdate extends BaseCommand */ protected $description = 'Runs all new database migrations for Castopod.'; + #[Override] public function run(array $params): void { $migrate = service('migrations'); diff --git a/modules/WebSub/Commands/Publish.php b/modules/WebSub/Commands/Publish.php index 1b167865..c0f47b22 100644 --- a/modules/WebSub/Commands/Publish.php +++ b/modules/WebSub/Commands/Publish.php @@ -9,6 +9,7 @@ use App\Models\PodcastModel; use CodeIgniter\CLI\BaseCommand; use CodeIgniter\HTTP\CURLRequest; use Exception; +use Override; class Publish extends BaseCommand { @@ -18,6 +19,7 @@ class Publish extends BaseCommand protected $description = 'Publishes feed updates to websub hubs.'; + #[Override] public function run(array $params): void { if (ENVIRONMENT !== 'production') { @@ -70,18 +72,20 @@ class Publish extends BaseCommand } catch (Exception $exception) { log_message( 'warning', - "COULD NOT PUBLISH @{$podcast->handle} ON {$hub}" . PHP_EOL . $exception->getMessage() + "COULD NOT PUBLISH @{$podcast->handle} ON {$hub}" . PHP_EOL . $exception->getMessage(), ); } } // set podcast feed as having been pushed onto hubs - (new PodcastModel())->update($podcast->id, [ - 'is_published_on_hubs' => 1, - ]); + new PodcastModel() + ->update($podcast->id, [ + 'is_published_on_hubs' => 1, + ]); // set newly published episodes as pushed onto hubs - (new EpisodeModel())->set('is_published_on_hubs', true) + new EpisodeModel() + ->set('is_published_on_hubs', true) ->where([ 'podcast_id' => $podcast->id, 'is_published_on_hubs' => false, diff --git a/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php b/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php index a5297b65..179e1bf9 100644 --- a/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php +++ b/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Modules\WebSub\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddIsPublishedOnHubsToPodcasts extends BaseMigration { + #[Override] public function up(): void { $this->forge->addColumn('podcasts', [ @@ -26,6 +28,7 @@ class AddIsPublishedOnHubsToPodcasts extends BaseMigration ]); } + #[Override] public function down(): void { $this->forge->dropColumn('podcasts', 'is_published_on_hubs'); diff --git a/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php b/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php index 05ee0d0b..b57be027 100644 --- a/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php +++ b/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Modules\WebSub\Database\Migrations; use App\Database\Migrations\BaseMigration; +use Override; class AddIsPublishedOnHubsToEpisodes extends BaseMigration { + #[Override] public function up(): void { $this->forge->addColumn('episodes', [ @@ -26,6 +28,7 @@ class AddIsPublishedOnHubsToEpisodes extends BaseMigration ]); } + #[Override] public function down(): void { $this->forge->dropColumn('episodes', 'is_published_on_hubs'); diff --git a/package.json b/package.json index a3f6ec10..3b51642c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "castopod", - "version": "1.15.5", + "version": "2.0.0-next.3", "description": "Castopod Host is an open-source hosting platform made for podcasters who want engage and interact with their audience.", "private": true, "license": "AGPL-3.0-or-later", @@ -11,7 +11,7 @@ }, "scripts": { "dev": "vite", - "build": "tsc && pnpm run build:static && vite build", + "build": "tsc && vite build", "serve": "vite preview", "build:static": "pnpm run build:icons && pnpm run build:svg", "build:icons": "svgo -f resources/icons -o resources/icons -r --config=./.svgo.icons.cjs", @@ -23,6 +23,8 @@ "prettier": "prettier --check . --ignore-path ./.gitignore --ignore-path ./docs/.gitignore", "format": "prettier --write . --ignore-path ./.gitignore --ignore-path ./docs/.gitignore", "typecheck": "tsc", + "all-contributors:add": "all-contributors add", + "all-contributors:generate": "all-contributors generate", "commit": "cz", "release": "semantic-release", "prepare": "is-ci || husky" @@ -31,6 +33,7 @@ "@amcharts/amcharts4": "^4.10.40", "@amcharts/amcharts4-geodata": "^4.1.31", "@codemirror/commands": "^6.10.2", + "@codemirror/lang-html": "^6.4.11", "@codemirror/lang-xml": "^6.1.0", "@codemirror/language": "^6.12.1", "@codemirror/state": "^6.5.4", @@ -40,11 +43,12 @@ "@github/hotkey": "^3.1.1", "@github/markdown-toolbar-element": "^2.2.3", "@github/relative-time-element": "^5.0.0", - "@tailwindcss/nesting": "0.0.0-insiders.565cd3e", + "@patternfly/elements": "^4.3.1", "@vime/core": "^5.4.1", "choices.js": "^11.1.0", "codemirror": "^6.0.2", "flatpickr": "^4.6.13", + "htmlfy": "^1.0.1", "leaflet": "^1.9.4", "leaflet.markercluster": "^1.5.3", "lit": "^3.3.2", @@ -53,10 +57,11 @@ "xml-formatter": "^3.6.7" }, "devDependencies": { - "@commitlint/cli": "^20.4.1", - "@commitlint/config-conventional": "^20.4.1", + "@commitlint/cli": "^20.4.2", + "@commitlint/config-conventional": "^20.4.2", "@csstools/css-tokenizer": "^4.0.0", - "@eslint/js": "10.0.1", + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^10.0.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^7.1.0", "@semantic-release/git": "^10.0.1", @@ -64,16 +69,16 @@ "@tailwindcss/forms": "^0.5.11", "@tailwindcss/typography": "^0.5.19", "@types/leaflet": "^1.9.21", - "@typescript-eslint/eslint-plugin": "^8.56.0", - "@typescript-eslint/parser": "^8.56.0", "all-contributors-cli": "^6.26.1", "commitizen": "^4.3.1", + "conventional-changelog-conventionalcommits": "^9.1.0", "cross-env": "^10.1.0", "cssnano": "^7.1.2", "cz-conventional-changelog": "^3.3.0", "eslint": "^10.0.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", + "glob": "^13.0.5", "globals": "^17.3.0", "husky": "^9.1.7", "is-ci": "^4.1.0", @@ -86,6 +91,7 @@ "prettier": "3.8.1", "prettier-plugin-organize-imports": "^4.3.0", "semantic-release": "^25.0.3", + "sharp": "^0.34.5", "stylelint": "^17.3.0", "stylelint-config-standard": "^40.0.0", "svgo": "^4.0.0", @@ -94,21 +100,18 @@ "typescript-eslint": "^8.56.0", "vite": "^7.3.1", "vite-plugin-codeigniter": "^2.0.0", + "vite-plugin-inspect": "^11.3.3", "vite-plugin-pwa": "^1.2.0", + "vite-plugin-static-copy": "^3.2.0", "workbox-build": "^7.4.0", "workbox-core": "^7.4.0", "workbox-routing": "^7.4.0", "workbox-strategies": "^7.4.0" }, "lint-staged": { - "*.{ts,js}": [ - "eslint --fix", - "prettier --write" - ], - "*.css": [ - "stylelint --fix", - "prettier --write" - ] + "*.{js,ts,css,md,json}": "prettier --write", + "*.{ts,js}": "eslint --fix", + "*.css": "stylelint --fix" }, "config": { "commitizen": { diff --git a/phpstan.neon b/phpstan.neon index 5bf26849..37bf9c76 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -26,6 +26,7 @@ parameters: - Modules\Install\Config\ - Modules\Media\Config\ - Modules\MediaClipper\Config\ + - Modules\Plugins\Config\ - Modules\PodcastImport\Config\ - Modules\PremiumPodcasts\Config\ - Modules\WebSub\Config\ @@ -44,9 +45,9 @@ parameters: - Michalsn\Uuid\Config\Services - Modules\Media\Config\Services - Modules\Platforms\Config\Services + - Modules\Plugins\Config\Services - Modules\PremiumPodcasts\Config\Services - Modules\Api\Rest\V1\Config\Services ignoreErrors: - - '#^Call to an undefined method CodeIgniter\\Cache\\CacheInterface\:\:deleteMatching\(\)#' - identifier: missingType.generics - '#\$callback of static method CodeIgniter\\Events\\Events\:\:on\(\) expects callable\(mixed\)\: mixed, Closure\(mixed, mixed\)\: void given.#' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index aa9d002b..766a18f5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,24 +1,30 @@ + - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd" + bootstrap="vendor/codeigniter4/framework/system/Test/bootstrap.php" + backupGlobals="false" + beStrictAboutOutputDuringTests="true" + colors="true" + columns="max" + failOnRisky="true" + failOnWarning="true" + displayDetailsOnTestsThatTriggerDeprecations="true" + displayDetailsOnTestsThatTriggerErrors="true" + displayDetailsOnTestsThatTriggerNotices="true" + displayDetailsOnTestsThatTriggerWarnings="true" + displayDetailsOnPhpunitDeprecations="true" + cacheDirectory="build/.phpunit.cache" +> + - - - - + + + + @@ -27,34 +33,34 @@ - - - + + + - ./app + ./app - ./app/Views - ./app/Config/Routes.php + ./app/Views + ./app/Config/Routes.php - + - + - + - + - - - - - - - + + + + + + + - + \ No newline at end of file diff --git a/plugins/.gitkeep b/plugins/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b4b4fd4..6e2c7420 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,9 @@ importers: "@codemirror/commands": specifier: ^6.10.2 version: 6.10.2 + "@codemirror/lang-html": + specifier: ^6.4.11 + version: 6.4.11 "@codemirror/lang-xml": specifier: ^6.1.0 version: 6.1.0 @@ -43,9 +46,9 @@ importers: "@github/relative-time-element": specifier: ^5.0.0 version: 5.0.0 - "@tailwindcss/nesting": - specifier: 0.0.0-insiders.565cd3e - version: 0.0.0-insiders.565cd3e(postcss@8.5.6) + "@patternfly/elements": + specifier: ^4.3.1 + version: 4.3.1 "@vime/core": specifier: ^5.4.1 version: 5.4.1 @@ -58,6 +61,9 @@ importers: flatpickr: specifier: ^4.6.13 version: 4.6.13 + htmlfy: + specifier: ^1.0.1 + version: 1.0.1 leaflet: specifier: ^1.9.4 version: 1.9.4 @@ -78,17 +84,20 @@ importers: version: 3.6.7 devDependencies: "@commitlint/cli": - specifier: ^20.4.1 - version: 20.4.1(@types/node@24.3.0)(typescript@5.9.3) + specifier: ^20.4.2 + version: 20.4.2(@types/node@24.7.0)(typescript@5.9.3) "@commitlint/config-conventional": - specifier: ^20.4.1 - version: 20.4.1 + specifier: ^20.4.2 + version: 20.4.2 "@csstools/css-tokenizer": specifier: ^4.0.0 version: 4.0.0 + "@eslint/eslintrc": + specifier: ^3.3.3 + version: 3.3.3 "@eslint/js": - specifier: 10.0.1 - version: 10.0.1(eslint@10.0.0(jiti@2.5.1)) + specifier: ^10.0.1 + version: 10.0.1(eslint@10.0.0(jiti@1.21.7)) "@semantic-release/changelog": specifier: ^6.0.3 version: 6.0.3(semantic-release@25.0.3(typescript@5.9.3)) @@ -103,25 +112,22 @@ importers: version: 13.3.0(semantic-release@25.0.3(typescript@5.9.3)) "@tailwindcss/forms": specifier: ^0.5.11 - version: 0.5.11(tailwindcss@3.4.19) + version: 0.5.11(tailwindcss@3.4.19(yaml@2.8.2)) "@tailwindcss/typography": specifier: ^0.5.19 - version: 0.5.19(tailwindcss@3.4.19) + version: 0.5.19(tailwindcss@3.4.19(yaml@2.8.2)) "@types/leaflet": specifier: ^1.9.21 version: 1.9.21 - "@typescript-eslint/eslint-plugin": - specifier: ^8.56.0 - version: 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) - "@typescript-eslint/parser": - specifier: ^8.56.0 - version: 8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) all-contributors-cli: specifier: ^6.26.1 version: 6.26.1 commitizen: specifier: ^4.3.1 - version: 4.3.1(@types/node@24.3.0)(typescript@5.9.3) + version: 4.3.1(@types/node@24.7.0)(typescript@5.9.3) + conventional-changelog-conventionalcommits: + specifier: ^9.1.0 + version: 9.1.0 cross-env: specifier: ^10.1.0 version: 10.1.0 @@ -130,16 +136,19 @@ importers: version: 7.1.2(postcss@8.5.6) cz-conventional-changelog: specifier: ^3.3.0 - version: 3.3.0(@types/node@24.3.0)(typescript@5.9.3) + version: 3.3.0(@types/node@24.7.0)(typescript@5.9.3) eslint: specifier: ^10.0.0 - version: 10.0.0(jiti@2.5.1) + version: 10.0.0(jiti@1.21.7) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.0.0(jiti@2.5.1)) + version: 10.1.8(eslint@10.0.0(jiti@1.21.7)) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@2.5.1)))(eslint@10.0.0(jiti@2.5.1))(prettier@3.8.1) + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@1.21.7)))(eslint@10.0.0(jiti@1.21.7))(prettier@3.8.1) + glob: + specifier: ^13.0.5 + version: 13.0.5 globals: specifier: ^17.3.0 version: 17.3.0 @@ -176,6 +185,9 @@ importers: semantic-release: specifier: ^25.0.3 version: 25.0.3(typescript@5.9.3) + sharp: + specifier: ^0.34.5 + version: 0.34.5 stylelint: specifier: ^17.3.0 version: 17.3.0(typescript@5.9.3) @@ -187,22 +199,28 @@ importers: version: 4.0.0 tailwindcss: specifier: ^3.4.19 - version: 3.4.19 + version: 3.4.19(yaml@2.8.2) typescript: specifier: ~5.9.3 version: 5.9.3 typescript-eslint: specifier: ^8.56.0 - version: 8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) + version: 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1) + version: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) vite-plugin-codeigniter: specifier: ^2.0.0 - version: 2.0.0(vite@7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)) + version: 2.0.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + vite-plugin-inspect: + specifier: ^11.3.3 + version: 11.3.3(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) vite-plugin-pwa: specifier: ^1.2.0 - version: 1.2.0(vite@7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(workbox-build@7.4.0)(workbox-window@7.4.0) + version: 1.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.4.0)(workbox-window@7.4.0) + vite-plugin-static-copy: + specifier: ^3.2.0 + version: 3.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) workbox-build: specifier: ^7.4.0 version: 7.4.0 @@ -217,28 +235,28 @@ importers: version: 7.4.0 packages: - "@actions/core@2.0.1": + "@actions/core@3.0.0": resolution: { - integrity: sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg==, + integrity: sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==, } - "@actions/exec@2.0.0": + "@actions/exec@3.0.0": resolution: { - integrity: sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==, + integrity: sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==, } - "@actions/http-client@3.0.0": + "@actions/http-client@4.0.0": resolution: { - integrity: sha512-1s3tXAfVMSz9a4ZEBkXXRQD4QhY3+GAsWSbaYpeknPOKEeyRiU3lH+bHiLMZdo2x/fIeQ/hscL1wCkDLVM2DZQ==, + integrity: sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==, } - "@actions/io@2.0.0": + "@actions/io@3.0.2": resolution: { - integrity: sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==, + integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==, } "@alloc/quick-lru@5.2.0": @@ -260,13 +278,6 @@ packages: integrity: sha512-F5RrlWCg/fIRvTnnXenWZg7bTlEWJDvELyvXVAAi5GFvFVF4IegIP1vk5TatkgBzYO5v+SNGj2S3N1MkLwYA8w==, } - "@ampproject/remapping@2.3.0": - resolution: - { - integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, - } - engines: { node: ">=6.0.0" } - "@apideck/better-ajv-errors@0.3.6": resolution: { @@ -276,31 +287,31 @@ packages: peerDependencies: ajv: ">=8" - "@babel/code-frame@7.27.1": + "@babel/code-frame@7.29.0": resolution: { - integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==, + integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==, } engines: { node: ">=6.9.0" } - "@babel/compat-data@7.28.0": + "@babel/compat-data@7.29.0": resolution: { - integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==, + integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==, } engines: { node: ">=6.9.0" } - "@babel/core@7.28.3": + "@babel/core@7.29.0": resolution: { - integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==, + integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==, } engines: { node: ">=6.9.0" } - "@babel/generator@7.28.3": + "@babel/generator@7.29.1": resolution: { - integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==, + integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==, } engines: { node: ">=6.9.0" } @@ -311,35 +322,35 @@ packages: } engines: { node: ">=6.9.0" } - "@babel/helper-compilation-targets@7.27.2": + "@babel/helper-compilation-targets@7.28.6": resolution: { - integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==, + integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==, } engines: { node: ">=6.9.0" } - "@babel/helper-create-class-features-plugin@7.28.3": + "@babel/helper-create-class-features-plugin@7.28.6": resolution: { - integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==, + integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0 - "@babel/helper-create-regexp-features-plugin@7.27.1": + "@babel/helper-create-regexp-features-plugin@7.28.5": resolution: { - integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==, + integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0 - "@babel/helper-define-polyfill-provider@0.6.5": + "@babel/helper-define-polyfill-provider@0.6.6": resolution: { - integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==, + integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==, } peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -351,24 +362,24 @@ packages: } engines: { node: ">=6.9.0" } - "@babel/helper-member-expression-to-functions@7.27.1": + "@babel/helper-member-expression-to-functions@7.28.5": resolution: { - integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==, + integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==, } engines: { node: ">=6.9.0" } - "@babel/helper-module-imports@7.27.1": + "@babel/helper-module-imports@7.28.6": resolution: { - integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==, + integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==, } engines: { node: ">=6.9.0" } - "@babel/helper-module-transforms@7.28.3": + "@babel/helper-module-transforms@7.28.6": resolution: { - integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==, + integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -381,10 +392,10 @@ packages: } engines: { node: ">=6.9.0" } - "@babel/helper-plugin-utils@7.27.1": + "@babel/helper-plugin-utils@7.28.6": resolution: { - integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==, + integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==, } engines: { node: ">=6.9.0" } @@ -397,10 +408,10 @@ packages: peerDependencies: "@babel/core": ^7.0.0 - "@babel/helper-replace-supers@7.27.1": + "@babel/helper-replace-supers@7.28.6": resolution: { - integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==, + integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -420,10 +431,10 @@ packages: } engines: { node: ">=6.9.0" } - "@babel/helper-validator-identifier@7.27.1": + "@babel/helper-validator-identifier@7.28.5": resolution: { - integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==, + integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, } engines: { node: ">=6.9.0" } @@ -434,32 +445,32 @@ packages: } engines: { node: ">=6.9.0" } - "@babel/helper-wrap-function@7.28.3": + "@babel/helper-wrap-function@7.28.6": resolution: { - integrity: sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==, + integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==, } engines: { node: ">=6.9.0" } - "@babel/helpers@7.28.3": + "@babel/helpers@7.28.6": resolution: { - integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==, + integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==, } engines: { node: ">=6.9.0" } - "@babel/parser@7.28.3": + "@babel/parser@7.29.0": resolution: { - integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==, + integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==, } engines: { node: ">=6.0.0" } hasBin: true - "@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1": + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5": resolution: { - integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==, + integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -492,10 +503,10 @@ packages: peerDependencies: "@babel/core": ^7.13.0 - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3": + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6": resolution: { - integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==, + integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -510,19 +521,19 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-syntax-import-assertions@7.27.1": + "@babel/plugin-syntax-import-assertions@7.28.6": resolution: { - integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==, + integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-syntax-import-attributes@7.27.1": + "@babel/plugin-syntax-import-attributes@7.28.6": resolution: { - integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==, + integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -546,19 +557,19 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-async-generator-functions@7.28.0": + "@babel/plugin-transform-async-generator-functions@7.29.0": resolution: { - integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==, + integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-async-to-generator@7.27.1": + "@babel/plugin-transform-async-to-generator@7.28.6": resolution: { - integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==, + integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -573,64 +584,64 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-block-scoping@7.28.0": + "@babel/plugin-transform-block-scoping@7.28.6": resolution: { - integrity: sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==, + integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-class-properties@7.27.1": + "@babel/plugin-transform-class-properties@7.28.6": resolution: { - integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==, + integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-class-static-block@7.28.3": + "@babel/plugin-transform-class-static-block@7.28.6": resolution: { - integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==, + integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.12.0 - "@babel/plugin-transform-classes@7.28.3": + "@babel/plugin-transform-classes@7.28.6": resolution: { - integrity: sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==, + integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-computed-properties@7.27.1": + "@babel/plugin-transform-computed-properties@7.28.6": resolution: { - integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==, + integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-destructuring@7.28.0": + "@babel/plugin-transform-destructuring@7.28.5": resolution: { - integrity: sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==, + integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-dotall-regex@7.27.1": + "@babel/plugin-transform-dotall-regex@7.28.6": resolution: { - integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==, + integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -645,10 +656,10 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1": + "@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0": resolution: { - integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==, + integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -663,19 +674,19 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-explicit-resource-management@7.28.0": + "@babel/plugin-transform-explicit-resource-management@7.28.6": resolution: { - integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==, + integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-exponentiation-operator@7.27.1": + "@babel/plugin-transform-exponentiation-operator@7.28.6": resolution: { - integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==, + integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -708,10 +719,10 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-json-strings@7.27.1": + "@babel/plugin-transform-json-strings@7.28.6": resolution: { - integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==, + integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -726,10 +737,10 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-logical-assignment-operators@7.27.1": + "@babel/plugin-transform-logical-assignment-operators@7.28.6": resolution: { - integrity: sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==, + integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -753,19 +764,19 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-modules-commonjs@7.27.1": + "@babel/plugin-transform-modules-commonjs@7.28.6": resolution: { - integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==, + integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-modules-systemjs@7.27.1": + "@babel/plugin-transform-modules-systemjs@7.29.0": resolution: { - integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==, + integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -780,10 +791,10 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-named-capturing-groups-regex@7.27.1": + "@babel/plugin-transform-named-capturing-groups-regex@7.29.0": resolution: { - integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==, + integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -798,28 +809,28 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-nullish-coalescing-operator@7.27.1": + "@babel/plugin-transform-nullish-coalescing-operator@7.28.6": resolution: { - integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==, + integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-numeric-separator@7.27.1": + "@babel/plugin-transform-numeric-separator@7.28.6": resolution: { - integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==, + integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-object-rest-spread@7.28.0": + "@babel/plugin-transform-object-rest-spread@7.28.6": resolution: { - integrity: sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==, + integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -834,19 +845,19 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-optional-catch-binding@7.27.1": + "@babel/plugin-transform-optional-catch-binding@7.28.6": resolution: { - integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==, + integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-optional-chaining@7.27.1": + "@babel/plugin-transform-optional-chaining@7.28.6": resolution: { - integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==, + integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -861,19 +872,19 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-private-methods@7.27.1": + "@babel/plugin-transform-private-methods@7.28.6": resolution: { - integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==, + integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-private-property-in-object@7.27.1": + "@babel/plugin-transform-private-property-in-object@7.28.6": resolution: { - integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==, + integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -888,19 +899,19 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-regenerator@7.28.3": + "@babel/plugin-transform-regenerator@7.29.0": resolution: { - integrity: sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==, + integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-regexp-modifiers@7.27.1": + "@babel/plugin-transform-regexp-modifiers@7.28.6": resolution: { - integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==, + integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -924,10 +935,10 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-spread@7.27.1": + "@babel/plugin-transform-spread@7.28.6": resolution: { - integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==, + integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -969,10 +980,10 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-unicode-property-regex@7.27.1": + "@babel/plugin-transform-unicode-property-regex@7.28.6": resolution: { - integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==, + integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -987,19 +998,19 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 - "@babel/plugin-transform-unicode-sets-regex@7.27.1": + "@babel/plugin-transform-unicode-sets-regex@7.28.6": resolution: { - integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==, + integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==, } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0 - "@babel/preset-env@7.28.3": + "@babel/preset-env@7.29.0": resolution: { - integrity: sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==, + integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==, } engines: { node: ">=6.9.0" } peerDependencies: @@ -1013,31 +1024,31 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 || ^8.0.0-0 <8.0.0 - "@babel/runtime@7.28.3": + "@babel/runtime@7.28.6": resolution: { - integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==, + integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==, } engines: { node: ">=6.9.0" } - "@babel/template@7.27.2": + "@babel/template@7.28.6": resolution: { - integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==, + integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==, } engines: { node: ">=6.9.0" } - "@babel/traverse@7.28.3": + "@babel/traverse@7.29.0": resolution: { - integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==, + integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==, } engines: { node: ">=6.9.0" } - "@babel/types@7.28.2": + "@babel/types@7.29.0": resolution: { - integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==, + integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, } engines: { node: ">=6.9.0" } @@ -1053,10 +1064,10 @@ packages: integrity: sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==, } - "@codemirror/autocomplete@6.18.6": + "@codemirror/autocomplete@6.20.0": resolution: { - integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==, + integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==, } "@codemirror/commands@6.10.2": @@ -1065,6 +1076,24 @@ packages: integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==, } + "@codemirror/lang-css@6.3.1": + resolution: + { + integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==, + } + + "@codemirror/lang-html@6.4.11": + resolution: + { + integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==, + } + + "@codemirror/lang-javascript@6.2.4": + resolution: + { + integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==, + } + "@codemirror/lang-xml@6.1.0": resolution: { @@ -1077,16 +1106,16 @@ packages: integrity: sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==, } - "@codemirror/lint@6.8.5": + "@codemirror/lint@6.9.4": resolution: { - integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==, + integrity: sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==, } - "@codemirror/search@6.5.11": + "@codemirror/search@6.6.0": resolution: { - integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==, + integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==, } "@codemirror/state@6.5.4": @@ -1108,25 +1137,18 @@ packages: } engines: { node: ">=0.1.90" } - "@commitlint/cli@20.4.1": + "@commitlint/cli@20.4.2": resolution: { - integrity: sha512-uuFKKpc7OtQM+6SRqT+a4kV818o1pS+uvv/gsRhyX7g4x495jg+Q7P0+O9VNGyLXBYP0syksS7gMRDJKcekr6A==, + integrity: sha512-YjYSX2yj/WsVoxh9mNiymfFS2ADbg2EK4+1WAsMuckwKMCqJ5PDG0CJU/8GvmHWcv4VRB2V02KqSiecRksWqZQ==, } engines: { node: ">=v18" } hasBin: true - "@commitlint/config-conventional@20.4.1": + "@commitlint/config-conventional@20.4.2": resolution: { - integrity: sha512-0YUvIeBtpi86XriqrR+TCULVFiyYTIOEPjK7tTRMxjcBm1qlzb+kz7IF2WxL6Fq5DaundG8VO37BNgMkMTBwqA==, - } - engines: { node: ">=v18" } - - "@commitlint/config-validator@19.8.1": - resolution: - { - integrity: sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==, + integrity: sha512-rwkTF55q7Q+6dpSKUmJoScV0f3EpDlWKw2UPzklkLS4o5krMN1tPWAVOgHRtyUTMneIapLeQwaCjn44Td6OzBQ==, } engines: { node: ">=v18" } @@ -1144,13 +1166,6 @@ packages: } engines: { node: ">=v18" } - "@commitlint/execute-rule@19.8.1": - resolution: - { - integrity: sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==, - } - engines: { node: ">=v18" } - "@commitlint/execute-rule@20.0.0": resolution: { @@ -1172,17 +1187,10 @@ packages: } engines: { node: ">=v18" } - "@commitlint/lint@20.4.1": + "@commitlint/lint@20.4.2": resolution: { - integrity: sha512-g94LrGl/c6UhuhDQqNqU232aslLEN2vzc7MPfQTHzwzM4GHNnEAwVWWnh0zX8S5YXecuLXDwbCsoGwmpAgPWKA==, - } - engines: { node: ">=v18" } - - "@commitlint/load@19.8.1": - resolution: - { - integrity: sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==, + integrity: sha512-buquzNRtFng6xjXvBU1abY/WPEEjCgUipNQrNmIWe8QuJ6LWLtei/LDBAzEe5ASm45+Q9L2Xi3/GVvlj50GAug==, } engines: { node: ">=v18" } @@ -1214,13 +1222,6 @@ packages: } engines: { node: ">=v18" } - "@commitlint/resolve-extends@19.8.1": - resolution: - { - integrity: sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==, - } - engines: { node: ">=v18" } - "@commitlint/resolve-extends@20.4.0": resolution: { @@ -1228,10 +1229,10 @@ packages: } engines: { node: ">=v18" } - "@commitlint/rules@20.4.1": + "@commitlint/rules@20.4.2": resolution: { - integrity: sha512-WtqypKEPbQEuJwJS4aKs0OoJRBKz1HXPBC9wRtzVNH68FLhPWzxXlF09hpUXM9zdYTpm4vAdoTGkWiBgQ/vL0g==, + integrity: sha512-oz83pnp5Yq6uwwTAabuVQPNlPfeD2Y5ZjMb7Wx8FSUlu4sLYJjbBWt8031Z0osCFPfHzAwSYrjnfDFKtuSMdKg==, } engines: { node: ">=v18" } @@ -1249,13 +1250,6 @@ packages: } engines: { node: ">=v18" } - "@commitlint/types@19.8.1": - resolution: - { - integrity: sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==, - } - engines: { node: ">=v18" } - "@commitlint/types@20.4.0": resolution: { @@ -1280,16 +1274,6 @@ packages: } engines: { node: ">=20.19.0" } - "@csstools/css-calc@3.0.0": - resolution: - { - integrity: sha512-q4d82GTl8BIlh/dTnVsWmxnbWJeb3kiU8eUH71UxlxnS+WIaALmtzTL8gR15PkYOexMQYVk0CO4qIG93C1IvPA==, - } - engines: { node: ">=20.19.0" } - peerDependencies: - "@csstools/css-parser-algorithms": ^4.0.0 - "@csstools/css-tokenizer": ^4.0.0 - "@csstools/css-calc@3.1.1": resolution: { @@ -1319,10 +1303,10 @@ packages: peerDependencies: "@csstools/css-tokenizer": ^4.0.0 - "@csstools/css-syntax-patches-for-csstree@1.0.26": + "@csstools/css-syntax-patches-for-csstree@1.0.27": resolution: { - integrity: sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==, + integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==, } "@csstools/css-tokenizer@4.0.0": @@ -1750,249 +1734,240 @@ packages: integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==, } - "@esbuild/aix-ppc64@0.27.2": + "@esbuild/aix-ppc64@0.27.3": resolution: { - integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==, + integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==, } engines: { node: ">=18" } cpu: [ppc64] os: [aix] - "@esbuild/android-arm64@0.27.2": + "@esbuild/android-arm64@0.27.3": resolution: { - integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==, + integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==, } engines: { node: ">=18" } cpu: [arm64] os: [android] - "@esbuild/android-arm@0.27.2": + "@esbuild/android-arm@0.27.3": resolution: { - integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==, + integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==, } engines: { node: ">=18" } cpu: [arm] os: [android] - "@esbuild/android-x64@0.27.2": + "@esbuild/android-x64@0.27.3": resolution: { - integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==, + integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==, } engines: { node: ">=18" } cpu: [x64] os: [android] - "@esbuild/darwin-arm64@0.27.2": + "@esbuild/darwin-arm64@0.27.3": resolution: { - integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==, + integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==, } engines: { node: ">=18" } cpu: [arm64] os: [darwin] - "@esbuild/darwin-x64@0.27.2": + "@esbuild/darwin-x64@0.27.3": resolution: { - integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==, + integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==, } engines: { node: ">=18" } cpu: [x64] os: [darwin] - "@esbuild/freebsd-arm64@0.27.2": + "@esbuild/freebsd-arm64@0.27.3": resolution: { - integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==, + integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==, } engines: { node: ">=18" } cpu: [arm64] os: [freebsd] - "@esbuild/freebsd-x64@0.27.2": + "@esbuild/freebsd-x64@0.27.3": resolution: { - integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==, + integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==, } engines: { node: ">=18" } cpu: [x64] os: [freebsd] - "@esbuild/linux-arm64@0.27.2": + "@esbuild/linux-arm64@0.27.3": resolution: { - integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==, + integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==, } engines: { node: ">=18" } cpu: [arm64] os: [linux] - "@esbuild/linux-arm@0.27.2": + "@esbuild/linux-arm@0.27.3": resolution: { - integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==, + integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==, } engines: { node: ">=18" } cpu: [arm] os: [linux] - "@esbuild/linux-ia32@0.27.2": + "@esbuild/linux-ia32@0.27.3": resolution: { - integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==, + integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==, } engines: { node: ">=18" } cpu: [ia32] os: [linux] - "@esbuild/linux-loong64@0.27.2": + "@esbuild/linux-loong64@0.27.3": resolution: { - integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==, + integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==, } engines: { node: ">=18" } cpu: [loong64] os: [linux] - "@esbuild/linux-mips64el@0.27.2": + "@esbuild/linux-mips64el@0.27.3": resolution: { - integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==, + integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==, } engines: { node: ">=18" } cpu: [mips64el] os: [linux] - "@esbuild/linux-ppc64@0.27.2": + "@esbuild/linux-ppc64@0.27.3": resolution: { - integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==, + integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==, } engines: { node: ">=18" } cpu: [ppc64] os: [linux] - "@esbuild/linux-riscv64@0.27.2": + "@esbuild/linux-riscv64@0.27.3": resolution: { - integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==, + integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==, } engines: { node: ">=18" } cpu: [riscv64] os: [linux] - "@esbuild/linux-s390x@0.27.2": + "@esbuild/linux-s390x@0.27.3": resolution: { - integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==, + integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==, } engines: { node: ">=18" } cpu: [s390x] os: [linux] - "@esbuild/linux-x64@0.27.2": + "@esbuild/linux-x64@0.27.3": resolution: { - integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==, + integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==, } engines: { node: ">=18" } cpu: [x64] os: [linux] - "@esbuild/netbsd-arm64@0.27.2": + "@esbuild/netbsd-arm64@0.27.3": resolution: { - integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==, + integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==, } engines: { node: ">=18" } cpu: [arm64] os: [netbsd] - "@esbuild/netbsd-x64@0.27.2": + "@esbuild/netbsd-x64@0.27.3": resolution: { - integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==, + integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==, } engines: { node: ">=18" } cpu: [x64] os: [netbsd] - "@esbuild/openbsd-arm64@0.27.2": + "@esbuild/openbsd-arm64@0.27.3": resolution: { - integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==, + integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==, } engines: { node: ">=18" } cpu: [arm64] os: [openbsd] - "@esbuild/openbsd-x64@0.27.2": + "@esbuild/openbsd-x64@0.27.3": resolution: { - integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==, + integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==, } engines: { node: ">=18" } cpu: [x64] os: [openbsd] - "@esbuild/openharmony-arm64@0.27.2": + "@esbuild/openharmony-arm64@0.27.3": resolution: { - integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==, + integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==, } engines: { node: ">=18" } cpu: [arm64] os: [openharmony] - "@esbuild/sunos-x64@0.27.2": + "@esbuild/sunos-x64@0.27.3": resolution: { - integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==, + integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==, } engines: { node: ">=18" } cpu: [x64] os: [sunos] - "@esbuild/win32-arm64@0.27.2": + "@esbuild/win32-arm64@0.27.3": resolution: { - integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==, + integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==, } engines: { node: ">=18" } cpu: [arm64] os: [win32] - "@esbuild/win32-ia32@0.27.2": + "@esbuild/win32-ia32@0.27.3": resolution: { - integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==, + integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==, } engines: { node: ">=18" } cpu: [ia32] os: [win32] - "@esbuild/win32-x64@0.27.2": + "@esbuild/win32-x64@0.27.3": resolution: { - integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==, + integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==, } engines: { node: ">=18" } cpu: [x64] os: [win32] - "@eslint-community/eslint-utils@4.9.0": - resolution: - { - integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - "@eslint-community/eslint-utils@4.9.1": resolution: { @@ -2030,6 +2005,13 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + "@eslint/eslintrc@3.3.3": + resolution: + { + integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@eslint/js@10.0.1": resolution: { @@ -2056,13 +2038,6 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - "@fastify/busboy@2.1.1": - resolution: - { - integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==, - } - engines: { node: ">=14" } - "@floating-ui/core@1.7.4": resolution: { @@ -2136,10 +2111,10 @@ packages: } engines: { node: ">=18.18.0" } - "@humanfs/node@0.16.6": + "@humanfs/node@0.16.7": resolution: { - integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==, + integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==, } engines: { node: ">=18.18.0" } @@ -2150,13 +2125,6 @@ packages: } engines: { node: ">=12.22" } - "@humanwhocodes/retry@0.3.1": - resolution: - { - integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==, - } - engines: { node: ">=18.18" } - "@humanwhocodes/retry@0.4.3": resolution: { @@ -2392,26 +2360,12 @@ packages: cpu: [x64] os: [win32] - "@isaacs/balanced-match@4.0.1": + "@isaacs/cliui@9.0.0": resolution: { - integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==, + integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==, } - engines: { node: 20 || >=22 } - - "@isaacs/brace-expansion@5.0.0": - resolution: - { - integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==, - } - engines: { node: 20 || >=22 } - - "@isaacs/cliui@8.0.2": - resolution: - { - integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, - } - engines: { node: ">=12" } + engines: { node: ">=18" } "@jridgewell/gen-mapping@0.3.13": resolution: @@ -2419,6 +2373,12 @@ packages: integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, } + "@jridgewell/remapping@2.3.5": + resolution: + { + integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==, + } + "@jridgewell/resolve-uri@3.1.2": resolution: { @@ -2438,20 +2398,20 @@ packages: integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, } - "@jridgewell/trace-mapping@0.3.30": + "@jridgewell/trace-mapping@0.3.31": resolution: { - integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==, + integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, } - "@keyv/bigmap@1.3.0": + "@keyv/bigmap@1.3.1": resolution: { - integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==, + integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==, } engines: { node: ">= 18" } peerDependencies: - keyv: ^5.5.4 + keyv: ^5.6.0 "@keyv/serialize@1.1.1": resolution: @@ -2459,28 +2419,40 @@ packages: integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==, } - "@lezer/common@1.2.3": - resolution: - { - integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==, - } - "@lezer/common@1.5.1": resolution: { integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==, } - "@lezer/highlight@1.2.1": + "@lezer/css@1.3.1": resolution: { - integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==, + integrity: sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==, } - "@lezer/lr@1.4.2": + "@lezer/highlight@1.2.3": resolution: { - integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==, + integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==, + } + + "@lezer/html@1.3.13": + resolution: + { + integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==, + } + + "@lezer/javascript@1.5.4": + resolution: + { + integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==, + } + + "@lezer/lr@1.4.8": + resolution: + { + integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==, } "@lezer/xml@1.0.6": @@ -2489,16 +2461,22 @@ packages: integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==, } - "@lit-labs/ssr-dom-shim@1.4.0": + "@lit-labs/ssr-dom-shim@1.5.1": resolution: { - integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==, + integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==, } - "@lit/reactive-element@2.1.1": + "@lit/context@1.1.6": resolution: { - integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==, + integrity: sha512-M26qDE6UkQbZA2mQ3RjJ3Gzd8TxP+/0obMgE5HfkfLhEEyYE3Bui4A5XHiGPjy0MUGAyxB3QgVuw2ciS0kHn6A==, + } + + "@lit/reactive-element@2.1.2": + resolution: + { + integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==, } "@marijn/find-cluster-break@1.0.2": @@ -2535,33 +2513,27 @@ packages: } engines: { node: ">= 20" } - "@octokit/core@7.0.3": + "@octokit/core@7.0.6": resolution: { - integrity: sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==, + integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==, } engines: { node: ">= 20" } - "@octokit/endpoint@11.0.0": + "@octokit/endpoint@11.0.3": resolution: { - integrity: sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==, + integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==, } engines: { node: ">= 20" } - "@octokit/graphql@9.0.1": + "@octokit/graphql@9.0.3": resolution: { - integrity: sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==, + integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==, } engines: { node: ">= 20" } - "@octokit/openapi-types@25.1.0": - resolution: - { - integrity: sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==, - } - "@octokit/openapi-types@27.0.0": resolution: { @@ -2577,56 +2549,61 @@ packages: peerDependencies: "@octokit/core": ">=6" - "@octokit/plugin-retry@8.0.1": + "@octokit/plugin-retry@8.1.0": resolution: { - integrity: sha512-KUoYR77BjF5O3zcwDQHRRZsUvJwepobeqiSSdCJ8lWt27FZExzb0GgVxrhhfuyF6z2B2zpO0hN5pteni1sqWiw==, + integrity: sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==, } engines: { node: ">= 20" } peerDependencies: "@octokit/core": ">=7" - "@octokit/plugin-throttling@11.0.1": + "@octokit/plugin-throttling@11.0.3": resolution: { - integrity: sha512-S+EVhy52D/272L7up58dr3FNSMXWuNZolkL4zMJBNIfIxyZuUcczsQAU4b5w6dewJXnKYVgSHSV5wxitMSW1kw==, + integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==, } engines: { node: ">= 20" } peerDependencies: "@octokit/core": ^7.0.0 - "@octokit/request-error@7.0.0": + "@octokit/request-error@7.1.0": resolution: { - integrity: sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==, + integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==, } engines: { node: ">= 20" } - "@octokit/request@10.0.3": + "@octokit/request@10.0.7": resolution: { - integrity: sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==, + integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==, } engines: { node: ">= 20" } - "@octokit/types@14.1.0": - resolution: - { - integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==, - } - "@octokit/types@16.0.0": resolution: { integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==, } - "@pkgjs/parseargs@0.11.0": + "@patternfly/elements@4.3.1": resolution: { - integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, + integrity: sha512-MRVwxcam+ACyy+0Xy5igPr+LcSVRbX422NGPE4I7WRuwAEhRBA3BayyLi8mNVKXpLLZbk8EtJ17kM30PcMziMw==, + } + + "@patternfly/icons@1.0.3": + resolution: + { + integrity: sha512-8BARaCFBUZU2/TxuOQb8R2/VIpxGMnFwdw5ddT1AMnR2KSifdo+d05SgZtVmFkOIAOA0oCo/YKRgSORDA47wig==, + } + + "@patternfly/pfe-core@5.0.6": + resolution: + { + integrity: sha512-95j0BDltTTVQzOSIqpiZXQYzm1kuwqpHeB/7/QOjmZP3wtvYcaccnE/2dldKF68rn0Rwgk9xbgz7MR8/CnKFgQ==, } - engines: { node: ">=14" } "@pkgr/core@0.2.9": resolution: @@ -2649,13 +2626,19 @@ packages: } engines: { node: ">=12.22.0" } - "@pnpm/npm-conf@2.3.1": + "@pnpm/npm-conf@3.0.2": resolution: { - integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==, + integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==, } engines: { node: ">=12" } + "@polka/url@1.0.0-next.29": + resolution: + { + integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==, + } + "@rollup/plugin-babel@5.3.1": resolution: { @@ -2711,10 +2694,10 @@ packages: peerDependencies: rollup: ^1.20.0||^2.0.0 - "@rollup/pluginutils@5.2.0": + "@rollup/pluginutils@5.3.0": resolution: { - integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==, + integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==, } engines: { node: ">=14.0.0" } peerDependencies: @@ -2723,173 +2706,215 @@ packages: rollup: optional: true - "@rollup/rollup-android-arm-eabi@4.47.1": + "@rollup/rollup-android-arm-eabi@4.57.1": resolution: { - integrity: sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==, + integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==, } cpu: [arm] os: [android] - "@rollup/rollup-android-arm64@4.47.1": + "@rollup/rollup-android-arm64@4.57.1": resolution: { - integrity: sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==, + integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==, } cpu: [arm64] os: [android] - "@rollup/rollup-darwin-arm64@4.47.1": + "@rollup/rollup-darwin-arm64@4.57.1": resolution: { - integrity: sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==, + integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==, } cpu: [arm64] os: [darwin] - "@rollup/rollup-darwin-x64@4.47.1": + "@rollup/rollup-darwin-x64@4.57.1": resolution: { - integrity: sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==, + integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==, } cpu: [x64] os: [darwin] - "@rollup/rollup-freebsd-arm64@4.47.1": + "@rollup/rollup-freebsd-arm64@4.57.1": resolution: { - integrity: sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==, + integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==, } cpu: [arm64] os: [freebsd] - "@rollup/rollup-freebsd-x64@4.47.1": + "@rollup/rollup-freebsd-x64@4.57.1": resolution: { - integrity: sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==, + integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==, } cpu: [x64] os: [freebsd] - "@rollup/rollup-linux-arm-gnueabihf@4.47.1": + "@rollup/rollup-linux-arm-gnueabihf@4.57.1": resolution: { - integrity: sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==, + integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==, } cpu: [arm] os: [linux] libc: [glibc] - "@rollup/rollup-linux-arm-musleabihf@4.47.1": + "@rollup/rollup-linux-arm-musleabihf@4.57.1": resolution: { - integrity: sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==, + integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==, } cpu: [arm] os: [linux] libc: [musl] - "@rollup/rollup-linux-arm64-gnu@4.47.1": + "@rollup/rollup-linux-arm64-gnu@4.57.1": resolution: { - integrity: sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==, + integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==, } cpu: [arm64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-arm64-musl@4.47.1": + "@rollup/rollup-linux-arm64-musl@4.57.1": resolution: { - integrity: sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==, + integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==, } cpu: [arm64] os: [linux] libc: [musl] - "@rollup/rollup-linux-loongarch64-gnu@4.47.1": + "@rollup/rollup-linux-loong64-gnu@4.57.1": resolution: { - integrity: sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==, + integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==, } cpu: [loong64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-ppc64-gnu@4.47.1": + "@rollup/rollup-linux-loong64-musl@4.57.1": resolution: { - integrity: sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==, + integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==, + } + cpu: [loong64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-ppc64-gnu@4.57.1": + resolution: + { + integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==, } cpu: [ppc64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-riscv64-gnu@4.47.1": + "@rollup/rollup-linux-ppc64-musl@4.57.1": resolution: { - integrity: sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==, + integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==, + } + cpu: [ppc64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-riscv64-gnu@4.57.1": + resolution: + { + integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==, } cpu: [riscv64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-riscv64-musl@4.47.1": + "@rollup/rollup-linux-riscv64-musl@4.57.1": resolution: { - integrity: sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==, + integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==, } cpu: [riscv64] os: [linux] libc: [musl] - "@rollup/rollup-linux-s390x-gnu@4.47.1": + "@rollup/rollup-linux-s390x-gnu@4.57.1": resolution: { - integrity: sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==, + integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==, } cpu: [s390x] os: [linux] libc: [glibc] - "@rollup/rollup-linux-x64-gnu@4.47.1": + "@rollup/rollup-linux-x64-gnu@4.57.1": resolution: { - integrity: sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==, + integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==, } cpu: [x64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-x64-musl@4.47.1": + "@rollup/rollup-linux-x64-musl@4.57.1": resolution: { - integrity: sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==, + integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==, } cpu: [x64] os: [linux] libc: [musl] - "@rollup/rollup-win32-arm64-msvc@4.47.1": + "@rollup/rollup-openbsd-x64@4.57.1": resolution: { - integrity: sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==, + integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==, + } + cpu: [x64] + os: [openbsd] + + "@rollup/rollup-openharmony-arm64@4.57.1": + resolution: + { + integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==, + } + cpu: [arm64] + os: [openharmony] + + "@rollup/rollup-win32-arm64-msvc@4.57.1": + resolution: + { + integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==, } cpu: [arm64] os: [win32] - "@rollup/rollup-win32-ia32-msvc@4.47.1": + "@rollup/rollup-win32-ia32-msvc@4.57.1": resolution: { - integrity: sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==, + integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==, } cpu: [ia32] os: [win32] - "@rollup/rollup-win32-x64-msvc@4.47.1": + "@rollup/rollup-win32-x64-gnu@4.57.1": resolution: { - integrity: sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==, + integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==, + } + cpu: [x64] + os: [win32] + + "@rollup/rollup-win32-x64-msvc@4.57.1": + resolution: + { + integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==, } cpu: [x64] os: [win32] @@ -2950,10 +2975,10 @@ packages: peerDependencies: semantic-release: ">=18.0.0" - "@semantic-release/github@12.0.2": + "@semantic-release/github@12.0.6": resolution: { - integrity: sha512-qyqLS+aSGH1SfXIooBKjs7mvrv0deg8v+jemegfJg1kq6ji+GJV8CO08VJDEsvjp3O8XJmTTIAjjZbMzagzsdw==, + integrity: sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==, } engines: { node: ^22.14.0 || >= 24.10.0 } peerDependencies: @@ -2968,10 +2993,10 @@ packages: peerDependencies: semantic-release: ">=20.1.0" - "@semantic-release/npm@13.1.3": + "@semantic-release/npm@13.1.4": resolution: { - integrity: sha512-q7zreY8n9V0FIP1Cbu63D+lXtRAVAIWb30MH5U3TdrfXt6r2MIrWCY0whAImN53qNvSGp0Zt07U95K+Qp9GpEg==, + integrity: sha512-z5Fn9ftK1QQgFxMSuOd3DtYbTl4hWI2trCEvZcEJMQJy1/OBR0WHcxqzfVun455FSkHML8KgvPxJEa9MtZIBsg==, } engines: { node: ^22.14.0 || >= 24.10.0 } peerDependencies: @@ -2993,10 +3018,10 @@ packages: } engines: { node: ">=10" } - "@sindresorhus/is@7.0.2": + "@sindresorhus/is@7.2.0": resolution: { - integrity: sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==, + integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==, } engines: { node: ">=18" } @@ -3028,13 +3053,6 @@ packages: integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==, } - "@szmarczak/http-timer@5.0.1": - resolution: - { - integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==, - } - engines: { node: ">=14.16" } - "@tailwindcss/forms@0.5.11": resolution: { @@ -3043,14 +3061,6 @@ packages: peerDependencies: tailwindcss: ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" - "@tailwindcss/nesting@0.0.0-insiders.565cd3e": - resolution: - { - integrity: sha512-WhHoFBx19TnH/c+xLwT/sxei6+4RpdfiyG3MYXfmLaMsADmVqBkF7B6lDalgZD9YdM459MF7DtxVbWkOrV7IaQ==, - } - peerDependencies: - postcss: ^8.2.15 - "@tailwindcss/typography@0.5.19": resolution: { @@ -3059,10 +3069,10 @@ packages: peerDependencies: tailwindcss: ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - "@types/conventional-commits-parser@5.0.1": + "@types/eslint@9.6.1": resolution: { - integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==, + integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==, } "@types/esrecurse@4.3.1": @@ -3095,10 +3105,10 @@ packages: integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==, } - "@types/http-cache-semantics@4.0.4": + "@types/http-cache-semantics@4.2.0": resolution: { - integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==, + integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==, } "@types/json-schema@7.0.15": @@ -3113,10 +3123,10 @@ packages: integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==, } - "@types/node@24.3.0": + "@types/node@24.7.0": resolution: { - integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==, + integrity: sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==, } "@types/normalize-package-data@2.4.4": @@ -3275,10 +3285,10 @@ packages: integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, } - ajv@8.17.1: + ajv@8.18.0: resolution: { - integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==, + integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==, } all-contributors-cli@6.26.1: @@ -3296,10 +3306,10 @@ packages: } engines: { node: ">=8" } - ansi-escapes@7.0.0: + ansi-escapes@7.3.0: resolution: { - integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==, + integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==, } engines: { node: ">=18" } @@ -3310,10 +3320,10 @@ packages: } engines: { node: ">=8" } - ansi-regex@6.2.0: + ansi-regex@6.2.2: resolution: { - integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==, + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, } engines: { node: ">=12" } @@ -3331,13 +3341,20 @@ packages: } engines: { node: ">=8" } - ansi-styles@6.2.1: + ansi-styles@6.2.3: resolution: { - integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==, + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, } engines: { node: ">=12" } + ansis@4.2.0: + resolution: + { + integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==, + } + engines: { node: ">=14" } + any-promise@1.3.0: resolution: { @@ -3416,10 +3433,10 @@ packages: } engines: { node: ">= 4.0.0" } - autoprefixer@10.4.23: + autoprefixer@10.4.24: resolution: { - integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==, + integrity: sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==, } engines: { node: ^10 || ^12 || >=14 } hasBin: true @@ -3433,26 +3450,26 @@ packages: } engines: { node: ">= 0.4" } - babel-plugin-polyfill-corejs2@0.4.14: + babel-plugin-polyfill-corejs2@0.4.15: resolution: { - integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==, + integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==, } peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.13.0: + babel-plugin-polyfill-corejs3@0.14.0: resolution: { - integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==, + integrity: sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==, } peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.5: + babel-plugin-polyfill-regenerator@0.6.6: resolution: { - integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==, + integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==, } peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -3470,6 +3487,13 @@ packages: } engines: { node: ">= 16" } + balanced-match@4.0.3: + resolution: + { + integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==, + } + engines: { node: 20 || >=22 } + base64-js@1.3.1: resolution: { @@ -3482,11 +3506,12 @@ packages: integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, } - baseline-browser-mapping@2.9.11: + baseline-browser-mapping@2.10.0: resolution: { - integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==, + integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==, } + engines: { node: ">=6.0.0" } hasBin: true before-after-hook@4.0.0: @@ -3502,6 +3527,12 @@ packages: } engines: { node: ">=8" } + birpc@2.9.0: + resolution: + { + integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==, + } + bl@4.1.0: resolution: { @@ -3532,6 +3563,13 @@ packages: integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, } + brace-expansion@5.0.2: + resolution: + { + integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==, + } + engines: { node: 20 || >=22 } + braces@3.0.3: resolution: { @@ -3545,14 +3583,6 @@ packages: integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==, } - browserslist@4.25.3: - resolution: - { - integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==, - } - engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } - hasBin: true - browserslist@4.28.1: resolution: { @@ -3573,6 +3603,20 @@ packages: integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, } + bundle-name@4.1.0: + resolution: + { + integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==, + } + engines: { node: ">=18" } + + byte-counter@0.1.0: + resolution: + { + integrity: sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ==, + } + engines: { node: ">=20" } + cacheable-lookup@7.0.0: resolution: { @@ -3580,10 +3624,10 @@ packages: } engines: { node: ">=14.16" } - cacheable-request@12.0.1: + cacheable-request@13.0.18: resolution: { - integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==, + integrity: sha512-rFWadDRKJs3s2eYdXlGggnBZKG7MTblkFBB0YllFds+UYnfogDp2wcR6JN97FhRkHTvq59n2vhNoHNZn29dh/Q==, } engines: { node: ">=18" } @@ -3648,16 +3692,10 @@ packages: integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==, } - caniuse-lite@1.0.30001737: + caniuse-lite@1.0.30001770: resolution: { - integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==, - } - - caniuse-lite@1.0.30001761: - resolution: - { - integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==, + integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==, } chalk@2.4.2: @@ -3674,10 +3712,10 @@ packages: } engines: { node: ">=10" } - chalk@5.6.0: + chalk@5.6.2: resolution: { - integrity: sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==, + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, } engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } @@ -3707,10 +3745,10 @@ packages: } engines: { node: ">= 8.10.0" } - ci-info@4.3.0: + ci-info@4.4.0: resolution: { - integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==, + integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==, } engines: { node: ">=8" } @@ -3721,10 +3759,10 @@ packages: } engines: { node: ">=6" } - clean-stack@5.2.0: + clean-stack@5.3.0: resolution: { - integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==, + integrity: sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==, } engines: { node: ">=14.16" } @@ -3861,10 +3899,10 @@ packages: } engines: { node: ">=16" } - commander@14.0.2: + commander@14.0.3: resolution: { - integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==, + integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==, } engines: { node: ">=20" } @@ -3921,13 +3959,6 @@ packages: integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==, } - conventional-changelog-angular@8.0.0: - resolution: - { - integrity: sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==, - } - engines: { node: ">=18" } - conventional-changelog-angular@8.1.0: resolution: { @@ -3963,14 +3994,6 @@ packages: } engines: { node: ">=18" } - conventional-commits-parser@6.2.0: - resolution: - { - integrity: sha512-uLnoLeIW4XaoFtH37qEcg/SXMJmKF4vi7V0H2rnPueg+VEtFGA/asSCNTcq4M/GQ6QmlzchAEtOoDTtKqWeHag==, - } - engines: { node: ">=18" } - hasBin: true - conventional-commits-parser@6.2.1: resolution: { @@ -3992,16 +4015,16 @@ packages: integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, } - core-js-compat@3.45.1: + core-js-compat@3.48.0: resolution: { - integrity: sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==, + integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==, } - core-js@3.45.1: + core-js@3.48.0: resolution: { - integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==, + integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==, } core-util-is@1.0.3: @@ -4010,10 +4033,10 @@ packages: integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, } - cosmiconfig-typescript-loader@6.1.0: + cosmiconfig-typescript-loader@6.2.0: resolution: { - integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==, + integrity: sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==, } engines: { node: ">=v18" } peerDependencies: @@ -4083,21 +4106,21 @@ packages: peerDependencies: postcss: ^8.4 - css-declaration-sorter@7.2.0: + css-declaration-sorter@7.3.1: resolution: { - integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==, + integrity: sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==, } engines: { node: ^14 || ^16 || >=18 } peerDependencies: postcss: ^8.0.9 - css-functions-list@3.2.3: + css-functions-list@3.3.3: resolution: { - integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==, + integrity: sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==, } - engines: { node: ">=12 || >=16" } + engines: { node: ">=12" } css-has-pseudo@8.0.0: resolution: @@ -4314,18 +4337,6 @@ packages: } engines: { node: ">= 0.4" } - debug@4.4.1: - resolution: - { - integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, - } - engines: { node: ">=6.0" } - peerDependencies: - supports-color: "*" - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: { @@ -4345,12 +4356,12 @@ packages: } engines: { node: ">=0.10.0" } - decompress-response@6.0.0: + decompress-response@10.0.0: resolution: { - integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==, + integrity: sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==, } - engines: { node: ">=10" } + engines: { node: ">=20" } dedent@0.7.0: resolution: @@ -4385,19 +4396,26 @@ packages: } engines: { node: ">=0.10.0" } + default-browser-id@5.0.1: + resolution: + { + integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==, + } + engines: { node: ">=18" } + + default-browser@5.5.0: + resolution: + { + integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==, + } + engines: { node: ">=18" } + defaults@1.0.4: resolution: { integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==, } - defer-to-connect@2.0.1: - resolution: - { - integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==, - } - engines: { node: ">=10" } - define-data-property@1.1.4: resolution: { @@ -4405,6 +4423,13 @@ packages: } engines: { node: ">= 0.4" } + define-lazy-prop@3.0.0: + resolution: + { + integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==, + } + engines: { node: ">=12" } + define-properties@1.2.1: resolution: { @@ -4503,12 +4528,6 @@ packages: integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==, } - eastasianwidth@0.2.0: - resolution: - { - integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, - } - ejs@3.1.10: resolution: { @@ -4517,22 +4536,16 @@ packages: engines: { node: ">=0.10.0" } hasBin: true - electron-to-chromium@1.5.208: + electron-to-chromium@1.5.286: resolution: { - integrity: sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==, + integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==, } - electron-to-chromium@1.5.267: + emoji-regex@10.6.0: resolution: { - integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==, - } - - emoji-regex@10.4.0: - resolution: - { - integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==, + integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, } emoji-regex@8.0.0: @@ -4541,12 +4554,6 @@ packages: integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, } - emoji-regex@9.2.2: - resolution: - { - integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, - } - emojilib@2.4.0: resolution: { @@ -4581,16 +4588,22 @@ packages: } engines: { node: ">=18" } - error-ex@1.3.2: + error-ex@1.3.4: resolution: { - integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, + integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==, } - es-abstract@1.24.0: + error-stack-parser-es@1.0.5: resolution: { - integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==, + integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==, + } + + es-abstract@1.24.1: + resolution: + { + integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==, } engines: { node: ">= 0.4" } @@ -4629,10 +4642,10 @@ packages: } engines: { node: ">= 0.4" } - esbuild@0.27.2: + esbuild@0.27.3: resolution: { - integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==, + integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==, } engines: { node: ">=18" } hasBin: true @@ -4705,6 +4718,13 @@ packages: } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + eslint-visitor-keys@4.2.1: + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + eslint-visitor-keys@5.0.0: resolution: { @@ -4725,6 +4745,13 @@ packages: jiti: optional: true + espree@10.4.0: + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + espree@11.1.0: resolution: { @@ -4772,10 +4799,10 @@ packages: } engines: { node: ">=0.10.0" } - eventemitter3@5.0.1: + eventemitter3@5.0.4: resolution: { - integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==, + integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, } execa@5.1.1: @@ -4792,10 +4819,10 @@ packages: } engines: { node: ">=16.17" } - execa@9.6.0: + execa@9.6.1: resolution: { - integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==, + integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==, } engines: { node: ^18.19.0 || >=20.5.0 } @@ -4850,10 +4877,10 @@ packages: integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, } - fast-uri@3.0.6: + fast-uri@3.1.0: resolution: { - integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==, + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, } fastest-levenshtein@1.0.16: @@ -4863,10 +4890,10 @@ packages: } engines: { node: ">= 4.9.1" } - fastq@1.19.1: + fastq@1.20.1: resolution: { - integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==, + integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==, } fdir@6.5.0: @@ -5047,10 +5074,10 @@ packages: integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==, } - fs-extra@11.3.1: + fs-extra@11.3.3: resolution: { - integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==, + integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==, } engines: { node: ">=14.14" } @@ -5114,6 +5141,13 @@ packages: } engines: { node: ">=10" } + generator-function@2.0.1: + resolution: + { + integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==, + } + engines: { node: ">= 0.4" } + gensync@1.0.0-beta.2: resolution: { @@ -5128,10 +5162,10 @@ packages: } engines: { node: 6.* || 8.* || >= 10.* } - get-east-asian-width@1.3.0: + get-east-asian-width@1.5.0: resolution: { - integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==, + integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==, } engines: { node: ">=18" } @@ -5218,14 +5252,6 @@ packages: } engines: { node: ">=10.13.0" } - glob@10.4.5: - resolution: - { - integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==, - } - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - glob@11.1.0: resolution: { @@ -5235,12 +5261,19 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@13.0.5: + resolution: + { + integrity: sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw==, + } + engines: { node: 20 || >=22 } + glob@7.2.3: resolution: { integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, } - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-directory@4.0.1: resolution: @@ -5277,6 +5310,13 @@ packages: } engines: { node: ">=6" } + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + } + engines: { node: ">=18" } + globals@17.3.0: resolution: { @@ -5298,10 +5338,10 @@ packages: } engines: { node: ">=18" } - globby@16.1.0: + globby@16.1.1: resolution: { - integrity: sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==, + integrity: sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==, } engines: { node: ">=20" } @@ -5318,10 +5358,10 @@ packages: } engines: { node: ">= 0.4" } - got@14.4.7: + got@14.6.6: resolution: { - integrity: sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==, + integrity: sha512-QLV1qeYSo5l13mQzWgP/y0LbMr5Plr5fJilgAIwgnwseproEbtNym8xpLsDzeZ6MWXgNE6kdWGBjdh3zT/Qerg==, } engines: { node: ">=20" } @@ -5400,10 +5440,10 @@ packages: } engines: { node: ">= 0.4" } - hashery@1.3.0: + hashery@1.5.0: resolution: { - integrity: sha512-fWltioiy5zsSAs9ouEnvhsVJeAXRybGCNNv0lvzpzNOSDbULXRy7ivFWwCCv4I5Am6kSo75hmbsCduOoc2/K4w==, + integrity: sha512-nhQ6ExaOIqti2FDWoEMWARUqIKyjr2VcZzXShrI+A3zpeiuPWzx6iPftt44LhP74E5sW36B75N6VHbvRtpvO6Q==, } engines: { node: ">=20" } @@ -5468,6 +5508,12 @@ packages: } engines: { node: ">=20.10" } + htmlfy@1.0.1: + resolution: + { + integrity: sha512-M85PmyEpWUDqhlEknsnvqmqGLq67h8e86WuKLMdXQdUl8iyvOSDbAAyv0tv0IVmIfh7Py1Vsot/IU1skdswt6g==, + } + http-cache-semantics@4.2.0: resolution: { @@ -5531,10 +5577,10 @@ packages: } engines: { node: ">=0.10.0" } - iconv-lite@0.6.3: + iconv-lite@0.7.2: resolution: { - integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, + integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==, } engines: { node: ">=0.10.0" } @@ -5578,12 +5624,6 @@ packages: } engines: { node: ">=18.20" } - import-meta-resolve@4.1.0: - resolution: - { - integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==, - } - import-meta-resolve@4.2.0: resolution: { @@ -5611,10 +5651,10 @@ packages: } engines: { node: ">=12" } - index-to-position@1.1.0: + index-to-position@1.2.0: resolution: { - integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==, + integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==, } engines: { node: ">=18" } @@ -5762,6 +5802,14 @@ packages: } engines: { node: ">= 0.4" } + is-docker@3.0.0: + resolution: + { + integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + hasBin: true + is-extglob@2.1.1: resolution: { @@ -5783,17 +5831,17 @@ packages: } engines: { node: ">=8" } - is-fullwidth-code-point@5.0.0: + is-fullwidth-code-point@5.1.0: resolution: { - integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==, + integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==, } engines: { node: ">=18" } - is-generator-function@1.1.0: + is-generator-function@1.1.2: resolution: { - integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==, + integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==, } engines: { node: ">= 0.4" } @@ -5804,6 +5852,14 @@ packages: } engines: { node: ">=0.10.0" } + is-inside-container@1.0.0: + resolution: + { + integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, + } + engines: { node: ">=14.16" } + hasBin: true + is-interactive@1.0.0: resolution: { @@ -5998,6 +6054,13 @@ packages: } engines: { node: ">=0.10.0" } + is-wsl@3.1.1: + resolution: + { + integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==, + } + engines: { node: ">=16" } + isarray@1.0.0: resolution: { @@ -6023,16 +6086,10 @@ packages: } engines: { node: ^18.17 || >=20.6.1 } - jackspeak@3.4.3: + jackspeak@4.2.3: resolution: { - integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, - } - - jackspeak@4.1.1: - resolution: - { - integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==, + integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==, } engines: { node: 20 || >=22 } @@ -6058,10 +6115,10 @@ packages: } hasBin: true - jiti@2.5.1: + jiti@2.6.1: resolution: { - integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==, + integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==, } hasBin: true @@ -6070,6 +6127,7 @@ packages: { integrity: sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==, } + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. js-tokens@4.0.0: resolution: @@ -6077,21 +6135,13 @@ packages: integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, } - js-yaml@4.1.0: + js-yaml@4.1.1: resolution: { - integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, } hasBin: true - jsesc@3.0.2: - resolution: - { - integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==, - } - engines: { node: ">=6" } - hasBin: true - jsesc@3.1.0: resolution: { @@ -6251,16 +6301,16 @@ packages: } engines: { node: ">=20.0.0" } - lit-element@4.2.1: + lit-element@4.2.2: resolution: { - integrity: sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==, + integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==, } - lit-html@3.3.1: + lit-html@3.3.2: resolution: { - integrity: sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==, + integrity: sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==, } lit@3.3.2: @@ -6297,10 +6347,10 @@ packages: } engines: { node: ">=10" } - lodash-es@4.17.21: + lodash-es@4.17.23: resolution: { - integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==, + integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==, } lodash.camelcase@4.3.0: @@ -6357,12 +6407,6 @@ packages: integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==, } - lodash.merge@4.6.2: - resolution: - { - integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, - } - lodash.mergewith@4.6.2: resolution: { @@ -6417,6 +6461,12 @@ packages: integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, } + lodash@4.17.23: + resolution: + { + integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==, + } + log-symbols@4.1.0: resolution: { @@ -6451,10 +6501,10 @@ packages: integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, } - lru-cache@11.2.4: + lru-cache@11.2.6: resolution: { - integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==, + integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==, } engines: { node: 20 || >=22 } @@ -6470,6 +6520,13 @@ packages: integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==, } + make-asynchronous@1.0.1: + resolution: + { + integrity: sha512-T9BPOmEOhp6SmV25SwLVcHK4E6JyG/coH3C6F1NjNXSziv/fd4GmsqMk8YR6qpPOswfaOCApSNkZv6fxoaYFcQ==, + } + engines: { node: ">=18" } + marked-terminal@7.3.0: resolution: { @@ -6567,10 +6624,10 @@ packages: } engines: { node: ">=8.6" } - mime@4.0.7: + mime@4.1.0: resolution: { - integrity: sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==, + integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==, } engines: { node: ">=16" } hasBin: true @@ -6596,13 +6653,6 @@ packages: } engines: { node: ">=18" } - mimic-response@3.1.0: - resolution: - { - integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==, - } - engines: { node: ">=10" } - mimic-response@4.0.0: resolution: { @@ -6617,10 +6667,10 @@ packages: } hasBin: true - minimatch@10.1.1: + minimatch@10.2.1: resolution: { - integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==, + integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==, } engines: { node: 20 || >=22 } @@ -6656,10 +6706,10 @@ packages: integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, } - minipass@7.1.2: + minipass@7.1.3: resolution: { - integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, + integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==, } engines: { node: ">=16 || 14 >=14.17" } @@ -6669,6 +6719,13 @@ packages: integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==, } + mrmime@2.0.1: + resolution: + { + integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==, + } + engines: { node: ">=10" } + ms@2.1.3: resolution: { @@ -6739,12 +6796,6 @@ packages: encoding: optional: true - node-releases@2.0.19: - resolution: - { - integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, - } - node-releases@2.0.27: resolution: { @@ -6772,10 +6823,10 @@ packages: } engines: { node: ">=0.10.0" } - normalize-url@8.0.2: + normalize-url@8.1.1: resolution: { - integrity: sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==, + integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==, } engines: { node: ">=14.16" } @@ -6800,10 +6851,10 @@ packages: } engines: { node: ">=18" } - npm@11.7.0: + npm@11.10.0: resolution: { - integrity: sha512-wiCZpv/41bIobCoJ31NStIWKfAxxYyD1iYnWCtiyns8s5v3+l8y0HCP/sScuH6B5+GhIfda4HQKiqeGZwJWhFw==, + integrity: sha512-i8hE43iSIAMFuYVi8TxsEISdELM4fIza600aLjJ0ankGPLqd0oTPKMJqAcO/QWm307MbSlWGzJcNZ0lGMQgHPA==, } engines: { node: ^20.17.0 || >=22.9.0 } hasBin: true @@ -6923,6 +6974,12 @@ packages: } engines: { node: ">= 0.4" } + ohash@2.0.11: + resolution: + { + integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==, + } + once@1.4.0: resolution: { @@ -6950,6 +7007,13 @@ packages: } engines: { node: ">=18" } + open@10.2.0: + resolution: + { + integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==, + } + engines: { node: ">=18" } + optionator@0.9.4: resolution: { @@ -6992,6 +7056,13 @@ packages: } engines: { node: ">=12" } + p-event@6.0.1: + resolution: + { + integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==, + } + engines: { node: ">=16.17" } + p-filter@4.1.0: resolution: { @@ -7048,13 +7119,6 @@ packages: } engines: { node: ">=10" } - p-map@7.0.3: - resolution: - { - integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==, - } - engines: { node: ">=18" } - p-map@7.0.4: resolution: { @@ -7076,6 +7140,13 @@ packages: } engines: { node: ">=12" } + p-timeout@6.1.4: + resolution: + { + integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==, + } + engines: { node: ">=14.16" } + p-try@1.0.0: resolution: { @@ -7216,13 +7287,6 @@ packages: integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, } - path-scurry@1.11.1: - resolution: - { - integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==, - } - engines: { node: ">=16 || 14 >=14.18" } - path-scurry@2.0.1: resolution: { @@ -7244,10 +7308,16 @@ packages: } engines: { node: ">=18" } - pdfmake@0.2.20: + pathe@2.0.3: resolution: { - integrity: sha512-bGbxbGFP5p8PWNT3Phsu1ZcRLnRfF6jmnuKTkgmt6i5PZzSdX6JaB+NeTz9q+aocfW8SE9GUjL3o/5GroBqGcQ==, + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } + + pdfmake@0.2.23: + resolution: + { + integrity: sha512-A/IksoKb/ikOZH1edSDJ/2zBbqJKDghD4+fXn3rT7quvCJDlsZMs3NmIB3eajLMMFU9Bd3bZPVvlUMXhvFI+bQ==, } engines: { node: ">=18" } @@ -7259,6 +7329,12 @@ packages: engines: { node: ">=0.10" } hasBin: true + perfect-debounce@2.1.0: + resolution: + { + integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==, + } + performance-now@2.1.0: resolution: { @@ -7562,10 +7638,10 @@ packages: peerDependencies: postcss: ^8.0.0 - postcss-js@4.0.1: + postcss-js@4.1.0: resolution: { - integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==, + integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==, } engines: { node: ^12 || ^14 || >= 16 } peerDependencies: @@ -7580,19 +7656,25 @@ packages: peerDependencies: postcss: ^8.4 - postcss-load-config@4.0.2: + postcss-load-config@6.0.1: resolution: { - integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==, + integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==, } - engines: { node: ">= 14" } + engines: { node: ">= 18" } peerDependencies: + jiti: ">=1.21.0" postcss: ">=8.0.9" - ts-node: ">=9.0.0" + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: + jiti: + optional: true postcss: optional: true - ts-node: + tsx: + optional: true + yaml: optional: true postcss-logical@9.0.0: @@ -7658,15 +7740,6 @@ packages: peerDependencies: postcss: ^8.4.32 - postcss-nested@5.0.6: - resolution: - { - integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==, - } - engines: { node: ">=12.0" } - peerDependencies: - postcss: ^8.2.14 - postcss-nested@6.2.0: resolution: { @@ -7895,13 +7968,6 @@ packages: } engines: { node: ">=4" } - postcss-selector-parser@7.1.0: - resolution: - { - integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==, - } - engines: { node: ">=4" } - postcss-selector-parser@7.1.1: resolution: { @@ -7997,10 +8063,10 @@ packages: } engines: { node: ^14.13.1 || >=16.0.0 } - pretty-ms@9.2.0: + pretty-ms@9.3.0: resolution: { - integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==, + integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==, } engines: { node: ">=18" } @@ -8088,10 +8154,10 @@ packages: } engines: { node: ">=20" } - read-pkg@10.0.0: + read-pkg@10.1.0: resolution: { - integrity: sha512-A70UlgfNdKI5NSvTTfHzLQj7NJRpJ4mT5tGafkllJ4wh71oYuGm/pzphHcmW4s35iox56KSK721AihodoXSc/A==, + integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==, } engines: { node: ">=20" } @@ -8129,10 +8195,10 @@ packages: } engines: { node: ">= 0.4" } - regenerate-unicode-properties@10.2.0: + regenerate-unicode-properties@10.2.2: resolution: { - integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==, + integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==, } engines: { node: ">=4" } @@ -8149,17 +8215,17 @@ packages: } engines: { node: ">= 0.4" } - regexpu-core@6.2.0: + regexpu-core@6.4.0: resolution: { - integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==, + integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==, } engines: { node: ">=4" } - registry-auth-token@5.1.0: + registry-auth-token@5.1.1: resolution: { - integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==, + integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==, } engines: { node: ">=14" } @@ -8169,10 +8235,10 @@ packages: integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==, } - regjsparser@0.12.0: + regjsparser@0.13.0: resolution: { - integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==, + integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==, } hasBin: true @@ -8229,20 +8295,20 @@ packages: } engines: { node: ">=8" } - resolve@1.22.10: + resolve@1.22.11: resolution: { - integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==, + integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, } engines: { node: ">= 0.4" } hasBin: true - responselike@3.0.0: + responselike@4.0.2: resolution: { - integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==, + integrity: sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==, } - engines: { node: ">=14.16" } + engines: { node: ">=20" } restore-cursor@3.1.0: resolution: @@ -8286,14 +8352,21 @@ packages: engines: { node: ">=10.0.0" } hasBin: true - rollup@4.47.1: + rollup@4.57.1: resolution: { - integrity: sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==, + integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==, } engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true + run-applescript@7.1.0: + resolution: + { + integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==, + } + engines: { node: ">=18" } + run-async@2.4.1: resolution: { @@ -8359,11 +8432,12 @@ packages: integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, } - sax@1.4.1: + sax@1.4.4: resolution: { - integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==, + integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==, } + engines: { node: ">=11.0.0" } semantic-release@25.0.3: resolution: @@ -8387,14 +8461,6 @@ packages: } hasBin: true - semver@7.7.2: - resolution: - { - integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==, - } - engines: { node: ">=10" } - hasBin: true - semver@7.7.4: resolution: { @@ -8505,6 +8571,13 @@ packages: } engines: { node: ">=6" } + sirv@3.0.2: + resolution: + { + integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==, + } + engines: { node: ">=18" } + skin-tone@2.0.0: resolution: { @@ -8526,18 +8599,19 @@ packages: } engines: { node: ">=10" } - slice-ansi@7.1.0: + slice-ansi@7.1.2: resolution: { - integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==, + integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==, } engines: { node: ">=18" } - smob@1.5.0: + smob@1.6.1: resolution: { - integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==, + integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==, } + engines: { node: ">=20.0.0" } source-map-js@1.2.1: resolution: @@ -8657,13 +8731,6 @@ packages: } engines: { node: ">=8" } - string-width@5.1.2: - resolution: - { - integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, - } - engines: { node: ">=12" } - string-width@7.2.0: resolution: { @@ -8671,17 +8738,10 @@ packages: } engines: { node: ">=18" } - string-width@8.1.0: + string-width@8.2.0: resolution: { - integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==, - } - engines: { node: ">=20" } - - string-width@8.1.1: - resolution: - { - integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==, + integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==, } engines: { node: ">=20" } @@ -8739,10 +8799,10 @@ packages: } engines: { node: ">=8" } - strip-ansi@7.1.0: + strip-ansi@7.1.2: resolution: { - integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==, + integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==, } engines: { node: ">=12" } @@ -8802,16 +8862,16 @@ packages: } engines: { node: ">=8" } - style-mod@4.1.2: + style-mod@4.1.3: resolution: { - integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==, + integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==, } - stylehacks@7.0.6: + stylehacks@7.0.7: resolution: { - integrity: sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==, + integrity: sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==, } engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } peerDependencies: @@ -8843,18 +8903,18 @@ packages: engines: { node: ">=20.19.0" } hasBin: true - sucrase@3.35.0: + sucrase@3.35.1: resolution: { - integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==, + integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==, } engines: { node: ">=16 || 14 >=14.17" } hasBin: true - super-regex@1.0.0: + super-regex@1.1.0: resolution: { - integrity: sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==, + integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==, } engines: { node: ">=18" } @@ -8964,17 +9024,17 @@ packages: } engines: { node: ">=10" } - tempy@3.1.0: + tempy@3.2.0: resolution: { - integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==, + integrity: sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==, } engines: { node: ">=14.16" } - terser@5.43.1: + terser@5.46.0: resolution: { - integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==, + integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==, } engines: { node: ">=10" } hasBin: true @@ -9023,18 +9083,12 @@ packages: integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==, } - tinyexec@1.0.1: + tinyexec@1.0.2: resolution: { - integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==, + integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==, } - - tinyglobby@0.2.14: - resolution: - { - integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==, - } - engines: { node: ">=12.0.0" } + engines: { node: ">=18" } tinyglobby@0.2.15: resolution: @@ -9063,6 +9117,13 @@ packages: } engines: { node: ">=8.0" } + totalist@3.0.1: + resolution: + { + integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==, + } + engines: { node: ">=6" } + tr46@0.0.3: resolution: { @@ -9158,10 +9219,10 @@ packages: } engines: { node: ">=16" } - type-fest@5.3.1: + type-fest@5.4.4: resolution: { - integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==, + integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==, } engines: { node: ">=20" } @@ -9226,23 +9287,23 @@ packages: } engines: { node: ">= 0.4" } - undici-types@7.10.0: + undici-types@7.14.0: resolution: { - integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==, + integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==, } - undici@5.29.0: + undici@6.23.0: resolution: { - integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==, + integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==, } - engines: { node: ">=14.0" } + engines: { node: ">=18.17" } - undici@7.16.0: + undici@7.22.0: resolution: { - integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==, + integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==, } engines: { node: ">=20.18.1" } @@ -9267,10 +9328,10 @@ packages: } engines: { node: ">=4" } - unicode-match-property-value-ecmascript@2.2.0: + unicode-match-property-value-ecmascript@2.2.1: resolution: { - integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==, + integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==, } engines: { node: ">=4" } @@ -9280,10 +9341,10 @@ packages: integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==, } - unicode-property-aliases-ecmascript@2.1.0: + unicode-property-aliases-ecmascript@2.2.0: resolution: { - integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==, + integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==, } engines: { node: ">=4" } @@ -9341,6 +9402,13 @@ packages: } engines: { node: ">= 10.0.0" } + unplugin-utils@0.3.1: + resolution: + { + integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==, + } + engines: { node: ">=20.19.0" } + upath@1.2.0: resolution: { @@ -9348,15 +9416,6 @@ packages: } engines: { node: ">=4" } - update-browserslist-db@1.1.3: - resolution: - { - integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, - } - hasBin: true - peerDependencies: - browserslist: ">= 4.21.0" - update-browserslist-db@1.2.3: resolution: { @@ -9397,6 +9456,22 @@ packages: integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==, } + vite-dev-rpc@1.1.0: + resolution: + { + integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==, + } + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 + + vite-hot-client@2.1.0: + resolution: + { + integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==, + } + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + vite-plugin-codeigniter@2.0.0: resolution: { @@ -9405,6 +9480,19 @@ packages: peerDependencies: vite: ^7.0.0 + vite-plugin-inspect@11.3.3: + resolution: + { + integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==, + } + engines: { node: ">=14" } + peerDependencies: + "@nuxt/kit": "*" + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + "@nuxt/kit": + optional: true + vite-plugin-pwa@1.2.0: resolution: { @@ -9490,6 +9578,12 @@ packages: integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, } + web-worker@1.2.0: + resolution: + { + integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==, + } + webidl-conversions@3.0.1: resolution: { @@ -9541,10 +9635,10 @@ packages: integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==, } - which-typed-array@1.1.19: + which-typed-array@1.1.20: resolution: { - integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==, + integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==, } engines: { node: ">= 0.4" } @@ -9687,17 +9781,10 @@ packages: } engines: { node: ">=10" } - wrap-ansi@8.1.0: + wrap-ansi@9.0.2: resolution: { - integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, - } - engines: { node: ">=12" } - - wrap-ansi@9.0.0: - resolution: - { - integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==, + integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, } engines: { node: ">=18" } @@ -9714,6 +9801,13 @@ packages: } engines: { node: ^20.17.0 || >=22.9.0 } + wsl-utils@0.1.0: + resolution: + { + integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==, + } + engines: { node: ">=18" } + xml-formatter@3.6.7: resolution: { @@ -9728,10 +9822,10 @@ packages: } engines: { node: ">= 16" } - xmldoc@2.0.2: + xmldoc@2.0.3: resolution: { - integrity: sha512-UiRwoSStEXS3R+YE8OqYv3jebza8cBBAI2y8g3B15XFkn3SbEOyyLnmPHjLBPZANrPJKEzxxB7A3XwcLikQVlQ==, + integrity: sha512-6gRk4NY/Jvg67xn7OzJuxLRsGgiXBaPUQplVJ/9l99uIugxh4FTOewYz5ic8WScj7Xx/2WvhENiQKwkK9RpE4w==, } engines: { node: ">=12.0.0" } @@ -9761,10 +9855,10 @@ packages: integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, } - yaml@2.8.1: + yaml@2.8.2: resolution: { - integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==, + integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==, } engines: { node: ">= 14.6" } hasBin: true @@ -9846,21 +9940,21 @@ packages: } snapshots: - "@actions/core@2.0.1": + "@actions/core@3.0.0": dependencies: - "@actions/exec": 2.0.0 - "@actions/http-client": 3.0.0 + "@actions/exec": 3.0.0 + "@actions/http-client": 4.0.0 - "@actions/exec@2.0.0": + "@actions/exec@3.0.0": dependencies: - "@actions/io": 2.0.0 + "@actions/io": 3.0.2 - "@actions/http-client@3.0.0": + "@actions/http-client@4.0.0": dependencies: tunnel: 0.0.6 - undici: 5.29.0 + undici: 6.23.0 - "@actions/io@2.0.0": {} + "@actions/io@3.0.2": {} "@alloc/quick-lru@5.2.0": {} @@ -9868,14 +9962,14 @@ snapshots: "@amcharts/amcharts4@4.10.40": dependencies: - "@babel/runtime": 7.28.3 - core-js: 3.45.1 + "@babel/runtime": 7.28.6 + core-js: 3.48.0 d3-force: 3.0.0 d3-geo: 3.1.1 d3-geo-projection: 4.0.0 d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - pdfmake: 0.2.20 + pdfmake: 0.2.23 polylabel: 1.1.0 raf: 3.4.1 regression: 2.0.1 @@ -9883,705 +9977,730 @@ snapshots: stackblur-canvas: 2.7.0 tslib: 2.8.1 - "@ampproject/remapping@2.3.0": + "@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)": dependencies: - "@jridgewell/gen-mapping": 0.3.13 - "@jridgewell/trace-mapping": 0.3.30 - - "@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)": - dependencies: - ajv: 8.17.1 + ajv: 8.18.0 json-schema: 0.4.0 jsonpointer: 5.0.1 leven: 3.1.0 - "@babel/code-frame@7.27.1": + "@babel/code-frame@7.29.0": dependencies: - "@babel/helper-validator-identifier": 7.27.1 + "@babel/helper-validator-identifier": 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - "@babel/compat-data@7.28.0": {} + "@babel/compat-data@7.29.0": {} - "@babel/core@7.28.3": + "@babel/core@7.29.0": dependencies: - "@ampproject/remapping": 2.3.0 - "@babel/code-frame": 7.27.1 - "@babel/generator": 7.28.3 - "@babel/helper-compilation-targets": 7.27.2 - "@babel/helper-module-transforms": 7.28.3(@babel/core@7.28.3) - "@babel/helpers": 7.28.3 - "@babel/parser": 7.28.3 - "@babel/template": 7.27.2 - "@babel/traverse": 7.28.3 - "@babel/types": 7.28.2 + "@babel/code-frame": 7.29.0 + "@babel/generator": 7.29.1 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helpers": 7.28.6 + "@babel/parser": 7.29.0 + "@babel/template": 7.28.6 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + "@jridgewell/remapping": 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - "@babel/generator@7.28.3": + "@babel/generator@7.29.1": dependencies: - "@babel/parser": 7.28.3 - "@babel/types": 7.28.2 + "@babel/parser": 7.29.0 + "@babel/types": 7.29.0 "@jridgewell/gen-mapping": 0.3.13 - "@jridgewell/trace-mapping": 0.3.30 + "@jridgewell/trace-mapping": 0.3.31 jsesc: 3.1.0 "@babel/helper-annotate-as-pure@7.27.3": dependencies: - "@babel/types": 7.28.2 + "@babel/types": 7.29.0 - "@babel/helper-compilation-targets@7.27.2": + "@babel/helper-compilation-targets@7.28.6": dependencies: - "@babel/compat-data": 7.28.0 + "@babel/compat-data": 7.29.0 "@babel/helper-validator-option": 7.27.1 - browserslist: 4.25.3 + browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 - "@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.3)": + "@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 + "@babel/core": 7.29.0 "@babel/helper-annotate-as-pure": 7.27.3 - "@babel/helper-member-expression-to-functions": 7.27.1 + "@babel/helper-member-expression-to-functions": 7.28.5 "@babel/helper-optimise-call-expression": 7.27.1 - "@babel/helper-replace-supers": 7.27.1(@babel/core@7.28.3) + "@babel/helper-replace-supers": 7.28.6(@babel/core@7.29.0) "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 - "@babel/traverse": 7.28.3 + "@babel/traverse": 7.29.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - "@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.28.3)": + "@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 + "@babel/core": 7.29.0 "@babel/helper-annotate-as-pure": 7.27.3 - regexpu-core: 6.2.0 + regexpu-core: 6.4.0 semver: 6.3.1 - "@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.3)": + "@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-compilation-targets": 7.27.2 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color "@babel/helper-globals@7.28.0": {} - "@babel/helper-member-expression-to-functions@7.27.1": + "@babel/helper-member-expression-to-functions@7.28.5": dependencies: - "@babel/traverse": 7.28.3 - "@babel/types": 7.28.2 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/helper-module-imports@7.27.1": + "@babel/helper-module-imports@7.28.6": dependencies: - "@babel/traverse": 7.28.3 - "@babel/types": 7.28.2 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)": + "@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-module-imports": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 - "@babel/traverse": 7.28.3 + "@babel/core": 7.29.0 + "@babel/helper-module-imports": 7.28.6 + "@babel/helper-validator-identifier": 7.28.5 + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color "@babel/helper-optimise-call-expression@7.27.1": dependencies: - "@babel/types": 7.28.2 + "@babel/types": 7.29.0 - "@babel/helper-plugin-utils@7.27.1": {} + "@babel/helper-plugin-utils@7.28.6": {} - "@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.3)": + "@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 + "@babel/core": 7.29.0 "@babel/helper-annotate-as-pure": 7.27.3 - "@babel/helper-wrap-function": 7.28.3 - "@babel/traverse": 7.28.3 + "@babel/helper-wrap-function": 7.28.6 + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/helper-replace-supers@7.27.1(@babel/core@7.28.3)": + "@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-member-expression-to-functions": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-member-expression-to-functions": 7.28.5 "@babel/helper-optimise-call-expression": 7.27.1 - "@babel/traverse": 7.28.3 + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color "@babel/helper-skip-transparent-expression-wrappers@7.27.1": dependencies: - "@babel/traverse": 7.28.3 - "@babel/types": 7.28.2 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 transitivePeerDependencies: - supports-color "@babel/helper-string-parser@7.27.1": {} - "@babel/helper-validator-identifier@7.27.1": {} + "@babel/helper-validator-identifier@7.28.5": {} "@babel/helper-validator-option@7.27.1": {} - "@babel/helper-wrap-function@7.28.3": + "@babel/helper-wrap-function@7.28.6": dependencies: - "@babel/template": 7.27.2 - "@babel/traverse": 7.28.3 - "@babel/types": 7.28.2 + "@babel/template": 7.28.6 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/helpers@7.28.3": + "@babel/helpers@7.28.6": dependencies: - "@babel/template": 7.27.2 - "@babel/types": 7.28.2 + "@babel/template": 7.28.6 + "@babel/types": 7.29.0 - "@babel/parser@7.28.3": + "@babel/parser@7.29.0": dependencies: - "@babel/types": 7.28.2 + "@babel/types": 7.29.0 - "@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/traverse": 7.28.3 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 - "@babel/plugin-transform-optional-chaining": 7.27.1(@babel/core@7.28.3) + "@babel/plugin-transform-optional-chaining": 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.28.3)": + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/traverse": 7.28.3 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.3)": + "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 + "@babel/core": 7.29.0 - "@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.3)": + "@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-regexp-features-plugin": 7.27.1(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.3)": + "@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/helper-remap-async-to-generator": 7.27.1(@babel/core@7.28.3) - "@babel/traverse": 7.28.3 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-remap-async-to-generator": 7.27.1(@babel/core@7.29.0) + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-module-imports": 7.27.1 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/helper-remap-async-to-generator": 7.27.1(@babel/core@7.28.3) + "@babel/core": 7.29.0 + "@babel/helper-module-imports": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-remap-async-to-generator": 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - "@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-block-scoping@7.28.0(@babel/core@7.28.3)": + "@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-class-features-plugin": 7.28.3(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.3)": + "@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-class-features-plugin": 7.28.3(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-classes@7.28.3(@babel/core@7.28.3)": + "@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 + "@babel/core": 7.29.0 "@babel/helper-annotate-as-pure": 7.27.3 - "@babel/helper-compilation-targets": 7.27.2 + "@babel/helper-compilation-targets": 7.28.6 "@babel/helper-globals": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/helper-replace-supers": 7.27.1(@babel/core@7.28.3) - "@babel/traverse": 7.28.3 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-replace-supers": 7.28.6(@babel/core@7.29.0) + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/template": 7.27.2 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/template": 7.28.6 - "@babel/plugin-transform-destructuring@7.28.0(@babel/core@7.28.3)": + "@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/traverse": 7.28.3 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-regexp-features-plugin": 7.27.1(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-regexp-features-plugin": 7.27.1(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.3)": + "@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/plugin-transform-destructuring": 7.28.0(@babel/core@7.28.3) + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/plugin-transform-destructuring": 7.28.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - "@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-compilation-targets": 7.27.2 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/traverse": 7.28.3 + "@babel/core": 7.29.0 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-module-transforms": 7.28.3(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-module-transforms": 7.28.3(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-module-transforms": 7.28.3(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 - "@babel/traverse": 7.28.3 + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-validator-identifier": 7.28.5 + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-module-transforms": 7.28.3(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-regexp-features-plugin": 7.27.1(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-object-rest-spread@7.28.0(@babel/core@7.28.3)": + "@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-compilation-targets": 7.27.2 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/plugin-transform-destructuring": 7.28.0(@babel/core@7.28.3) - "@babel/plugin-transform-parameters": 7.27.7(@babel/core@7.28.3) - "@babel/traverse": 7.28.3 + "@babel/core": 7.29.0 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/plugin-transform-destructuring": 7.28.5(@babel/core@7.29.0) + "@babel/plugin-transform-parameters": 7.27.7(@babel/core@7.29.0) + "@babel/traverse": 7.29.0 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/helper-replace-supers": 7.27.1(@babel/core@7.28.3) + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-replace-supers": 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - "@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.3)": + "@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-class-features-plugin": 7.28.3(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 + "@babel/core": 7.29.0 "@babel/helper-annotate-as-pure": 7.27.3 - "@babel/helper-create-class-features-plugin": 7.28.3(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-regenerator@7.28.3(@babel/core@7.28.3)": + "@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-regexp-features-plugin": 7.27.1(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 transitivePeerDependencies: - supports-color - "@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-regexp-features-plugin": 7.27.1(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-regexp-features-plugin": 7.27.1(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 - "@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.3)": + "@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-create-regexp-features-plugin": 7.27.1(@babel/core@7.28.3) - "@babel/helper-plugin-utils": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 - "@babel/preset-env@7.28.3(@babel/core@7.28.3)": + "@babel/preset-env@7.29.0(@babel/core@7.29.0)": dependencies: - "@babel/compat-data": 7.28.0 - "@babel/core": 7.28.3 - "@babel/helper-compilation-targets": 7.27.2 - "@babel/helper-plugin-utils": 7.27.1 + "@babel/compat-data": 7.29.0 + "@babel/core": 7.29.0 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 "@babel/helper-validator-option": 7.27.1 - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-bugfix-safari-class-field-initializer-scope": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": 7.28.3(@babel/core@7.28.3) - "@babel/plugin-proposal-private-property-in-object": 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.3) - "@babel/plugin-syntax-import-assertions": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-syntax-import-attributes": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-syntax-unicode-sets-regex": 7.18.6(@babel/core@7.28.3) - "@babel/plugin-transform-arrow-functions": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-async-generator-functions": 7.28.0(@babel/core@7.28.3) - "@babel/plugin-transform-async-to-generator": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-block-scoped-functions": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-block-scoping": 7.28.0(@babel/core@7.28.3) - "@babel/plugin-transform-class-properties": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-class-static-block": 7.28.3(@babel/core@7.28.3) - "@babel/plugin-transform-classes": 7.28.3(@babel/core@7.28.3) - "@babel/plugin-transform-computed-properties": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-destructuring": 7.28.0(@babel/core@7.28.3) - "@babel/plugin-transform-dotall-regex": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-duplicate-keys": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-dynamic-import": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-explicit-resource-management": 7.28.0(@babel/core@7.28.3) - "@babel/plugin-transform-exponentiation-operator": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-export-namespace-from": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-for-of": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-function-name": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-json-strings": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-literals": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-logical-assignment-operators": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-member-expression-literals": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-modules-amd": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-modules-commonjs": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-modules-systemjs": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-modules-umd": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-named-capturing-groups-regex": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-new-target": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-nullish-coalescing-operator": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-numeric-separator": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-object-rest-spread": 7.28.0(@babel/core@7.28.3) - "@babel/plugin-transform-object-super": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-optional-catch-binding": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-optional-chaining": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-parameters": 7.27.7(@babel/core@7.28.3) - "@babel/plugin-transform-private-methods": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-private-property-in-object": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-property-literals": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-regenerator": 7.28.3(@babel/core@7.28.3) - "@babel/plugin-transform-regexp-modifiers": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-reserved-words": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-shorthand-properties": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-spread": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-sticky-regex": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-template-literals": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-typeof-symbol": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-unicode-escapes": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-unicode-property-regex": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-unicode-regex": 7.27.1(@babel/core@7.28.3) - "@babel/plugin-transform-unicode-sets-regex": 7.27.1(@babel/core@7.28.3) - "@babel/preset-modules": 0.1.6-no-external-plugins(@babel/core@7.28.3) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.3) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.3) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.3) - core-js-compat: 3.45.1 + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": 7.28.5(@babel/core@7.29.0) + "@babel/plugin-bugfix-safari-class-field-initializer-scope": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-proposal-private-property-in-object": 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + "@babel/plugin-syntax-import-assertions": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-syntax-import-attributes": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-syntax-unicode-sets-regex": 7.18.6(@babel/core@7.29.0) + "@babel/plugin-transform-arrow-functions": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-async-generator-functions": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-async-to-generator": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-block-scoped-functions": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-block-scoping": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-class-properties": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-class-static-block": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-classes": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-computed-properties": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-destructuring": 7.28.5(@babel/core@7.29.0) + "@babel/plugin-transform-dotall-regex": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-duplicate-keys": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-dynamic-import": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-explicit-resource-management": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-exponentiation-operator": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-export-namespace-from": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-for-of": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-function-name": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-json-strings": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-literals": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-logical-assignment-operators": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-member-expression-literals": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-modules-amd": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-modules-commonjs": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-modules-systemjs": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-modules-umd": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-named-capturing-groups-regex": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-new-target": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-nullish-coalescing-operator": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-numeric-separator": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-object-rest-spread": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-object-super": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-optional-catch-binding": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-optional-chaining": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-parameters": 7.27.7(@babel/core@7.29.0) + "@babel/plugin-transform-private-methods": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-private-property-in-object": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-property-literals": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-regenerator": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-regexp-modifiers": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-reserved-words": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-shorthand-properties": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-spread": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-sticky-regex": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-template-literals": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-typeof-symbol": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-unicode-escapes": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-unicode-property-regex": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-unicode-regex": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-unicode-sets-regex": 7.28.6(@babel/core@7.29.0) + "@babel/preset-modules": 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - "@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.3)": + "@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-plugin-utils": 7.27.1 - "@babel/types": 7.28.2 + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/types": 7.29.0 esutils: 2.0.3 - "@babel/runtime@7.28.3": {} + "@babel/runtime@7.28.6": {} - "@babel/template@7.27.2": + "@babel/template@7.28.6": dependencies: - "@babel/code-frame": 7.27.1 - "@babel/parser": 7.28.3 - "@babel/types": 7.28.2 + "@babel/code-frame": 7.29.0 + "@babel/parser": 7.29.0 + "@babel/types": 7.29.0 - "@babel/traverse@7.28.3": + "@babel/traverse@7.29.0": dependencies: - "@babel/code-frame": 7.27.1 - "@babel/generator": 7.28.3 + "@babel/code-frame": 7.29.0 + "@babel/generator": 7.29.1 "@babel/helper-globals": 7.28.0 - "@babel/parser": 7.28.3 - "@babel/template": 7.27.2 - "@babel/types": 7.28.2 - debug: 4.4.1 + "@babel/parser": 7.29.0 + "@babel/template": 7.28.6 + "@babel/types": 7.29.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color - "@babel/types@7.28.2": + "@babel/types@7.29.0": dependencies: "@babel/helper-string-parser": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 + "@babel/helper-validator-identifier": 7.28.5 "@cacheable/memory@2.0.7": dependencies: "@cacheable/utils": 2.3.4 - "@keyv/bigmap": 1.3.0(keyv@5.6.0) + "@keyv/bigmap": 1.3.1(keyv@5.6.0) hookified: 1.15.1 keyv: 5.6.0 "@cacheable/utils@2.3.4": dependencies: - hashery: 1.3.0 + hashery: 1.5.0 keyv: 5.6.0 - "@codemirror/autocomplete@6.18.6": + "@codemirror/autocomplete@6.20.0": dependencies: "@codemirror/language": 6.12.1 "@codemirror/state": 6.5.4 "@codemirror/view": 6.39.14 - "@lezer/common": 1.2.3 + "@lezer/common": 1.5.1 "@codemirror/commands@6.10.2": dependencies: "@codemirror/language": 6.12.1 "@codemirror/state": 6.5.4 "@codemirror/view": 6.39.14 - "@lezer/common": 1.2.3 + "@lezer/common": 1.5.1 - "@codemirror/lang-xml@6.1.0": + "@codemirror/lang-css@6.3.1": dependencies: - "@codemirror/autocomplete": 6.18.6 + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.12.1 + "@codemirror/state": 6.5.4 + "@lezer/common": 1.5.1 + "@lezer/css": 1.3.1 + + "@codemirror/lang-html@6.4.11": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/lang-css": 6.3.1 + "@codemirror/lang-javascript": 6.2.4 "@codemirror/language": 6.12.1 "@codemirror/state": 6.5.4 "@codemirror/view": 6.39.14 - "@lezer/common": 1.2.3 + "@lezer/common": 1.5.1 + "@lezer/css": 1.3.1 + "@lezer/html": 1.3.13 + + "@codemirror/lang-javascript@6.2.4": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.12.1 + "@codemirror/lint": 6.9.4 + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + "@lezer/common": 1.5.1 + "@lezer/javascript": 1.5.4 + + "@codemirror/lang-xml@6.1.0": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.12.1 + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + "@lezer/common": 1.5.1 "@lezer/xml": 1.0.6 "@codemirror/language@6.12.1": @@ -10589,17 +10708,17 @@ snapshots: "@codemirror/state": 6.5.4 "@codemirror/view": 6.39.14 "@lezer/common": 1.5.1 - "@lezer/highlight": 1.2.1 - "@lezer/lr": 1.4.2 - style-mod: 4.1.2 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 + style-mod: 4.1.3 - "@codemirror/lint@6.8.5": + "@codemirror/lint@6.9.4": dependencies: "@codemirror/state": 6.5.4 "@codemirror/view": 6.39.14 crelt: 1.0.6 - "@codemirror/search@6.5.11": + "@codemirror/search@6.6.0": dependencies: "@codemirror/state": 6.5.4 "@codemirror/view": 6.39.14 @@ -10613,40 +10732,34 @@ snapshots: dependencies: "@codemirror/state": 6.5.4 crelt: 1.0.6 - style-mod: 4.1.2 + style-mod: 4.1.3 w3c-keyname: 2.2.8 "@colors/colors@1.5.0": optional: true - "@commitlint/cli@20.4.1(@types/node@24.3.0)(typescript@5.9.3)": + "@commitlint/cli@20.4.2(@types/node@24.7.0)(typescript@5.9.3)": dependencies: "@commitlint/format": 20.4.0 - "@commitlint/lint": 20.4.1 - "@commitlint/load": 20.4.0(@types/node@24.3.0)(typescript@5.9.3) + "@commitlint/lint": 20.4.2 + "@commitlint/load": 20.4.0(@types/node@24.7.0)(typescript@5.9.3) "@commitlint/read": 20.4.0 "@commitlint/types": 20.4.0 - tinyexec: 1.0.1 + tinyexec: 1.0.2 yargs: 17.7.2 transitivePeerDependencies: - "@types/node" - typescript - "@commitlint/config-conventional@20.4.1": + "@commitlint/config-conventional@20.4.2": dependencies: "@commitlint/types": 20.4.0 conventional-changelog-conventionalcommits: 9.1.0 - "@commitlint/config-validator@19.8.1": - dependencies: - "@commitlint/types": 19.8.1 - ajv: 8.17.1 - optional: true - "@commitlint/config-validator@20.4.0": dependencies: "@commitlint/types": 20.4.0 - ajv: 8.17.1 + ajv: 8.18.0 "@commitlint/ensure@20.4.1": dependencies: @@ -10657,9 +10770,6 @@ snapshots: lodash.startcase: 4.4.0 lodash.upperfirst: 4.3.1 - "@commitlint/execute-rule@19.8.1": - optional: true - "@commitlint/execute-rule@20.0.0": {} "@commitlint/format@20.4.0": @@ -10670,40 +10780,23 @@ snapshots: "@commitlint/is-ignored@20.4.1": dependencies: "@commitlint/types": 20.4.0 - semver: 7.7.2 + semver: 7.7.4 - "@commitlint/lint@20.4.1": + "@commitlint/lint@20.4.2": dependencies: "@commitlint/is-ignored": 20.4.1 "@commitlint/parse": 20.4.1 - "@commitlint/rules": 20.4.1 + "@commitlint/rules": 20.4.2 "@commitlint/types": 20.4.0 - "@commitlint/load@19.8.1(@types/node@24.3.0)(typescript@5.9.3)": - dependencies: - "@commitlint/config-validator": 19.8.1 - "@commitlint/execute-rule": 19.8.1 - "@commitlint/resolve-extends": 19.8.1 - "@commitlint/types": 19.8.1 - chalk: 5.6.0 - cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.1.0(@types/node@24.3.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - lodash.uniq: 4.5.0 - transitivePeerDependencies: - - "@types/node" - - typescript - optional: true - - "@commitlint/load@20.4.0(@types/node@24.3.0)(typescript@5.9.3)": + "@commitlint/load@20.4.0(@types/node@24.7.0)(typescript@5.9.3)": dependencies: "@commitlint/config-validator": 20.4.0 "@commitlint/execute-rule": 20.0.0 "@commitlint/resolve-extends": 20.4.0 "@commitlint/types": 20.4.0 cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.1.0(@types/node@24.3.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@24.7.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) is-plain-obj: 4.1.0 lodash.mergewith: 4.6.2 picocolors: 1.1.1 @@ -10725,28 +10818,18 @@ snapshots: "@commitlint/types": 20.4.0 git-raw-commits: 4.0.0 minimist: 1.2.8 - tinyexec: 1.0.1 - - "@commitlint/resolve-extends@19.8.1": - dependencies: - "@commitlint/config-validator": 19.8.1 - "@commitlint/types": 19.8.1 - global-directory: 4.0.1 - import-meta-resolve: 4.1.0 - lodash.mergewith: 4.6.2 - resolve-from: 5.0.0 - optional: true + tinyexec: 1.0.2 "@commitlint/resolve-extends@20.4.0": dependencies: "@commitlint/config-validator": 20.4.0 "@commitlint/types": 20.4.0 global-directory: 4.0.1 - import-meta-resolve: 4.1.0 + import-meta-resolve: 4.2.0 lodash.mergewith: 4.6.2 resolve-from: 5.0.0 - "@commitlint/rules@20.4.1": + "@commitlint/rules@20.4.2": dependencies: "@commitlint/ensure": 20.4.1 "@commitlint/message": 20.4.0 @@ -10759,12 +10842,6 @@ snapshots: dependencies: escalade: 3.2.0 - "@commitlint/types@19.8.1": - dependencies: - "@types/conventional-commits-parser": 5.0.1 - chalk: 5.6.0 - optional: true - "@commitlint/types@20.4.0": dependencies: conventional-commits-parser: 6.2.1 @@ -10777,11 +10854,6 @@ snapshots: "@csstools/color-helpers@6.0.1": {} - "@csstools/css-calc@3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": - dependencies: - "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) - "@csstools/css-tokenizer": 4.0.0 - "@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": dependencies: "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) @@ -10790,7 +10862,7 @@ snapshots: "@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": dependencies: "@csstools/color-helpers": 6.0.1 - "@csstools/css-calc": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) "@csstools/css-tokenizer": 4.0.0 @@ -10798,7 +10870,7 @@ snapshots: dependencies: "@csstools/css-tokenizer": 4.0.0 - "@csstools/css-syntax-patches-for-csstree@1.0.26": {} + "@csstools/css-syntax-patches-for-csstree@1.0.27": {} "@csstools/css-tokenizer@4.0.0": {} @@ -10877,7 +10949,7 @@ snapshots: "@csstools/postcss-exponential-functions@3.0.0(postcss@8.5.6)": dependencies: - "@csstools/css-calc": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) "@csstools/css-tokenizer": 4.0.0 postcss: 8.5.6 @@ -10963,7 +11035,7 @@ snapshots: "@csstools/postcss-media-minmax@3.0.0(postcss@8.5.6)": dependencies: - "@csstools/css-calc": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) "@csstools/css-tokenizer": 4.0.0 "@csstools/media-query-list-parser": 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) @@ -11019,7 +11091,7 @@ snapshots: "@csstools/postcss-random-function@3.0.0(postcss@8.5.6)": dependencies: - "@csstools/css-calc": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) "@csstools/css-tokenizer": 4.0.0 postcss: 8.5.6 @@ -11040,14 +11112,14 @@ snapshots: "@csstools/postcss-sign-functions@2.0.0(postcss@8.5.6)": dependencies: - "@csstools/css-calc": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) "@csstools/css-tokenizer": 4.0.0 postcss: 8.5.6 "@csstools/postcss-stepped-value-functions@5.0.0(postcss@8.5.6)": dependencies: - "@csstools/css-calc": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) "@csstools/css-tokenizer": 4.0.0 postcss: 8.5.6 @@ -11071,7 +11143,7 @@ snapshots: "@csstools/postcss-trigonometric-functions@5.0.0(postcss@8.5.6)": dependencies: - "@csstools/css-calc": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) "@csstools/css-tokenizer": 4.0.0 postcss: 8.5.6 @@ -11099,92 +11171,87 @@ snapshots: "@epic-web/invariant@1.0.0": {} - "@esbuild/aix-ppc64@0.27.2": + "@esbuild/aix-ppc64@0.27.3": optional: true - "@esbuild/android-arm64@0.27.2": + "@esbuild/android-arm64@0.27.3": optional: true - "@esbuild/android-arm@0.27.2": + "@esbuild/android-arm@0.27.3": optional: true - "@esbuild/android-x64@0.27.2": + "@esbuild/android-x64@0.27.3": optional: true - "@esbuild/darwin-arm64@0.27.2": + "@esbuild/darwin-arm64@0.27.3": optional: true - "@esbuild/darwin-x64@0.27.2": + "@esbuild/darwin-x64@0.27.3": optional: true - "@esbuild/freebsd-arm64@0.27.2": + "@esbuild/freebsd-arm64@0.27.3": optional: true - "@esbuild/freebsd-x64@0.27.2": + "@esbuild/freebsd-x64@0.27.3": optional: true - "@esbuild/linux-arm64@0.27.2": + "@esbuild/linux-arm64@0.27.3": optional: true - "@esbuild/linux-arm@0.27.2": + "@esbuild/linux-arm@0.27.3": optional: true - "@esbuild/linux-ia32@0.27.2": + "@esbuild/linux-ia32@0.27.3": optional: true - "@esbuild/linux-loong64@0.27.2": + "@esbuild/linux-loong64@0.27.3": optional: true - "@esbuild/linux-mips64el@0.27.2": + "@esbuild/linux-mips64el@0.27.3": optional: true - "@esbuild/linux-ppc64@0.27.2": + "@esbuild/linux-ppc64@0.27.3": optional: true - "@esbuild/linux-riscv64@0.27.2": + "@esbuild/linux-riscv64@0.27.3": optional: true - "@esbuild/linux-s390x@0.27.2": + "@esbuild/linux-s390x@0.27.3": optional: true - "@esbuild/linux-x64@0.27.2": + "@esbuild/linux-x64@0.27.3": optional: true - "@esbuild/netbsd-arm64@0.27.2": + "@esbuild/netbsd-arm64@0.27.3": optional: true - "@esbuild/netbsd-x64@0.27.2": + "@esbuild/netbsd-x64@0.27.3": optional: true - "@esbuild/openbsd-arm64@0.27.2": + "@esbuild/openbsd-arm64@0.27.3": optional: true - "@esbuild/openbsd-x64@0.27.2": + "@esbuild/openbsd-x64@0.27.3": optional: true - "@esbuild/openharmony-arm64@0.27.2": + "@esbuild/openharmony-arm64@0.27.3": optional: true - "@esbuild/sunos-x64@0.27.2": + "@esbuild/sunos-x64@0.27.3": optional: true - "@esbuild/win32-arm64@0.27.2": + "@esbuild/win32-arm64@0.27.3": optional: true - "@esbuild/win32-ia32@0.27.2": + "@esbuild/win32-ia32@0.27.3": optional: true - "@esbuild/win32-x64@0.27.2": + "@esbuild/win32-x64@0.27.3": optional: true - "@eslint-community/eslint-utils@4.9.0(eslint@10.0.0(jiti@2.5.1))": + "@eslint-community/eslint-utils@4.9.1(eslint@10.0.0(jiti@1.21.7))": dependencies: - eslint: 10.0.0(jiti@2.5.1) - eslint-visitor-keys: 3.4.3 - - "@eslint-community/eslint-utils@4.9.1(eslint@10.0.0(jiti@2.5.1))": - dependencies: - eslint: 10.0.0(jiti@2.5.1) + eslint: 10.0.0(jiti@1.21.7) eslint-visitor-keys: 3.4.3 "@eslint-community/regexpp@4.12.2": {} @@ -11193,7 +11260,7 @@ snapshots: dependencies: "@eslint/object-schema": 3.0.1 debug: 4.4.3 - minimatch: 10.1.1 + minimatch: 10.2.1 transitivePeerDependencies: - supports-color @@ -11205,9 +11272,23 @@ snapshots: dependencies: "@types/json-schema": 7.0.15 - "@eslint/js@10.0.1(eslint@10.0.0(jiti@2.5.1))": + "@eslint/eslintrc@3.3.3": + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + "@eslint/js@10.0.1(eslint@10.0.0(jiti@1.21.7))": optionalDependencies: - eslint: 10.0.0(jiti@2.5.1) + eslint: 10.0.0(jiti@1.21.7) "@eslint/object-schema@3.0.1": {} @@ -11216,8 +11297,6 @@ snapshots: "@eslint/core": 1.1.0 levn: 0.4.1 - "@fastify/busboy@2.1.1": {} - "@floating-ui/core@1.7.4": dependencies: "@floating-ui/utils": 0.2.10 @@ -11265,15 +11344,13 @@ snapshots: "@humanfs/core@0.19.1": {} - "@humanfs/node@0.16.6": + "@humanfs/node@0.16.7": dependencies: "@humanfs/core": 0.19.1 - "@humanwhocodes/retry": 0.3.1 + "@humanwhocodes/retry": 0.4.3 "@humanwhocodes/module-importer@1.0.1": {} - "@humanwhocodes/retry@0.3.1": {} - "@humanwhocodes/retry@0.4.3": {} "@img/colour@1.0.0": {} @@ -11372,71 +11449,83 @@ snapshots: "@img/sharp-win32-x64@0.34.5": optional: true - "@isaacs/balanced-match@4.0.1": {} - - "@isaacs/brace-expansion@5.0.0": - dependencies: - "@isaacs/balanced-match": 4.0.1 - - "@isaacs/cliui@8.0.2": - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + "@isaacs/cliui@9.0.0": {} "@jridgewell/gen-mapping@0.3.13": dependencies: "@jridgewell/sourcemap-codec": 1.5.5 - "@jridgewell/trace-mapping": 0.3.30 + "@jridgewell/trace-mapping": 0.3.31 + + "@jridgewell/remapping@2.3.5": + dependencies: + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 "@jridgewell/resolve-uri@3.1.2": {} "@jridgewell/source-map@0.3.11": dependencies: "@jridgewell/gen-mapping": 0.3.13 - "@jridgewell/trace-mapping": 0.3.30 + "@jridgewell/trace-mapping": 0.3.31 "@jridgewell/sourcemap-codec@1.5.5": {} - "@jridgewell/trace-mapping@0.3.30": + "@jridgewell/trace-mapping@0.3.31": dependencies: "@jridgewell/resolve-uri": 3.1.2 "@jridgewell/sourcemap-codec": 1.5.5 - "@keyv/bigmap@1.3.0(keyv@5.6.0)": + "@keyv/bigmap@1.3.1(keyv@5.6.0)": dependencies: - hashery: 1.3.0 + hashery: 1.5.0 hookified: 1.15.1 keyv: 5.6.0 "@keyv/serialize@1.1.1": {} - "@lezer/common@1.2.3": {} - "@lezer/common@1.5.1": {} - "@lezer/highlight@1.2.1": + "@lezer/css@1.3.1": dependencies: - "@lezer/common": 1.2.3 + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 - "@lezer/lr@1.4.2": + "@lezer/highlight@1.2.3": dependencies: - "@lezer/common": 1.2.3 + "@lezer/common": 1.5.1 + + "@lezer/html@1.3.13": + dependencies: + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 + + "@lezer/javascript@1.5.4": + dependencies: + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 + + "@lezer/lr@1.4.8": + dependencies: + "@lezer/common": 1.5.1 "@lezer/xml@1.0.6": dependencies: - "@lezer/common": 1.2.3 - "@lezer/highlight": 1.2.1 - "@lezer/lr": 1.4.2 + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 - "@lit-labs/ssr-dom-shim@1.4.0": {} + "@lit-labs/ssr-dom-shim@1.5.1": {} - "@lit/reactive-element@2.1.1": + "@lit/context@1.1.6": dependencies: - "@lit-labs/ssr-dom-shim": 1.4.0 + "@lit/reactive-element": 2.1.2 + + "@lit/reactive-element@2.1.2": + dependencies: + "@lit-labs/ssr-dom-shim": 1.5.1 "@marijn/find-cluster-break@1.0.2": {} @@ -11450,75 +11539,81 @@ snapshots: "@nodelib/fs.walk@1.2.8": dependencies: "@nodelib/fs.scandir": 2.1.5 - fastq: 1.19.1 + fastq: 1.20.1 "@octokit/auth-token@6.0.0": {} - "@octokit/core@7.0.3": + "@octokit/core@7.0.6": dependencies: "@octokit/auth-token": 6.0.0 - "@octokit/graphql": 9.0.1 - "@octokit/request": 10.0.3 - "@octokit/request-error": 7.0.0 - "@octokit/types": 14.1.0 + "@octokit/graphql": 9.0.3 + "@octokit/request": 10.0.7 + "@octokit/request-error": 7.1.0 + "@octokit/types": 16.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 - "@octokit/endpoint@11.0.0": + "@octokit/endpoint@11.0.3": dependencies: - "@octokit/types": 14.1.0 + "@octokit/types": 16.0.0 universal-user-agent: 7.0.3 - "@octokit/graphql@9.0.1": + "@octokit/graphql@9.0.3": dependencies: - "@octokit/request": 10.0.3 - "@octokit/types": 14.1.0 + "@octokit/request": 10.0.7 + "@octokit/types": 16.0.0 universal-user-agent: 7.0.3 - "@octokit/openapi-types@25.1.0": {} - "@octokit/openapi-types@27.0.0": {} - "@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.3)": + "@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)": dependencies: - "@octokit/core": 7.0.3 + "@octokit/core": 7.0.6 "@octokit/types": 16.0.0 - "@octokit/plugin-retry@8.0.1(@octokit/core@7.0.3)": + "@octokit/plugin-retry@8.1.0(@octokit/core@7.0.6)": dependencies: - "@octokit/core": 7.0.3 - "@octokit/request-error": 7.0.0 - "@octokit/types": 14.1.0 + "@octokit/core": 7.0.6 + "@octokit/request-error": 7.1.0 + "@octokit/types": 16.0.0 bottleneck: 2.19.5 - "@octokit/plugin-throttling@11.0.1(@octokit/core@7.0.3)": + "@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.6)": dependencies: - "@octokit/core": 7.0.3 - "@octokit/types": 14.1.0 + "@octokit/core": 7.0.6 + "@octokit/types": 16.0.0 bottleneck: 2.19.5 - "@octokit/request-error@7.0.0": + "@octokit/request-error@7.1.0": dependencies: - "@octokit/types": 14.1.0 + "@octokit/types": 16.0.0 - "@octokit/request@10.0.3": + "@octokit/request@10.0.7": dependencies: - "@octokit/endpoint": 11.0.0 - "@octokit/request-error": 7.0.0 - "@octokit/types": 14.1.0 + "@octokit/endpoint": 11.0.3 + "@octokit/request-error": 7.1.0 + "@octokit/types": 16.0.0 fast-content-type-parse: 3.0.0 universal-user-agent: 7.0.3 - "@octokit/types@14.1.0": - dependencies: - "@octokit/openapi-types": 25.1.0 - "@octokit/types@16.0.0": dependencies: "@octokit/openapi-types": 27.0.0 - "@pkgjs/parseargs@0.11.0": - optional: true + "@patternfly/elements@4.3.1": + dependencies: + "@lit/context": 1.1.6 + "@patternfly/icons": 1.0.3 + "@patternfly/pfe-core": 5.0.6 + lit: 3.3.2 + tslib: 2.8.1 + + "@patternfly/icons@1.0.3": {} + + "@patternfly/pfe-core@5.0.6": + dependencies: + "@lit/context": 1.1.6 + lit: 3.3.2 "@pkgr/core@0.2.9": {} @@ -11528,16 +11623,18 @@ snapshots: dependencies: graceful-fs: 4.2.10 - "@pnpm/npm-conf@2.3.1": + "@pnpm/npm-conf@3.0.2": dependencies: "@pnpm/config.env-replace": 1.1.0 "@pnpm/network.ca-file": 1.0.2 config-chain: 1.1.13 - "@rollup/plugin-babel@5.3.1(@babel/core@7.28.3)(rollup@2.79.2)": + "@polka/url@1.0.0-next.29": {} + + "@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(rollup@2.79.2)": dependencies: - "@babel/core": 7.28.3 - "@babel/helper-module-imports": 7.27.1 + "@babel/core": 7.29.0 + "@babel/helper-module-imports": 7.28.6 "@rollup/pluginutils": 3.1.0(rollup@2.79.2) rollup: 2.79.2 transitivePeerDependencies: @@ -11545,11 +11642,11 @@ snapshots: "@rollup/plugin-node-resolve@15.3.1(rollup@2.79.2)": dependencies: - "@rollup/pluginutils": 5.2.0(rollup@2.79.2) + "@rollup/pluginutils": 5.3.0(rollup@2.79.2) "@types/resolve": 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 - resolve: 1.22.10 + resolve: 1.22.11 optionalDependencies: rollup: 2.79.2 @@ -11562,8 +11659,8 @@ snapshots: "@rollup/plugin-terser@0.4.4(rollup@2.79.2)": dependencies: serialize-javascript: 6.0.2 - smob: 1.5.0 - terser: 5.43.1 + smob: 1.6.1 + terser: 5.46.0 optionalDependencies: rollup: 2.79.2 @@ -11574,7 +11671,7 @@ snapshots: picomatch: 2.3.1 rollup: 2.79.2 - "@rollup/pluginutils@5.2.0(rollup@2.79.2)": + "@rollup/pluginutils@5.3.0(rollup@2.79.2)": dependencies: "@types/estree": 1.0.8 estree-walker: 2.0.2 @@ -11582,64 +11679,79 @@ snapshots: optionalDependencies: rollup: 2.79.2 - "@rollup/rollup-android-arm-eabi@4.47.1": + "@rollup/rollup-android-arm-eabi@4.57.1": optional: true - "@rollup/rollup-android-arm64@4.47.1": + "@rollup/rollup-android-arm64@4.57.1": optional: true - "@rollup/rollup-darwin-arm64@4.47.1": + "@rollup/rollup-darwin-arm64@4.57.1": optional: true - "@rollup/rollup-darwin-x64@4.47.1": + "@rollup/rollup-darwin-x64@4.57.1": optional: true - "@rollup/rollup-freebsd-arm64@4.47.1": + "@rollup/rollup-freebsd-arm64@4.57.1": optional: true - "@rollup/rollup-freebsd-x64@4.47.1": + "@rollup/rollup-freebsd-x64@4.57.1": optional: true - "@rollup/rollup-linux-arm-gnueabihf@4.47.1": + "@rollup/rollup-linux-arm-gnueabihf@4.57.1": optional: true - "@rollup/rollup-linux-arm-musleabihf@4.47.1": + "@rollup/rollup-linux-arm-musleabihf@4.57.1": optional: true - "@rollup/rollup-linux-arm64-gnu@4.47.1": + "@rollup/rollup-linux-arm64-gnu@4.57.1": optional: true - "@rollup/rollup-linux-arm64-musl@4.47.1": + "@rollup/rollup-linux-arm64-musl@4.57.1": optional: true - "@rollup/rollup-linux-loongarch64-gnu@4.47.1": + "@rollup/rollup-linux-loong64-gnu@4.57.1": optional: true - "@rollup/rollup-linux-ppc64-gnu@4.47.1": + "@rollup/rollup-linux-loong64-musl@4.57.1": optional: true - "@rollup/rollup-linux-riscv64-gnu@4.47.1": + "@rollup/rollup-linux-ppc64-gnu@4.57.1": optional: true - "@rollup/rollup-linux-riscv64-musl@4.47.1": + "@rollup/rollup-linux-ppc64-musl@4.57.1": optional: true - "@rollup/rollup-linux-s390x-gnu@4.47.1": + "@rollup/rollup-linux-riscv64-gnu@4.57.1": optional: true - "@rollup/rollup-linux-x64-gnu@4.47.1": + "@rollup/rollup-linux-riscv64-musl@4.57.1": optional: true - "@rollup/rollup-linux-x64-musl@4.47.1": + "@rollup/rollup-linux-s390x-gnu@4.57.1": optional: true - "@rollup/rollup-win32-arm64-msvc@4.47.1": + "@rollup/rollup-linux-x64-gnu@4.57.1": optional: true - "@rollup/rollup-win32-ia32-msvc@4.47.1": + "@rollup/rollup-linux-x64-musl@4.57.1": optional: true - "@rollup/rollup-win32-x64-msvc@4.47.1": + "@rollup/rollup-openbsd-x64@4.57.1": + optional: true + + "@rollup/rollup-openharmony-arm64@4.57.1": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.57.1": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.57.1": + optional: true + + "@rollup/rollup-win32-x64-gnu@4.57.1": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.57.1": optional: true "@sec-ant/readable-stream@0.4.1": {} @@ -11648,19 +11760,19 @@ snapshots: dependencies: "@semantic-release/error": 3.0.0 aggregate-error: 3.1.0 - fs-extra: 11.3.1 - lodash: 4.17.21 + fs-extra: 11.3.3 + lodash: 4.17.23 semantic-release: 25.0.3(typescript@5.9.3) "@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.3(typescript@5.9.3))": dependencies: - conventional-changelog-angular: 8.0.0 + conventional-changelog-angular: 8.1.0 conventional-changelog-writer: 8.2.0 conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.0 + conventional-commits-parser: 6.2.1 debug: 4.4.3 import-from-esm: 2.0.0 - lodash-es: 4.17.21 + lodash-es: 4.17.23 micromatch: 4.0.8 semantic-release: 25.0.3(typescript@5.9.3) transitivePeerDependencies: @@ -11674,9 +11786,9 @@ snapshots: dependencies: "@semantic-release/error": 4.0.0 aggregate-error: 3.1.0 - debug: 4.4.1 - execa: 9.6.0 - lodash-es: 4.17.21 + debug: 4.4.3 + execa: 9.6.1 + lodash-es: 4.17.23 parse-json: 8.3.0 semantic-release: 25.0.3(typescript@5.9.3) transitivePeerDependencies: @@ -11686,22 +11798,22 @@ snapshots: dependencies: "@semantic-release/error": 3.0.0 aggregate-error: 3.1.0 - debug: 4.4.1 + debug: 4.4.3 dir-glob: 3.0.1 execa: 5.1.1 - lodash: 4.17.21 + lodash: 4.17.23 micromatch: 4.0.8 p-reduce: 2.1.0 semantic-release: 25.0.3(typescript@5.9.3) transitivePeerDependencies: - supports-color - "@semantic-release/github@12.0.2(semantic-release@25.0.3(typescript@5.9.3))": + "@semantic-release/github@12.0.6(semantic-release@25.0.3(typescript@5.9.3))": dependencies: - "@octokit/core": 7.0.3 - "@octokit/plugin-paginate-rest": 14.0.0(@octokit/core@7.0.3) - "@octokit/plugin-retry": 8.0.1(@octokit/core@7.0.3) - "@octokit/plugin-throttling": 11.0.1(@octokit/core@7.0.3) + "@octokit/core": 7.0.6 + "@octokit/plugin-paginate-rest": 14.0.0(@octokit/core@7.0.6) + "@octokit/plugin-retry": 8.1.0(@octokit/core@7.0.6) + "@octokit/plugin-throttling": 11.0.3(@octokit/core@7.0.6) "@semantic-release/error": 4.0.0 aggregate-error: 5.0.0 debug: 4.4.3 @@ -11709,12 +11821,12 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 issue-parser: 7.0.1 - lodash-es: 4.17.21 - mime: 4.0.7 + lodash-es: 4.17.23 + mime: 4.1.0 p-filter: 4.1.0 semantic-release: 25.0.3(typescript@5.9.3) tinyglobby: 0.2.15 - undici: 7.16.0 + undici: 7.22.0 url-join: 5.0.0 transitivePeerDependencies: - supports-color @@ -11727,47 +11839,47 @@ snapshots: dir-glob: 3.0.1 escape-string-regexp: 5.0.0 formdata-node: 6.0.3 - fs-extra: 11.3.1 + fs-extra: 11.3.3 globby: 14.1.0 - got: 14.4.7 + got: 14.6.6 hpagent: 1.2.0 - lodash-es: 4.17.21 + lodash-es: 4.17.23 parse-url: 10.0.3 semantic-release: 25.0.3(typescript@5.9.3) url-join: 4.0.1 transitivePeerDependencies: - supports-color - "@semantic-release/npm@13.1.3(semantic-release@25.0.3(typescript@5.9.3))": + "@semantic-release/npm@13.1.4(semantic-release@25.0.3(typescript@5.9.3))": dependencies: - "@actions/core": 2.0.1 + "@actions/core": 3.0.0 "@semantic-release/error": 4.0.0 aggregate-error: 5.0.0 env-ci: 11.2.0 - execa: 9.6.0 - fs-extra: 11.3.1 - lodash-es: 4.17.21 + execa: 9.6.1 + fs-extra: 11.3.3 + lodash-es: 4.17.23 nerf-dart: 1.0.0 - normalize-url: 8.0.2 - npm: 11.7.0 + normalize-url: 8.1.1 + npm: 11.10.0 rc: 1.2.8 - read-pkg: 10.0.0 - registry-auth-token: 5.1.0 + read-pkg: 10.1.0 + registry-auth-token: 5.1.1 semantic-release: 25.0.3(typescript@5.9.3) - semver: 7.7.2 - tempy: 3.1.0 + semver: 7.7.4 + tempy: 3.2.0 "@semantic-release/release-notes-generator@14.1.0(semantic-release@25.0.3(typescript@5.9.3))": dependencies: - conventional-changelog-angular: 8.0.0 + conventional-changelog-angular: 8.1.0 conventional-changelog-writer: 8.2.0 conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.0 + conventional-commits-parser: 6.2.1 debug: 4.4.3 get-stream: 7.0.1 import-from-esm: 2.0.0 into-stream: 7.0.0 - lodash-es: 4.17.21 + lodash-es: 4.17.23 read-package-up: 11.0.0 semantic-release: 25.0.3(typescript@5.9.3) transitivePeerDependencies: @@ -11775,7 +11887,7 @@ snapshots: "@sindresorhus/is@4.6.0": {} - "@sindresorhus/is@7.0.2": {} + "@sindresorhus/is@7.2.0": {} "@sindresorhus/merge-streams@2.3.0": {} @@ -11790,28 +11902,20 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 - "@szmarczak/http-timer@5.0.1": - dependencies: - defer-to-connect: 2.0.1 - - "@tailwindcss/forms@0.5.11(tailwindcss@3.4.19)": + "@tailwindcss/forms@0.5.11(tailwindcss@3.4.19(yaml@2.8.2))": dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.19 + tailwindcss: 3.4.19(yaml@2.8.2) - "@tailwindcss/nesting@0.0.0-insiders.565cd3e(postcss@8.5.6)": - dependencies: - postcss: 8.5.6 - postcss-nested: 5.0.6(postcss@8.5.6) - - "@tailwindcss/typography@0.5.19(tailwindcss@3.4.19)": + "@tailwindcss/typography@0.5.19(tailwindcss@3.4.19(yaml@2.8.2))": dependencies: postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.19 + tailwindcss: 3.4.19(yaml@2.8.2) - "@types/conventional-commits-parser@5.0.1": + "@types/eslint@9.6.1": dependencies: - "@types/node": 24.3.0 + "@types/estree": 1.0.8 + "@types/json-schema": 7.0.15 optional: true "@types/esrecurse@4.3.1": {} @@ -11824,7 +11928,7 @@ snapshots: "@types/geojson@7946.0.16": {} - "@types/http-cache-semantics@4.0.4": {} + "@types/http-cache-semantics@4.2.0": {} "@types/json-schema@7.0.15": {} @@ -11832,9 +11936,9 @@ snapshots: dependencies: "@types/geojson": 7946.0.16 - "@types/node@24.3.0": + "@types/node@24.7.0": dependencies: - undici-types: 7.10.0 + undici-types: 7.14.0 "@types/normalize-package-data@2.4.4": {} @@ -11842,15 +11946,15 @@ snapshots: "@types/trusted-types@2.0.7": {} - "@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3)": + "@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3)": dependencies: "@eslint-community/regexpp": 4.12.2 - "@typescript-eslint/parser": 8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) + "@typescript-eslint/parser": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) "@typescript-eslint/scope-manager": 8.56.0 - "@typescript-eslint/type-utils": 8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) - "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) + "@typescript-eslint/type-utils": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) "@typescript-eslint/visitor-keys": 8.56.0 - eslint: 10.0.0(jiti@2.5.1) + eslint: 10.0.0(jiti@1.21.7) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -11858,14 +11962,14 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3)": + "@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3)": dependencies: "@typescript-eslint/scope-manager": 8.56.0 "@typescript-eslint/types": 8.56.0 "@typescript-eslint/typescript-estree": 8.56.0(typescript@5.9.3) "@typescript-eslint/visitor-keys": 8.56.0 debug: 4.4.3 - eslint: 10.0.0(jiti@2.5.1) + eslint: 10.0.0(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -11888,13 +11992,13 @@ snapshots: dependencies: typescript: 5.9.3 - "@typescript-eslint/type-utils@8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3)": + "@typescript-eslint/type-utils@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3)": dependencies: "@typescript-eslint/types": 8.56.0 "@typescript-eslint/typescript-estree": 8.56.0(typescript@5.9.3) - "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) + "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) debug: 4.4.3 - eslint: 10.0.0(jiti@2.5.1) + eslint: 10.0.0(jiti@1.21.7) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -11917,13 +12021,13 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/utils@8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3)": + "@typescript-eslint/utils@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3)": dependencies: - "@eslint-community/eslint-utils": 4.9.1(eslint@10.0.0(jiti@2.5.1)) + "@eslint-community/eslint-utils": 4.9.1(eslint@10.0.0(jiti@1.21.7)) "@typescript-eslint/scope-manager": 8.56.0 "@typescript-eslint/types": 8.56.0 "@typescript-eslint/typescript-estree": 8.56.0(typescript@5.9.3) - eslint: 10.0.0(jiti@2.5.1) + eslint: 10.0.0(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -11956,7 +12060,7 @@ snapshots: aggregate-error@5.0.0: dependencies: - clean-stack: 5.2.0 + clean-stack: 5.3.0 indent-string: 5.0.0 ajv@6.12.6: @@ -11966,22 +12070,22 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 all-contributors-cli@6.26.1: dependencies: - "@babel/runtime": 7.28.3 + "@babel/runtime": 7.28.6 async: 3.2.6 chalk: 4.1.2 didyoumean: 1.2.2 inquirer: 7.3.3 json-fixer: 1.6.15 - lodash: 4.17.21 + lodash: 4.17.23 node-fetch: 2.7.0 pify: 5.0.0 yargs: 15.4.1 @@ -11994,13 +12098,13 @@ snapshots: dependencies: type-fest: 0.21.3 - ansi-escapes@7.0.0: + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 ansi-regex@5.0.1: {} - ansi-regex@6.2.0: {} + ansi-regex@6.2.2: {} ansi-styles@3.2.1: dependencies: @@ -12010,7 +12114,9 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} + + ansis@4.2.0: {} any-promise@1.3.0: {} @@ -12037,7 +12143,7 @@ snapshots: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -12050,10 +12156,10 @@ snapshots: at-least-node@1.0.0: {} - autoprefixer@10.4.23(postcss@8.5.6): + autoprefixer@10.4.24(postcss@8.5.6): dependencies: browserslist: 4.28.1 - caniuse-lite: 1.0.30001761 + caniuse-lite: 1.0.30001770 fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.6 @@ -12063,27 +12169,27 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.3): + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.29.0): dependencies: - "@babel/compat-data": 7.28.0 - "@babel/core": 7.28.3 - "@babel/helper-define-polyfill-provider": 0.6.5(@babel/core@7.28.3) + "@babel/compat-data": 7.29.0 + "@babel/core": 7.29.0 + "@babel/helper-define-polyfill-provider": 0.6.6(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.3): + babel-plugin-polyfill-corejs3@0.14.0(@babel/core@7.29.0): dependencies: - "@babel/core": 7.28.3 - "@babel/helper-define-polyfill-provider": 0.6.5(@babel/core@7.28.3) - core-js-compat: 3.45.1 + "@babel/core": 7.29.0 + "@babel/helper-define-polyfill-provider": 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.3): + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.29.0): dependencies: - "@babel/core": 7.28.3 - "@babel/helper-define-polyfill-provider": 0.6.5(@babel/core@7.28.3) + "@babel/core": 7.29.0 + "@babel/helper-define-polyfill-provider": 0.6.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -12091,16 +12197,20 @@ snapshots: balanced-match@3.0.1: {} + balanced-match@4.0.3: {} + base64-js@1.3.1: {} base64-js@1.5.1: {} - baseline-browser-mapping@2.9.11: {} + baseline-browser-mapping@2.10.0: {} before-after-hook@4.0.0: {} binary-extensions@2.3.0: {} + birpc@2.9.0: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -12120,6 +12230,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.2: + dependencies: + balanced-match: 4.0.3 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -12128,18 +12242,11 @@ snapshots: dependencies: base64-js: 1.5.1 - browserslist@4.25.3: - dependencies: - caniuse-lite: 1.0.30001737 - electron-to-chromium: 1.5.208 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.3) - browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.11 - caniuse-lite: 1.0.30001761 - electron-to-chromium: 1.5.267 + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001770 + electron-to-chromium: 1.5.286 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -12150,17 +12257,23 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + byte-counter@0.1.0: {} + cacheable-lookup@7.0.0: {} - cacheable-request@12.0.1: + cacheable-request@13.0.18: dependencies: - "@types/http-cache-semantics": 4.0.4 + "@types/http-cache-semantics": 4.2.0 get-stream: 9.0.1 http-cache-semantics: 4.2.0 - keyv: 4.5.4 + keyv: 5.6.0 mimic-response: 4.0.0 - normalize-url: 8.0.2 - responselike: 3.0.0 + normalize-url: 8.1.1 + responselike: 4.0.2 cacheable@2.3.2: dependencies: @@ -12198,13 +12311,11 @@ snapshots: caniuse-api@3.0.0: dependencies: browserslist: 4.28.1 - caniuse-lite: 1.0.30001737 + caniuse-lite: 1.0.30001770 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001737: {} - - caniuse-lite@1.0.30001761: {} + caniuse-lite@1.0.30001770: {} chalk@2.4.2: dependencies: @@ -12217,7 +12328,7 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.0: {} + chalk@5.6.2: {} char-regex@1.0.2: {} @@ -12239,11 +12350,11 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - ci-info@4.3.0: {} + ci-info@4.4.0: {} clean-stack@2.2.0: {} - clean-stack@5.2.0: + clean-stack@5.3.0: dependencies: escape-string-regexp: 5.0.0 @@ -12274,8 +12385,8 @@ snapshots: cli-truncate@5.1.1: dependencies: - slice-ansi: 7.1.0 - string-width: 8.1.0 + slice-ansi: 7.1.2 + string-width: 8.2.0 cli-width@3.0.0: {} @@ -12300,18 +12411,18 @@ snapshots: cliui@9.0.1: dependencies: string-width: 7.2.0 - strip-ansi: 7.1.0 - wrap-ansi: 9.0.0 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 clone@1.0.4: {} codemirror@6.0.2: dependencies: - "@codemirror/autocomplete": 6.18.6 + "@codemirror/autocomplete": 6.20.0 "@codemirror/commands": 6.10.2 "@codemirror/language": 6.12.1 - "@codemirror/lint": 6.8.5 - "@codemirror/search": 6.5.11 + "@codemirror/lint": 6.9.4 + "@codemirror/search": 6.6.0 "@codemirror/state": 6.5.4 "@codemirror/view": 6.39.14 @@ -12333,7 +12444,7 @@ snapshots: commander@11.1.0: {} - commander@14.0.2: {} + commander@14.0.3: {} commander@2.20.3: {} @@ -12341,10 +12452,10 @@ snapshots: commander@7.2.0: {} - commitizen@4.3.1(@types/node@24.3.0)(typescript@5.9.3): + commitizen@4.3.1(@types/node@24.7.0)(typescript@5.9.3): dependencies: cachedir: 2.3.0 - cz-conventional-changelog: 3.3.0(@types/node@24.3.0)(typescript@5.9.3) + cz-conventional-changelog: 3.3.0(@types/node@24.7.0)(typescript@5.9.3) dedent: 0.7.0 detect-indent: 6.1.0 find-node-modules: 2.1.3 @@ -12375,10 +12486,6 @@ snapshots: ini: 1.3.8 proto-list: 1.2.4 - conventional-changelog-angular@8.0.0: - dependencies: - compare-func: 2.0.0 - conventional-changelog-angular@8.1.0: dependencies: compare-func: 2.0.0 @@ -12392,16 +12499,12 @@ snapshots: conventional-commits-filter: 5.0.0 handlebars: 4.7.8 meow: 13.2.0 - semver: 7.7.2 + semver: 7.7.4 conventional-commit-types@3.0.0: {} conventional-commits-filter@5.0.0: {} - conventional-commits-parser@6.2.0: - dependencies: - meow: 13.2.0 - conventional-commits-parser@6.2.1: dependencies: meow: 13.2.0 @@ -12410,26 +12513,26 @@ snapshots: convert-source-map@2.0.0: {} - core-js-compat@3.45.1: + core-js-compat@3.48.0: dependencies: - browserslist: 4.25.3 + browserslist: 4.28.1 - core-js@3.45.1: {} + core-js@3.48.0: {} core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@6.1.0(@types/node@24.3.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@6.2.0(@types/node@24.7.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: - "@types/node": 24.3.0 + "@types/node": 24.7.0 cosmiconfig: 9.0.0(typescript@5.9.3) - jiti: 2.5.1 + jiti: 2.6.1 typescript: 5.9.3 cosmiconfig@9.0.0(typescript@5.9.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -12460,11 +12563,11 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 7.1.1 - css-declaration-sorter@7.2.0(postcss@8.5.6): + css-declaration-sorter@7.3.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - css-functions-list@3.2.3: {} + css-functions-list@3.3.3: {} css-has-pseudo@8.0.0(postcss@8.5.6): dependencies: @@ -12504,7 +12607,7 @@ snapshots: cssnano-preset-default@7.0.10(postcss@8.5.6): dependencies: browserslist: 4.28.1 - css-declaration-sorter: 7.2.0(postcss@8.5.6) + css-declaration-sorter: 7.3.1(postcss@8.5.6) cssnano-utils: 5.0.1(postcss@8.5.6) postcss: 8.5.6 postcss-calc: 10.1.1(postcss@8.5.6) @@ -12549,16 +12652,16 @@ snapshots: dependencies: css-tree: 2.2.1 - cz-conventional-changelog@3.3.0(@types/node@24.3.0)(typescript@5.9.3): + cz-conventional-changelog@3.3.0(@types/node@24.7.0)(typescript@5.9.3): dependencies: chalk: 2.4.2 - commitizen: 4.3.1(@types/node@24.3.0)(typescript@5.9.3) + commitizen: 4.3.1(@types/node@24.7.0)(typescript@5.9.3) conventional-commit-types: 3.0.0 lodash.map: 4.6.0 longest: 2.0.1 word-wrap: 1.2.5 optionalDependencies: - "@commitlint/load": 19.8.1(@types/node@24.3.0)(typescript@5.9.3) + "@commitlint/load": 20.4.0(@types/node@24.7.0)(typescript@5.9.3) transitivePeerDependencies: - "@types/node" - typescript @@ -12628,19 +12731,15 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 decamelize@1.2.0: {} - decompress-response@6.0.0: + decompress-response@10.0.0: dependencies: - mimic-response: 3.1.0 + mimic-response: 4.0.0 dedent@0.7.0: {} @@ -12659,18 +12758,25 @@ snapshots: deepmerge@4.3.1: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + defaults@1.0.4: dependencies: clone: 1.0.4 - defer-to-connect@2.0.1: {} - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -12725,22 +12831,16 @@ snapshots: dependencies: readable-stream: 2.3.8 - eastasianwidth@0.2.0: {} - ejs@3.1.10: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.208: {} + electron-to-chromium@1.5.286: {} - electron-to-chromium@1.5.267: {} - - emoji-regex@10.4.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - emojilib@2.4.0: {} entities@4.5.0: {} @@ -12754,11 +12854,13 @@ snapshots: environment@1.1.0: {} - error-ex@1.3.2: + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 - es-abstract@1.24.0: + error-stack-parser-es@1.0.5: {} + + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 @@ -12813,7 +12915,7 @@ snapshots: typed-array-byte-offset: 1.0.4 typed-array-length: 1.0.7 unbox-primitive: 1.1.0 - which-typed-array: 1.1.19 + which-typed-array: 1.1.20 es-define-property@1.0.1: {} @@ -12836,34 +12938,34 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.27.2: + esbuild@0.27.3: optionalDependencies: - "@esbuild/aix-ppc64": 0.27.2 - "@esbuild/android-arm": 0.27.2 - "@esbuild/android-arm64": 0.27.2 - "@esbuild/android-x64": 0.27.2 - "@esbuild/darwin-arm64": 0.27.2 - "@esbuild/darwin-x64": 0.27.2 - "@esbuild/freebsd-arm64": 0.27.2 - "@esbuild/freebsd-x64": 0.27.2 - "@esbuild/linux-arm": 0.27.2 - "@esbuild/linux-arm64": 0.27.2 - "@esbuild/linux-ia32": 0.27.2 - "@esbuild/linux-loong64": 0.27.2 - "@esbuild/linux-mips64el": 0.27.2 - "@esbuild/linux-ppc64": 0.27.2 - "@esbuild/linux-riscv64": 0.27.2 - "@esbuild/linux-s390x": 0.27.2 - "@esbuild/linux-x64": 0.27.2 - "@esbuild/netbsd-arm64": 0.27.2 - "@esbuild/netbsd-x64": 0.27.2 - "@esbuild/openbsd-arm64": 0.27.2 - "@esbuild/openbsd-x64": 0.27.2 - "@esbuild/openharmony-arm64": 0.27.2 - "@esbuild/sunos-x64": 0.27.2 - "@esbuild/win32-arm64": 0.27.2 - "@esbuild/win32-ia32": 0.27.2 - "@esbuild/win32-x64": 0.27.2 + "@esbuild/aix-ppc64": 0.27.3 + "@esbuild/android-arm": 0.27.3 + "@esbuild/android-arm64": 0.27.3 + "@esbuild/android-x64": 0.27.3 + "@esbuild/darwin-arm64": 0.27.3 + "@esbuild/darwin-x64": 0.27.3 + "@esbuild/freebsd-arm64": 0.27.3 + "@esbuild/freebsd-x64": 0.27.3 + "@esbuild/linux-arm": 0.27.3 + "@esbuild/linux-arm64": 0.27.3 + "@esbuild/linux-ia32": 0.27.3 + "@esbuild/linux-loong64": 0.27.3 + "@esbuild/linux-mips64el": 0.27.3 + "@esbuild/linux-ppc64": 0.27.3 + "@esbuild/linux-riscv64": 0.27.3 + "@esbuild/linux-s390x": 0.27.3 + "@esbuild/linux-x64": 0.27.3 + "@esbuild/netbsd-arm64": 0.27.3 + "@esbuild/netbsd-x64": 0.27.3 + "@esbuild/openbsd-arm64": 0.27.3 + "@esbuild/openbsd-x64": 0.27.3 + "@esbuild/openharmony-arm64": 0.27.3 + "@esbuild/sunos-x64": 0.27.3 + "@esbuild/win32-arm64": 0.27.3 + "@esbuild/win32-ia32": 0.27.3 + "@esbuild/win32-x64": 0.27.3 escalade@3.2.0: {} @@ -12873,18 +12975,19 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@2.5.1)): + eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@1.21.7)): dependencies: - eslint: 10.0.0(jiti@2.5.1) + eslint: 10.0.0(jiti@1.21.7) - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@2.5.1)))(eslint@10.0.0(jiti@2.5.1))(prettier@3.8.1): + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@1.21.7)))(eslint@10.0.0(jiti@1.21.7))(prettier@3.8.1): dependencies: - eslint: 10.0.0(jiti@2.5.1) + eslint: 10.0.0(jiti@1.21.7) prettier: 3.8.1 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@10.0.0(jiti@2.5.1)) + "@types/eslint": 9.6.1 + eslint-config-prettier: 10.1.8(eslint@10.0.0(jiti@1.21.7)) eslint-scope@9.1.0: dependencies: @@ -12895,17 +12998,19 @@ snapshots: eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.0: {} - eslint@10.0.0(jiti@2.5.1): + eslint@10.0.0(jiti@1.21.7): dependencies: - "@eslint-community/eslint-utils": 4.9.0(eslint@10.0.0(jiti@2.5.1)) + "@eslint-community/eslint-utils": 4.9.1(eslint@10.0.0(jiti@1.21.7)) "@eslint-community/regexpp": 4.12.2 "@eslint/config-array": 0.23.1 "@eslint/config-helpers": 0.5.2 "@eslint/core": 1.1.0 "@eslint/plugin-kit": 0.6.0 - "@humanfs/node": 0.16.6 + "@humanfs/node": 0.16.7 "@humanwhocodes/module-importer": 1.0.1 "@humanwhocodes/retry": 0.4.3 "@types/estree": 1.0.8 @@ -12926,14 +13031,20 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.1.1 + minimatch: 10.2.1 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.5.1 + jiti: 1.21.7 transitivePeerDependencies: - supports-color + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + espree@11.1.0: dependencies: acorn: 8.15.0 @@ -12956,7 +13067,7 @@ snapshots: esutils@2.0.3: {} - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} execa@5.1.1: dependencies: @@ -12982,7 +13093,7 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - execa@9.6.0: + execa@9.6.1: dependencies: "@sindresorhus/merge-streams": 4.0.0 cross-spawn: 7.0.6 @@ -12992,7 +13103,7 @@ snapshots: is-plain-obj: 4.1.0 is-stream: 4.0.1 npm-run-path: 6.0.0 - pretty-ms: 9.2.0 + pretty-ms: 9.3.0 signal-exit: 4.1.0 strip-final-newline: 4.0.0 yoctocolors: 2.1.2 @@ -13025,11 +13136,11 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.6: {} + fast-uri@3.1.0: {} fastest-levenshtein@1.0.16: {} - fastq@1.19.1: + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -13091,7 +13202,7 @@ snapshots: find-versions@6.0.0: dependencies: semver-regex: 4.0.5 - super-regex: 1.0.0 + super-regex: 1.1.0 findup-sync@4.0.0: dependencies: @@ -13135,7 +13246,7 @@ snapshots: inherits: 2.0.4 readable-stream: 2.3.8 - fs-extra@11.3.1: + fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.0 @@ -13172,11 +13283,13 @@ snapshots: fuse.js@7.1.0: {} + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} - get-east-asian-width@1.3.0: {} + get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: dependencies: @@ -13238,24 +13351,21 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - glob@11.1.0: dependencies: foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.1.1 - minipass: 7.1.2 + jackspeak: 4.2.3 + minimatch: 10.2.1 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 2.0.1 + glob@13.0.5: + dependencies: + minimatch: 10.2.1 + minipass: 7.1.3 + path-scurry: 2.0.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -13293,6 +13403,8 @@ snapshots: kind-of: 6.0.3 which: 1.3.1 + globals@14.0.0: {} + globals@17.3.0: {} globalthis@1.0.4: @@ -13309,7 +13421,7 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.3.0 - globby@16.1.0: + globby@16.1.1: dependencies: "@sindresorhus/merge-streams": 4.0.0 fast-glob: 3.3.3 @@ -13322,18 +13434,19 @@ snapshots: gopd@1.2.0: {} - got@14.4.7: + got@14.6.6: dependencies: - "@sindresorhus/is": 7.0.2 - "@szmarczak/http-timer": 5.0.1 + "@sindresorhus/is": 7.2.0 + byte-counter: 0.1.0 cacheable-lookup: 7.0.0 - cacheable-request: 12.0.1 - decompress-response: 6.0.0 + cacheable-request: 13.0.18 + decompress-response: 10.0.0 form-data-encoder: 4.1.0 http2-wrapper: 2.2.1 + keyv: 5.6.0 lowercase-keys: 3.0.0 p-cancelable: 4.0.1 - responselike: 3.0.0 + responselike: 4.0.2 type-fest: 4.41.0 graceful-fs@4.2.10: {} @@ -13371,7 +13484,7 @@ snapshots: dependencies: has-symbols: 1.1.0 - hashery@1.3.0: + hashery@1.5.0: dependencies: hookified: 1.15.1 @@ -13395,12 +13508,14 @@ snapshots: hosted-git-info@9.0.2: dependencies: - lru-cache: 11.2.4 + lru-cache: 11.2.6 hpagent@1.2.0: {} html-tags@5.1.0: {} + htmlfy@1.0.1: {} + http-cache-semantics@4.2.0: {} http-proxy-agent@7.0.2: @@ -13434,7 +13549,7 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.6.3: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -13454,12 +13569,10 @@ snapshots: import-from-esm@2.0.0: dependencies: debug: 4.4.3 - import-meta-resolve: 4.1.0 + import-meta-resolve: 4.2.0 transitivePeerDependencies: - supports-color - import-meta-resolve@4.1.0: {} - import-meta-resolve@4.2.0: {} imurmurhash@0.1.4: {} @@ -13468,7 +13581,7 @@ snapshots: indent-string@5.0.0: {} - index-to-position@1.1.0: {} + index-to-position@1.2.0: {} inflight@1.0.6: dependencies: @@ -13489,7 +13602,7 @@ snapshots: cli-width: 3.0.0 external-editor: 3.1.0 figures: 3.2.0 - lodash: 4.17.21 + lodash: 4.17.23 mute-stream: 0.0.8 run-async: 2.4.1 rxjs: 6.6.7 @@ -13566,7 +13679,7 @@ snapshots: is-ci@4.1.0: dependencies: - ci-info: 4.3.0 + ci-info: 4.4.0 is-core-module@2.16.1: dependencies: @@ -13583,6 +13696,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -13591,13 +13706,14 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-fullwidth-code-point@5.0.0: + is-fullwidth-code-point@5.1.0: dependencies: - get-east-asian-width: 1.3.0 + get-east-asian-width: 1.5.0 - is-generator-function@1.1.0: + is-generator-function@1.1.2: dependencies: call-bound: 1.0.4 + generator-function: 2.0.1 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 @@ -13606,6 +13722,10 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-interactive@1.0.0: {} is-map@2.0.3: {} @@ -13665,7 +13785,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.19 + which-typed-array: 1.1.20 is-unicode-supported@0.1.0: {} @@ -13686,6 +13806,10 @@ snapshots: is-windows@1.0.2: {} + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isarray@1.0.0: {} isarray@2.0.5: {} @@ -13700,15 +13824,9 @@ snapshots: lodash.isstring: 4.0.1 lodash.uniqby: 4.7.0 - jackspeak@3.4.3: + jackspeak@4.2.3: dependencies: - "@isaacs/cliui": 8.0.2 - optionalDependencies: - "@pkgjs/parseargs": 0.11.0 - - jackspeak@4.1.1: - dependencies: - "@isaacs/cliui": 8.0.2 + "@isaacs/cliui": 9.0.0 jake@10.9.4: dependencies: @@ -13720,25 +13838,23 @@ snapshots: jiti@1.21.7: {} - jiti@2.5.1: {} + jiti@2.6.1: {} jpeg-exif@1.1.4: {} js-tokens@4.0.0: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 - jsesc@3.0.2: {} - jsesc@3.1.0: {} json-buffer@3.0.1: {} json-fixer@1.6.15: dependencies: - "@babel/runtime": 7.28.3 + "@babel/runtime": 7.28.6 chalk: 4.1.2 pegjs: 0.10.0 @@ -13795,38 +13911,38 @@ snapshots: lint-staged@16.2.7: dependencies: - commander: 14.0.2 + commander: 14.0.3 listr2: 9.0.5 micromatch: 4.0.8 nano-spawn: 2.0.0 pidtree: 0.6.0 string-argv: 0.3.2 - yaml: 2.8.1 + yaml: 2.8.2 listr2@9.0.5: dependencies: cli-truncate: 5.1.1 colorette: 2.0.20 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 log-update: 6.1.0 rfdc: 1.4.1 - wrap-ansi: 9.0.0 + wrap-ansi: 9.0.2 - lit-element@4.2.1: + lit-element@4.2.2: dependencies: - "@lit-labs/ssr-dom-shim": 1.4.0 - "@lit/reactive-element": 2.1.1 - lit-html: 3.3.1 + "@lit-labs/ssr-dom-shim": 1.5.1 + "@lit/reactive-element": 2.1.2 + lit-html: 3.3.2 - lit-html@3.3.1: + lit-html@3.3.2: dependencies: "@types/trusted-types": 2.0.7 lit@3.3.2: dependencies: - "@lit/reactive-element": 2.1.1 - lit-element: 4.2.1 - lit-html: 3.3.1 + "@lit/reactive-element": 2.1.2 + lit-element: 4.2.2 + lit-html: 3.3.2 load-json-file@4.0.0: dependencies: @@ -13848,7 +13964,7 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.21: {} + lodash-es@4.17.23: {} lodash.camelcase@4.3.0: {} @@ -13868,9 +13984,6 @@ snapshots: lodash.memoize@4.1.2: {} - lodash.merge@4.6.2: - optional: true - lodash.mergewith@4.6.2: {} lodash.snakecase@4.1.1: {} @@ -13889,6 +14002,8 @@ snapshots: lodash@4.17.21: {} + lodash@4.17.23: {} + log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -13896,11 +14011,11 @@ snapshots: log-update@6.1.0: dependencies: - ansi-escapes: 7.0.0 + ansi-escapes: 7.3.0 cli-cursor: 5.0.0 - slice-ansi: 7.1.0 - strip-ansi: 7.1.0 - wrap-ansi: 9.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 longest@2.0.1: {} @@ -13908,7 +14023,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.4: {} + lru-cache@11.2.6: {} lru-cache@5.1.1: dependencies: @@ -13918,11 +14033,17 @@ snapshots: dependencies: sourcemap-codec: 1.4.8 + make-asynchronous@1.0.1: + dependencies: + p-event: 6.0.1 + type-fest: 4.41.0 + web-worker: 1.2.0 + marked-terminal@7.3.0(marked@15.0.12): dependencies: - ansi-escapes: 7.0.0 - ansi-regex: 6.2.0 - chalk: 5.6.0 + ansi-escapes: 7.3.0 + ansi-regex: 6.2.2 + chalk: 5.6.2 cli-highlight: 2.1.11 cli-table3: 0.6.5 marked: 15.0.12 @@ -13958,7 +14079,7 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime@4.0.7: {} + mime@4.1.0: {} mimic-fn@2.1.0: {} @@ -13966,15 +14087,13 @@ snapshots: mimic-function@5.0.1: {} - mimic-response@3.1.0: {} - mimic-response@4.0.0: {} mini-svg-data-uri@1.4.4: {} - minimatch@10.1.1: + minimatch@10.2.1: dependencies: - "@isaacs/brace-expansion": 5.0.0 + brace-expansion: 5.0.2 minimatch@3.1.2: dependencies: @@ -13992,10 +14111,12 @@ snapshots: minimist@1.2.8: {} - minipass@7.1.2: {} + minipass@7.1.3: {} mitt@3.0.1: {} + mrmime@2.0.1: {} + ms@2.1.3: {} mute-stream@0.0.8: {} @@ -14027,8 +14148,6 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-releases@2.0.19: {} - node-releases@2.0.27: {} normalize-package-data@6.0.2: @@ -14040,12 +14159,12 @@ snapshots: normalize-package-data@8.0.0: dependencies: hosted-git-info: 9.0.2 - semver: 7.7.2 + semver: 7.7.4 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} - normalize-url@8.0.2: {} + normalize-url@8.1.1: {} npm-run-path@4.0.1: dependencies: @@ -14060,7 +14179,7 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 - npm@11.7.0: {} + npm@11.10.0: {} nth-check@2.1.1: dependencies: @@ -14088,6 +14207,8 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 + ohash@2.0.11: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -14104,6 +14225,13 @@ snapshots: dependencies: mimic-function: 5.0.1 + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -14137,9 +14265,13 @@ snapshots: p-each-series@3.0.0: {} + p-event@6.0.1: + dependencies: + p-timeout: 6.1.4 + p-filter@4.1.0: dependencies: - p-map: 7.0.3 + p-map: 7.0.4 p-is-promise@3.0.0: {} @@ -14167,14 +14299,14 @@ snapshots: dependencies: p-limit: 3.1.0 - p-map@7.0.3: {} - p-map@7.0.4: {} p-reduce@2.1.0: {} p-reduce@3.0.0: {} + p-timeout@6.1.4: {} + p-try@1.0.0: {} p-try@2.2.0: {} @@ -14189,20 +14321,20 @@ snapshots: parse-json@4.0.0: dependencies: - error-ex: 1.3.2 + error-ex: 1.3.4 json-parse-better-errors: 1.0.2 parse-json@5.2.0: dependencies: - "@babel/code-frame": 7.27.1 - error-ex: 1.3.2 + "@babel/code-frame": 7.29.0 + error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 parse-json@8.3.0: dependencies: - "@babel/code-frame": 7.27.1 - index-to-position: 1.1.0 + "@babel/code-frame": 7.29.0 + index-to-position: 1.2.0 type-fest: 4.41.0 parse-ms@4.0.0: {} @@ -14237,29 +14369,28 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - path-scurry@2.0.1: dependencies: - lru-cache: 11.2.4 - minipass: 7.1.2 + lru-cache: 11.2.6 + minipass: 7.1.3 path-type@4.0.0: {} path-type@6.0.0: {} - pdfmake@0.2.20: + pathe@2.0.3: {} + + pdfmake@0.2.23: dependencies: "@foliojs-fork/linebreak": 1.1.2 "@foliojs-fork/pdfkit": 0.15.3 - iconv-lite: 0.6.3 - xmldoc: 2.0.2 + iconv-lite: 0.7.2 + xmldoc: 2.0.3 pegjs@0.10.0: {} + perfect-debounce@2.1.0: {} + performance-now@2.1.0: {} picocolors@1.1.1: {} @@ -14299,7 +14430,7 @@ snapshots: postcss-calc@10.1.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 postcss-clamp@4.1.0(postcss@8.5.6): @@ -14375,7 +14506,7 @@ snapshots: postcss-discard-comments@7.0.5(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-discard-duplicates@7.0.2(postcss@8.5.6): dependencies: @@ -14425,16 +14556,16 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.10 + resolve: 1.22.11 postcss-import@16.1.1(postcss@8.5.6): dependencies: postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.10 + resolve: 1.22.11 - postcss-js@4.0.1(postcss@8.5.6): + postcss-js@4.1.0(postcss@8.5.6): dependencies: camelcase-css: 2.0.1 postcss: 8.5.6 @@ -14448,12 +14579,13 @@ snapshots: "@csstools/utilities": 3.0.0(postcss@8.5.6) postcss: 8.5.6 - postcss-load-config@4.0.2(postcss@8.5.6): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2): dependencies: lilconfig: 3.1.3 - yaml: 2.8.1 optionalDependencies: + jiti: 1.21.7 postcss: 8.5.6 + yaml: 2.8.2 postcss-logical@9.0.0(postcss@8.5.6): dependencies: @@ -14464,7 +14596,7 @@ snapshots: dependencies: postcss: 8.5.6 postcss-value-parser: 4.2.0 - stylehacks: 7.0.6(postcss@8.5.6) + stylehacks: 7.0.7(postcss@8.5.6) postcss-merge-rules@7.0.7(postcss@8.5.6): dependencies: @@ -14472,7 +14604,7 @@ snapshots: caniuse-api: 3.0.0 cssnano-utils: 5.0.1(postcss@8.5.6) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-minify-font-values@7.0.1(postcss@8.5.6): dependencies: @@ -14497,12 +14629,7 @@ snapshots: dependencies: cssesc: 3.0.0 postcss: 8.5.6 - postcss-selector-parser: 7.1.0 - - postcss-nested@5.0.6(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.1.1 postcss-nested@6.2.0(postcss@8.5.6): dependencies: @@ -14628,7 +14755,7 @@ snapshots: "@csstools/postcss-text-decoration-shorthand": 5.0.2(postcss@8.5.6) "@csstools/postcss-trigonometric-functions": 5.0.0(postcss@8.5.6) "@csstools/postcss-unset-value": 5.0.0(postcss@8.5.6) - autoprefixer: 10.4.23(postcss@8.5.6) + autoprefixer: 10.4.24(postcss@8.5.6) browserslist: 4.28.1 css-blank-pseudo: 8.0.1(postcss@8.5.6) css-has-pseudo: 8.0.0(postcss@8.5.6) @@ -14706,11 +14833,6 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.0: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 @@ -14725,7 +14847,7 @@ snapshots: postcss-unique-selectors@7.0.4(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser@4.2.0: {} @@ -14755,7 +14877,7 @@ snapshots: pretty-bytes@6.1.1: {} - pretty-ms@9.2.0: + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -14803,16 +14925,16 @@ snapshots: read-package-up@12.0.0: dependencies: find-up-simple: 1.0.1 - read-pkg: 10.0.0 - type-fest: 5.3.1 + read-pkg: 10.1.0 + type-fest: 5.4.4 - read-pkg@10.0.0: + read-pkg@10.1.0: dependencies: "@types/normalize-package-data": 2.4.4 normalize-package-data: 8.0.0 parse-json: 8.3.0 - type-fest: 5.3.1 - unicorn-magic: 0.3.0 + type-fest: 5.4.4 + unicorn-magic: 0.4.0 read-pkg@9.0.1: dependencies: @@ -14846,14 +14968,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 - regenerate-unicode-properties@10.2.0: + regenerate-unicode-properties@10.2.2: dependencies: regenerate: 1.4.2 @@ -14868,24 +14990,24 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - regexpu-core@6.2.0: + regexpu-core@6.4.0: dependencies: regenerate: 1.4.2 - regenerate-unicode-properties: 10.2.0 + regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.12.0 + regjsparser: 0.13.0 unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.2.0 + unicode-match-property-value-ecmascript: 2.2.1 - registry-auth-token@5.1.0: + registry-auth-token@5.1.1: dependencies: - "@pnpm/npm-conf": 2.3.1 + "@pnpm/npm-conf": 3.0.2 regjsgen@0.8.0: {} - regjsparser@0.12.0: + regjsparser@0.13.0: dependencies: - jsesc: 3.0.2 + jsesc: 3.1.0 regression@2.0.1: {} @@ -14906,13 +15028,13 @@ snapshots: resolve-from@5.0.0: {} - resolve@1.22.10: + resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - responselike@3.0.0: + responselike@4.0.2: dependencies: lowercase-keys: 3.0.0 @@ -14936,32 +15058,39 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.47.1: + rollup@4.57.1: dependencies: "@types/estree": 1.0.8 optionalDependencies: - "@rollup/rollup-android-arm-eabi": 4.47.1 - "@rollup/rollup-android-arm64": 4.47.1 - "@rollup/rollup-darwin-arm64": 4.47.1 - "@rollup/rollup-darwin-x64": 4.47.1 - "@rollup/rollup-freebsd-arm64": 4.47.1 - "@rollup/rollup-freebsd-x64": 4.47.1 - "@rollup/rollup-linux-arm-gnueabihf": 4.47.1 - "@rollup/rollup-linux-arm-musleabihf": 4.47.1 - "@rollup/rollup-linux-arm64-gnu": 4.47.1 - "@rollup/rollup-linux-arm64-musl": 4.47.1 - "@rollup/rollup-linux-loongarch64-gnu": 4.47.1 - "@rollup/rollup-linux-ppc64-gnu": 4.47.1 - "@rollup/rollup-linux-riscv64-gnu": 4.47.1 - "@rollup/rollup-linux-riscv64-musl": 4.47.1 - "@rollup/rollup-linux-s390x-gnu": 4.47.1 - "@rollup/rollup-linux-x64-gnu": 4.47.1 - "@rollup/rollup-linux-x64-musl": 4.47.1 - "@rollup/rollup-win32-arm64-msvc": 4.47.1 - "@rollup/rollup-win32-ia32-msvc": 4.47.1 - "@rollup/rollup-win32-x64-msvc": 4.47.1 + "@rollup/rollup-android-arm-eabi": 4.57.1 + "@rollup/rollup-android-arm64": 4.57.1 + "@rollup/rollup-darwin-arm64": 4.57.1 + "@rollup/rollup-darwin-x64": 4.57.1 + "@rollup/rollup-freebsd-arm64": 4.57.1 + "@rollup/rollup-freebsd-x64": 4.57.1 + "@rollup/rollup-linux-arm-gnueabihf": 4.57.1 + "@rollup/rollup-linux-arm-musleabihf": 4.57.1 + "@rollup/rollup-linux-arm64-gnu": 4.57.1 + "@rollup/rollup-linux-arm64-musl": 4.57.1 + "@rollup/rollup-linux-loong64-gnu": 4.57.1 + "@rollup/rollup-linux-loong64-musl": 4.57.1 + "@rollup/rollup-linux-ppc64-gnu": 4.57.1 + "@rollup/rollup-linux-ppc64-musl": 4.57.1 + "@rollup/rollup-linux-riscv64-gnu": 4.57.1 + "@rollup/rollup-linux-riscv64-musl": 4.57.1 + "@rollup/rollup-linux-s390x-gnu": 4.57.1 + "@rollup/rollup-linux-x64-gnu": 4.57.1 + "@rollup/rollup-linux-x64-musl": 4.57.1 + "@rollup/rollup-openbsd-x64": 4.57.1 + "@rollup/rollup-openharmony-arm64": 4.57.1 + "@rollup/rollup-win32-arm64-msvc": 4.57.1 + "@rollup/rollup-win32-ia32-msvc": 4.57.1 + "@rollup/rollup-win32-x64-gnu": 4.57.1 + "@rollup/rollup-win32-x64-msvc": 4.57.1 fsevents: 2.3.3 + run-applescript@7.1.0: {} + run-async@2.4.1: {} run-parallel@1.2.0: @@ -15001,20 +15130,20 @@ snapshots: safer-buffer@2.1.2: {} - sax@1.4.1: {} + sax@1.4.4: {} semantic-release@25.0.3(typescript@5.9.3): dependencies: "@semantic-release/commit-analyzer": 13.0.1(semantic-release@25.0.3(typescript@5.9.3)) "@semantic-release/error": 4.0.0 - "@semantic-release/github": 12.0.2(semantic-release@25.0.3(typescript@5.9.3)) - "@semantic-release/npm": 13.1.3(semantic-release@25.0.3(typescript@5.9.3)) + "@semantic-release/github": 12.0.6(semantic-release@25.0.3(typescript@5.9.3)) + "@semantic-release/npm": 13.1.4(semantic-release@25.0.3(typescript@5.9.3)) "@semantic-release/release-notes-generator": 14.1.0(semantic-release@25.0.3(typescript@5.9.3)) aggregate-error: 5.0.0 cosmiconfig: 9.0.0(typescript@5.9.3) debug: 4.4.3 env-ci: 11.2.0 - execa: 9.6.0 + execa: 9.6.1 figures: 6.1.0 find-versions: 6.0.0 get-stream: 6.0.1 @@ -15022,7 +15151,7 @@ snapshots: hook-std: 4.0.0 hosted-git-info: 9.0.2 import-from-esm: 2.0.0 - lodash-es: 4.17.21 + lodash-es: 4.17.23 marked: 15.0.12 marked-terminal: 7.3.0(marked@15.0.12) micromatch: 4.0.8 @@ -15030,7 +15159,7 @@ snapshots: p-reduce: 3.0.0 read-package-up: 12.0.0 resolve-from: 5.0.0 - semver: 7.7.2 + semver: 7.7.4 signale: 1.4.0 yargs: 18.0.0 transitivePeerDependencies: @@ -15041,8 +15170,6 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} - semver@7.7.4: {} serialize-javascript@6.0.2: @@ -15148,6 +15275,12 @@ snapshots: figures: 2.0.0 pkg-conf: 2.1.0 + sirv@3.0.2: + dependencies: + "@polka/url": 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + skin-tone@2.0.0: dependencies: unicode-emoji-modifier-base: 1.0.0 @@ -15160,12 +15293,12 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - slice-ansi@7.1.0: + slice-ansi@7.1.2: dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 5.0.0 + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 - smob@1.5.0: {} + smob@1.6.1: {} source-map-js@1.2.1: {} @@ -15226,34 +15359,23 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - string-width@7.2.0: dependencies: - emoji-regex: 10.4.0 - get-east-asian-width: 1.3.0 - strip-ansi: 7.1.0 + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.1.2 - string-width@8.1.0: + string-width@8.2.0: dependencies: - get-east-asian-width: 1.3.0 - strip-ansi: 7.1.0 - - string-width@8.1.1: - dependencies: - get-east-asian-width: 1.3.0 - strip-ansi: 7.1.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.1.2 string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -15270,7 +15392,7 @@ snapshots: call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 @@ -15305,9 +15427,9 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.1.2: dependencies: - ansi-regex: 6.2.0 + ansi-regex: 6.2.2 strip-bom@3.0.0: {} @@ -15325,9 +15447,9 @@ snapshots: strip-json-comments@3.1.1: {} - style-mod@4.1.2: {} + style-mod@4.1.3: {} - stylehacks@7.0.6(postcss@8.5.6): + stylehacks@7.0.7(postcss@8.5.6): dependencies: browserslist: 4.28.1 postcss: 8.5.6 @@ -15346,7 +15468,7 @@ snapshots: dependencies: "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) - "@csstools/css-syntax-patches-for-csstree": 1.0.26 + "@csstools/css-syntax-patches-for-csstree": 1.0.27 "@csstools/css-tokenizer": 4.0.0 "@csstools/media-query-list-parser": 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) "@csstools/selector-resolve-nested": 4.0.0(postcss-selector-parser@7.1.1) @@ -15354,14 +15476,14 @@ snapshots: balanced-match: 3.0.1 colord: 2.9.3 cosmiconfig: 9.0.0(typescript@5.9.3) - css-functions-list: 3.2.3 + css-functions-list: 3.3.3 css-tree: 3.1.0 debug: 4.4.3 fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 file-entry-cache: 11.1.2 global-modules: 2.0.0 - globby: 16.1.0 + globby: 16.1.1 globjoin: 0.1.4 html-tags: 5.1.0 ignore: 7.0.5 @@ -15378,7 +15500,7 @@ snapshots: postcss-safe-parser: 7.0.1(postcss@8.5.6) postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - string-width: 8.1.1 + string-width: 8.2.0 supports-hyperlinks: 4.4.0 svg-tags: 1.0.0 table: 6.9.0 @@ -15387,19 +15509,20 @@ snapshots: - supports-color - typescript - sucrase@3.35.0: + sucrase@3.35.1: dependencies: "@jridgewell/gen-mapping": 0.3.13 commander: 4.1.1 - glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.7 + tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 - super-regex@1.0.0: + super-regex@1.1.0: dependencies: function-timeout: 1.0.2 + make-asynchronous: 1.0.1 time-span: 5.1.0 supports-color@10.2.2: {} @@ -15434,7 +15557,7 @@ snapshots: css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 - sax: 1.4.1 + sax: 1.4.4 synckit@0.11.12: dependencies: @@ -15442,7 +15565,7 @@ snapshots: table@6.9.0: dependencies: - ajv: 8.17.1 + ajv: 8.18.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 @@ -15450,7 +15573,7 @@ snapshots: tagged-tag@1.0.0: {} - tailwindcss@3.4.19: + tailwindcss@3.4.19(yaml@2.8.2): dependencies: "@alloc/quick-lru": 5.2.0 arg: 5.0.2 @@ -15468,14 +15591,15 @@ snapshots: picocolors: 1.1.1 postcss: 8.5.6 postcss-import: 15.1.0(postcss@8.5.6) - postcss-js: 4.0.1(postcss@8.5.6) - postcss-load-config: 4.0.2(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2) postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 - resolve: 1.22.10 - sucrase: 3.35.0 + resolve: 1.22.11 + sucrase: 3.35.1 transitivePeerDependencies: - - ts-node + - tsx + - yaml temp-dir@2.0.0: {} @@ -15488,14 +15612,14 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - tempy@3.1.0: + tempy@3.2.0: dependencies: is-stream: 3.0.0 temp-dir: 3.0.0 type-fest: 2.19.0 unique-string: 3.0.0 - terser@5.43.1: + terser@5.46.0: dependencies: "@jridgewell/source-map": 0.3.11 acorn: 8.15.0 @@ -15525,12 +15649,7 @@ snapshots: tiny-inflate@1.0.3: {} - tinyexec@1.0.1: {} - - tinyglobby@0.2.14: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + tinyexec@1.0.2: {} tinyglobby@0.2.15: dependencies: @@ -15547,6 +15666,8 @@ snapshots: dependencies: is-number: 7.0.0 + totalist@3.0.1: {} + tr46@0.0.3: {} tr46@1.0.1: @@ -15581,7 +15702,7 @@ snapshots: type-fest@4.41.0: {} - type-fest@5.3.1: + type-fest@5.4.4: dependencies: tagged-tag: 1.0.0 @@ -15618,13 +15739,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3): + typescript-eslint@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3): dependencies: - "@typescript-eslint/eslint-plugin": 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) - "@typescript-eslint/parser": 8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) + "@typescript-eslint/eslint-plugin": 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + "@typescript-eslint/parser": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) "@typescript-eslint/typescript-estree": 8.56.0(typescript@5.9.3) - "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@2.5.1))(typescript@5.9.3) - eslint: 10.0.0(jiti@2.5.1) + "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.0.0(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -15641,13 +15762,11 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - undici-types@7.10.0: {} + undici-types@7.14.0: {} - undici@5.29.0: - dependencies: - "@fastify/busboy": 2.1.1 + undici@6.23.0: {} - undici@7.16.0: {} + undici@7.22.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -15656,16 +15775,16 @@ snapshots: unicode-match-property-ecmascript@2.0.0: dependencies: unicode-canonical-property-names-ecmascript: 2.0.1 - unicode-property-aliases-ecmascript: 2.1.0 + unicode-property-aliases-ecmascript: 2.2.0 - unicode-match-property-value-ecmascript@2.2.0: {} + unicode-match-property-value-ecmascript@2.2.1: {} unicode-properties@1.4.1: dependencies: base64-js: 1.5.1 unicode-trie: 2.0.0 - unicode-property-aliases-ecmascript@2.1.0: {} + unicode-property-aliases-ecmascript@2.2.0: {} unicode-trie@2.0.0: dependencies: @@ -15690,13 +15809,12 @@ snapshots: universalify@2.0.1: {} - upath@1.2.0: {} - - update-browserslist-db@1.1.3(browserslist@4.25.3): + unplugin-utils@0.3.1: dependencies: - browserslist: 4.25.3 - escalade: 3.2.0 - picocolors: 1.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + + upath@1.2.0: {} update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: @@ -15719,48 +15837,73 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite-plugin-codeigniter@2.0.0(vite@7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)): + vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + birpc: 2.9.0 + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + + vite-hot-client@2.1.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + + vite-plugin-codeigniter@2.0.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): dependencies: glob: 11.1.0 picocolors: 1.1.1 sharp: 0.34.5 - vite: 7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1) - vite-plugin-static-copy: 3.2.0(vite@7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)) + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + vite-plugin-static-copy: 3.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) zod: 4.3.6 - vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(workbox-build@7.4.0)(workbox-window@7.4.0): + vite-plugin-inspect@11.3.3(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): dependencies: - debug: 4.4.1 + ansis: 4.2.0 + debug: 4.4.3 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 2.1.0 + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + transitivePeerDependencies: + - supports-color + + vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.4.0)(workbox-window@7.4.0): + dependencies: + debug: 4.4.3 pretty-bytes: 6.1.1 - tinyglobby: 0.2.14 - vite: 7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1) + tinyglobby: 0.2.15 + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) workbox-build: 7.4.0 workbox-window: 7.4.0 transitivePeerDependencies: - supports-color - vite-plugin-static-copy@3.2.0(vite@7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)): + vite-plugin-static-copy@3.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): dependencies: chokidar: 3.6.0 p-map: 7.0.4 picocolors: 1.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) - vite@7.3.1(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1): + vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2): dependencies: - esbuild: 0.27.2 + esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.47.1 + rollup: 4.57.1 tinyglobby: 0.2.15 optionalDependencies: - "@types/node": 24.3.0 + "@types/node": 24.7.0 fsevents: 2.3.3 - jiti: 2.5.1 - terser: 5.43.1 - yaml: 2.8.1 + jiti: 1.21.7 + terser: 5.46.0 + yaml: 2.8.2 w3c-keyname@2.2.8: {} @@ -15770,6 +15913,8 @@ snapshots: dependencies: defaults: 1.0.4 + web-worker@1.2.0: {} + webidl-conversions@3.0.1: {} webidl-conversions@4.0.2: {} @@ -15801,13 +15946,13 @@ snapshots: is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.0 + is-generator-function: 1.1.2 is-regex: 1.2.1 is-weakref: 1.1.1 isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.19 + which-typed-array: 1.1.20 which-collection@1.0.2: dependencies: @@ -15818,7 +15963,7 @@ snapshots: which-module@2.0.1: {} - which-typed-array@1.1.19: + which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 @@ -15851,21 +15996,21 @@ snapshots: workbox-build@7.4.0: dependencies: - "@apideck/better-ajv-errors": 0.3.6(ajv@8.17.1) - "@babel/core": 7.28.3 - "@babel/preset-env": 7.28.3(@babel/core@7.28.3) - "@babel/runtime": 7.28.3 - "@rollup/plugin-babel": 5.3.1(@babel/core@7.28.3)(rollup@2.79.2) + "@apideck/better-ajv-errors": 0.3.6(ajv@8.18.0) + "@babel/core": 7.29.0 + "@babel/preset-env": 7.29.0(@babel/core@7.29.0) + "@babel/runtime": 7.28.6 + "@rollup/plugin-babel": 5.3.1(@babel/core@7.29.0)(rollup@2.79.2) "@rollup/plugin-node-resolve": 15.3.1(rollup@2.79.2) "@rollup/plugin-replace": 2.4.2(rollup@2.79.2) "@rollup/plugin-terser": 0.4.4(rollup@2.79.2) "@surma/rollup-plugin-off-main-thread": 2.2.3 - ajv: 8.17.1 + ajv: 8.18.0 common-tags: 1.8.2 fast-json-stable-stringify: 2.1.0 fs-extra: 9.1.0 glob: 11.1.0 - lodash: 4.17.21 + lodash: 4.17.23 pretty-bytes: 5.6.0 rollup: 2.79.2 source-map: 0.8.0-beta.0 @@ -15965,17 +16110,11 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: + wrap-ansi@9.0.2: dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - wrap-ansi@9.0.0: - dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 wrappy@1.0.2: {} @@ -15984,15 +16123,19 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + xml-formatter@3.6.7: dependencies: xml-parser-xo: 4.1.5 xml-parser-xo@4.1.5: {} - xmldoc@2.0.2: + xmldoc@2.0.3: dependencies: - sax: 1.4.1 + sax: 1.4.4 xtend@4.0.2: {} @@ -16002,7 +16145,7 @@ snapshots: yallist@3.1.1: {} - yaml@2.8.1: {} + yaml@2.8.2: {} yargs-parser@18.1.3: dependencies: diff --git a/postcss.config.cjs b/postcss.config.cjs index bf17a435..ec20059e 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -3,7 +3,6 @@ module.exports = { plugins: [ require("postcss-reporter"), - require("tailwindcss/nesting")(require("postcss-nesting")), require("tailwindcss"), require("postcss-preset-env")({ stage: 4, diff --git a/public/index.php b/public/index.php index 388a924e..070db6ca 100644 --- a/public/index.php +++ b/public/index.php @@ -11,7 +11,7 @@ use Config\Paths; *--------------------------------------------------------------- */ -$minPhpVersion = '8.1'; // If you update this, don't forget to update `spark`. +$minPhpVersion = '8.5'; // If you update this, don't forget to update `spark`. if (version_compare(PHP_VERSION, $minPhpVersion, '<')) { $message = sprintf( 'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s', diff --git a/rector.php b/rector.php index 2ba3efa0..9fb15216 100644 --- a/rector.php +++ b/rector.php @@ -17,8 +17,8 @@ use Rector\ValueObject\PhpVersion; return RectorConfig::configure() ->withPaths([__DIR__ . '/app', __DIR__ . '/modules', __DIR__ . '/tests', __DIR__ . '/public']) ->withBootstrapFiles([__DIR__ . '/vendor/codeigniter4/framework/system/Test/bootstrap.php']) - ->withPhpVersion(PhpVersion::PHP_81) - ->withPhpSets(php81: true) + ->withPhpVersion(PhpVersion::PHP_85) + ->withPhpSets(php85: true) ->withPreparedSets( typeDeclarations: true, codeQuality: true, diff --git a/resources/js/_modules/FieldArray.ts b/resources/js/_modules/FieldArray.ts new file mode 100644 index 00000000..05baad4e --- /dev/null +++ b/resources/js/_modules/FieldArray.ts @@ -0,0 +1,159 @@ +import Tooltip from "./Tooltip"; + +const FieldArray = (): void => { + const fieldArrays: NodeListOf = + document.querySelectorAll("[data-field-array]"); + + for (let i = 0; i < fieldArrays.length; i++) { + const fieldArray = fieldArrays[i]; + const fieldArrayContainer = fieldArray.querySelector( + "[data-field-array-container]" + ); + const items: NodeListOf = fieldArray.querySelectorAll( + "[data-field-array-item]" + ); + const addButton = fieldArray.querySelector( + "button[data-field-array-add]" + ) as HTMLButtonElement; + + const deleteButtons: NodeListOf = + fieldArray.querySelectorAll("[data-field-array-delete]"); + + deleteButtons.forEach((deleteBtn) => { + deleteBtn.addEventListener("click", (e) => { + e.preventDefault(); + deleteBtn.blur(); + fieldArrayContainer + ?.querySelector( + `[data-field-array-item="${deleteBtn.dataset.fieldArrayDelete}"]` + ) + ?.remove(); + }); + }); + + // create base element to clone + const baseItem = items[0].cloneNode(true) as HTMLElement; + + const elements: NodeListOf = baseItem.querySelectorAll( + "input, select, textarea" + ); + + elements.forEach((element) => { + element.value = ""; + }); + + if (fieldArrayContainer && addButton) { + addButton.addEventListener("click", (event) => { + event.preventDefault(); + + const newItem = baseItem.cloneNode(true) as HTMLElement; + + const deleteBtn: HTMLButtonElement | null = newItem.querySelector( + "button[data-field-array-delete]" + ); + + if (deleteBtn) { + deleteBtn.addEventListener("click", () => { + deleteBtn.blur(); + newItem.remove(); + }); + + fieldArrayContainer.appendChild(newItem); + newItem.scrollIntoView({ + behavior: "auto", + block: "center", + inline: "center", + }); + + // reload tooltip module for showing remove button label + Tooltip(); + + // focus to first form element if mouse click + if (event.screenX !== 0 && event.screenY !== 0) { + const elements: NodeListOf = + newItem.querySelectorAll("input, select, textarea"); + + if (elements.length > 0) { + elements[0].focus(); + } + } + } + }); + + const updateIndexes = () => { + // get last child item to set item count + const items: NodeListOf = + fieldArrayContainer.querySelectorAll("[data-field-array-item]"); + + let itemIndex = 0; + items.forEach((item) => { + const itemNumber: HTMLElement | null = item.querySelector( + "[data-field-array-number]" + ); + + if (itemNumber) { + itemNumber.innerHTML = "#"; + const indexNum = itemIndex + 1; + if (item.dataset.fieldArrayItem !== itemIndex.toString()) { + item.classList.add("motion-safe:animate-single-pulse"); + setTimeout(() => { + item.classList.remove("motion-safe:animate-single-pulse"); + itemNumber.innerHTML = indexNum.toString(); + }, 300); + } else { + itemNumber.innerHTML = indexNum.toString(); + } + } + + item.dataset.fieldArrayItem = itemIndex.toString(); + const deleteBtn = item.querySelector( + "button[data-field-array-delete]" + ) as HTMLButtonElement | null; + + if (deleteBtn) { + deleteBtn.dataset.fieldArrayDelete = itemIndex.toString(); + } + + const itemElements: NodeListOf = + item.querySelectorAll("input, select, textarea"); + + itemElements.forEach((element) => { + const label: HTMLLabelElement | null = item.querySelector( + `label[for="${element.id}"]` + ); + + const elementID = element.name.replace( + /(.*\[)\d+?(\].*)/g, + `$1${itemIndex}$2` + ); + + if (label) { + label.htmlFor = elementID; + } + + element.id = elementID; + element.name = elementID; + }); + + itemIndex++; + }); + }; + + // add mutation observer to run index updates when field array + // items are added or removed + const callback = function (mutationList: MutationRecord[]) { + for (const mutation of mutationList) { + if (mutation.type === "childList") { + updateIndexes(); + } + } + }; + + const observer = new MutationObserver(callback); + + observer.observe(fieldArrayContainer, { childList: true }); + } + } +}; + +export default FieldArray; diff --git a/resources/js/_modules/MultiSelect.ts b/resources/js/_modules/SelectMulti.ts similarity index 96% rename from resources/js/_modules/MultiSelect.ts rename to resources/js/_modules/SelectMulti.ts index 1f25fd77..064bc78a 100644 --- a/resources/js/_modules/MultiSelect.ts +++ b/resources/js/_modules/SelectMulti.ts @@ -1,6 +1,6 @@ import Choices from "choices.js"; -const MultiSelect = (): void => { +const SelectMulti = (): void => { // Pass single element const multiSelects: NodeListOf = document.querySelectorAll("select[multiple]"); @@ -52,4 +52,4 @@ const MultiSelect = (): void => { } }; -export default MultiSelect; +export default SelectMulti; diff --git a/resources/js/_modules/code-editor.ts b/resources/js/_modules/code-editor.ts new file mode 100644 index 00000000..446752ca --- /dev/null +++ b/resources/js/_modules/code-editor.ts @@ -0,0 +1,229 @@ +import { indentWithTab } from "@codemirror/commands"; +import { html as htmlLang } from "@codemirror/lang-html"; +import { xml } from "@codemirror/lang-xml"; +import { + defaultHighlightStyle, + syntaxHighlighting, +} from "@codemirror/language"; +import { Compartment, EditorState, Extension } from "@codemirror/state"; +import { keymap, ViewUpdate } from "@codemirror/view"; +import { basicSetup, EditorView } from "codemirror"; +import { prettify as prettifyHTML, minify as minifyHTML } from "htmlfy"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { + customElement, + property, + queryAssignedNodes, + state, +} from "lit/decorators.js"; +import xmlFormat from "xml-formatter"; + +const language = new Compartment(); + +@customElement("code-editor") +export class XMLEditor extends LitElement { + @queryAssignedNodes({ slot: "textarea" }) + _textarea!: NodeListOf; + + @property() + lang = "html"; + + @state() + editorState!: EditorState; + + @state() + editorView!: EditorView; + + _textareaEvents = [ + { + events: ["focus", "invalid"], + onEvent: (_: Event, editor: EditorView) => { + // focus editor when textarea is focused or invalid + editor.focus(); + }, + }, + ]; + + firstUpdated(): void { + const minHeightEditor = EditorView.baseTheme({ + ".cm-content, .cm-gutter": { + minHeight: this._textarea[0].clientHeight + "px", + }, + }); + + const extensions: Extension[] = [ + basicSetup, + keymap.of([indentWithTab]), + minHeightEditor, + syntaxHighlighting(defaultHighlightStyle), + EditorView.updateListener.of((viewUpdate: ViewUpdate) => { + if (viewUpdate.docChanged) { + // Document changed, minify and update textarea value + switch (this.lang) { + case "xml": + this._textarea[0].value = minifyXML( + viewUpdate.state.doc.toString() + ); + break; + case "html": + this._textarea[0].value = minifyHTML( + viewUpdate.state.doc.toString() + ); + break; + default: + this._textarea[0].value = viewUpdate.state.doc.toString(); + break; + } + } + }), + ]; + + let editorContents = ""; + switch (this.lang) { + case "xml": + editorContents = formatXML(this._textarea[0].value); + extensions.push(language.of(xml())); + break; + case "html": + editorContents = prettifyHTML(this._textarea[0].value); + extensions.push(language.of(htmlLang())); + break; + default: + break; + } + + this.editorState = EditorState.create({ + doc: editorContents, + extensions: extensions, + }); + + this.editorView = new EditorView({ + state: this.editorState, + root: this.shadowRoot as ShadowRoot, + parent: this.shadowRoot as ShadowRoot, + }); + + // hide textarea + this._textarea[0].style.position = "absolute"; + this._textarea[0].style.opacity = "0"; + this._textarea[0].style.zIndex = "-9999"; + this._textarea[0].style.pointerEvents = "none"; + + for (const event of this._textareaEvents) { + event.events.forEach((name) => { + this._textarea[0].addEventListener(name, (e) => + event.onEvent(e, this.editorView) + ); + }); + } + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + + for (const event of this._textareaEvents) { + event.events.forEach((name) => { + this._textarea[0].removeEventListener(name, (e) => + event.onEvent(e, this.editorView) + ); + }); + } + } + + static styles = css` + .cm-editor { + border-radius: 0.5rem; + overflow: hidden; + border: 3px solid hsl(var(--color-border-contrast)); + background-color: hsl(var(--color-background-elevated)); + transition-property: + color, + background-color, + border-color, + text-decoration-color, + fill, + stroke, + opacity, + box-shadow, + transform, + filter, + backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + } + .cm-editor.cm-focused { + outline: 2px solid transparent; + box-shadow: + 0 0 0 2px hsl(var(--color-background-elevated)), + 0 0 0 calc(4px) hsl(var(--color-accent-base)); + } + .cm-gutters { + background-color: hsl(var(--color-background-elevated)) !important; + } + + .cm-activeLine { + background-color: hsla( + var(--color-background-highlight) / 0.25 + ) !important; + } + + .cm-activeLineGutter { + background-color: hsl(var(--color-background-highlight)) !important; + } + + .ͼ4 .cm-line { + caret-color: hsl(var(--color-text-base)) !important; + } + + .ͼ1 .cm-cursor { + border: none; + } + `; + + render(): TemplateResult<1> { + return html``; + } +} + +function formatXML(contents: string) { + if (contents === "") { + return contents; + } + + try { + return xmlFormat(contents, { + indentation: " ", + }); + } catch { + // xml doesn't have a root node + const editorContents = xmlFormat("" + contents + "", { + indentation: " ", + }); + // remove root, unnecessary lines and indents + return editorContents + .replace(/^/, "") + .replace(/<\/root>$/, "") + .replace(/^\s*[\r\n]/gm, "") + .replace(/[\r\n] {2}/gm, "\r\n") + .trim(); + } +} + +function minifyXML(contents: string) { + if (contents === "") { + return contents; + } + + try { + return xmlFormat.minify(contents, { + collapseContent: true, + }); + } catch { + const minifiedContent = xmlFormat.minify(`${contents}`, { + collapseContent: true, + }); + // remove root + return minifiedContent.replace(/^/, "").replace(/<\/root>$/, ""); + } +} diff --git a/resources/js/_modules/permalink-edit.ts b/resources/js/_modules/permalink-edit.ts index 6a8efad6..c0ea0224 100644 --- a/resources/js/_modules/permalink-edit.ts +++ b/resources/js/_modules/permalink-edit.ts @@ -72,6 +72,8 @@ export class PermalinkEdit extends LitElement { } firstUpdated(): void { + console.log(this._slugInput); + this.permalinkBase += this.permalinkBase.endsWith("/") ? "" : "/"; // set permalink value @@ -145,7 +147,7 @@ export class PermalinkEdit extends LitElement { border-color: transparent !important; padding-left: 0 !important; margin-left: -0.25rem !important; - font-weight: 600; + font-weight: 600 !important; } ::slotted([slot="domain"]) { diff --git a/resources/js/_modules/xml-editor.ts b/resources/js/_modules/xml-editor.ts deleted file mode 100644 index f4f5de57..00000000 --- a/resources/js/_modules/xml-editor.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { indentWithTab } from "@codemirror/commands"; -import { xml } from "@codemirror/lang-xml"; -import { - defaultHighlightStyle, - syntaxHighlighting, -} from "@codemirror/language"; -import { Compartment, EditorState } from "@codemirror/state"; -import { keymap } from "@codemirror/view"; -import { basicSetup, EditorView } from "codemirror"; -import { css, html, LitElement, TemplateResult } from "lit"; -import { customElement, queryAssignedNodes, state } from "lit/decorators.js"; -import prettifyXML from "xml-formatter"; - -const language = new Compartment(); - -@customElement("xml-editor") -export class XMLEditor extends LitElement { - @queryAssignedNodes({ slot: "textarea" }) - _textarea!: NodeListOf; - - @state() - editorState!: EditorState; - - @state() - editorView!: EditorView; - - firstUpdated(): void { - const minHeightEditor = EditorView.theme({ - ".cm-content, .cm-gutter": { - minHeight: this._textarea[0].clientHeight + "px", - }, - }); - - let editorContents = ""; - if (this._textarea[0].value) { - try { - editorContents = prettifyXML(this._textarea[0].value, { - indentation: " ", - }); - } catch { - // xml doesn't have a root node - editorContents = prettifyXML( - "" + this._textarea[0].value + "", - { - indentation: " ", - } - ); - // remove root, unnecessary lines and indents - editorContents = editorContents - .replace(/^/, "") - .replace(/<\/root>$/, "") - .replace(/^\s*[\r\n]/gm, "") - .replace(/[\r\n] {2}/gm, "\r\n") - .trim(); - } - } - - this.editorState = EditorState.create({ - doc: editorContents, - extensions: [ - basicSetup, - keymap.of([indentWithTab]), - language.of(xml()), - minHeightEditor, - syntaxHighlighting(defaultHighlightStyle), - ], - }); - - this.editorView = new EditorView({ - state: this.editorState, - root: this.shadowRoot as ShadowRoot, - parent: this.shadowRoot as ShadowRoot, - }); - - this._textarea[0].hidden = true; - if (this._textarea[0].form) { - this._textarea[0].form.addEventListener("submit", () => { - this._textarea[0].value = this.editorView.state.doc.toString(); - }); - } - } - - disconnectedCallback(): void { - if (this._textarea[0].form) { - this._textarea[0].form.removeEventListener("submit", () => { - this._textarea[0].value = this.editorView.state.doc.toString(); - }); - } - } - - static styles = css` - .cm-editor { - border-radius: 0.5rem; - overflow: hidden; - border: 3px solid hsl(var(--color-border-contrast)); - background-color: hsl(var(--color-background-elevated)); - } - .cm-editor.cm-focused { - outline: 2px solid transparent; - box-shadow: - 0 0 0 2px hsl(var(--color-background-elevated)), - 0 0 0 calc(4px) hsl(var(--color-accent-base)); - } - .cm-gutters { - background-color: hsl(var(--color-background-elevated)) !important; - } - - .cm-activeLine { - background-color: hsl(var(--color-background-highlight)) !important; - } - - .cm-activeLineGutter { - background-color: hsl(var(--color-background-highlight)) !important; - } - - .ͼ4 .cm-line { - caret-color: hsl(var(--color-text-base)) !important; - } - - .ͼ1 .cm-cursor { - border: none; - } - `; - - render(): TemplateResult<1> { - return html``; - } -} diff --git a/resources/js/admin.ts b/resources/js/admin.ts index d3917328..bc87479d 100644 --- a/resources/js/admin.ts +++ b/resources/js/admin.ts @@ -8,7 +8,7 @@ import Dropdown from "./_modules/Dropdown"; import HotKeys from "./_modules/HotKeys"; import "./_modules/markdown-preview"; import "./_modules/markdown-write-preview"; -import MultiSelect from "./_modules/MultiSelect"; +import SelectMulti from "./_modules/SelectMulti"; import "./_modules/permalink-edit"; import "./_modules/play-soundbite"; import PublishMessageWarning from "./_modules/PublishMessageWarning"; @@ -21,12 +21,14 @@ import Tooltip from "./_modules/Tooltip"; import ValidateFileSize from "./_modules/ValidateFileSize"; import "./_modules/video-clip-previewer"; import VideoClipBuilder from "./_modules/VideoClipBuilder"; -import "./_modules/xml-editor"; +import "./_modules/code-editor"; +import "@patternfly/elements/pf-tabs/pf-tabs.js"; +import FieldArray from "./_modules/FieldArray"; Dropdown(); Tooltip(); Select(); -MultiSelect(); +SelectMulti(); Slugify(); SidebarToggler(); ClientTimezone(); @@ -38,3 +40,4 @@ PublishMessageWarning(); HotKeys(); ValidateFileSize(); VideoClipBuilder(); +FieldArray(); diff --git a/resources/styles/_modules/breadcrumb.css b/resources/styles/_modules/breadcrumb.css index 5b73b775..b1ef8a30 100644 --- a/resources/styles/_modules/breadcrumb.css +++ b/resources/styles/_modules/breadcrumb.css @@ -15,10 +15,6 @@ &:hover { @apply underline; } - - &:focus { - @apply ring-accent; - } } .breadcrumb-item.active { diff --git a/resources/styles/_modules/choices.css b/resources/styles/_modules/choices.css index aaa9d547..a83a52f1 100644 --- a/resources/styles/_modules/choices.css +++ b/resources/styles/_modules/choices.css @@ -179,7 +179,7 @@ } .choices__list--multiple .choices__item { - @apply inline-block font-semibold px-1 py-0.5 text-sm align-middle rounded text-accent-hover bg-base border-accent-base ring-2 ring-accent; + @apply inline-block font-semibold px-1 py-0.5 text-sm align-middle rounded text-accent-hover bg-base border-accent-base ring-2 ring-accent-base; word-break: break-all; box-sizing: border-box; diff --git a/resources/styles/_modules/custom.css b/resources/styles/_modules/custom.css index 3831ee67..e327b2ca 100644 --- a/resources/styles/_modules/custom.css +++ b/resources/styles/_modules/custom.css @@ -1,3 +1,17 @@ +@layer base { + html { + scroll-behavior: smooth; + } + + .form-helper { + @apply text-skin-muted; + } + + select { + box-shadow: 2px 2px 0 hsl(var(--color-border-contrast)); + } +} + @layer components { .post-content { & a { @@ -6,7 +20,7 @@ } .ring-accent { - @apply outline-none ring-2 ring-offset-2; + @apply outline-none ring-2 ring-offset-2 ring-accent-base; /* FIXME: why doesn't ring-accent-base work? */ --tw-ring-opacity: 1; @@ -79,4 +93,13 @@ #facc15 20px ); } + + .divide-fieldset-y > :not([hidden], legend) ~ :not([hidden], legend) { + @apply pt-4; + + --tw-divide-y-reverse: 0; + + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); + } } diff --git a/resources/styles/_modules/radioBtn.css b/resources/styles/_modules/radioBtn.css index 72834e3e..e892ee0d 100644 --- a/resources/styles/_modules/radioBtn.css +++ b/resources/styles/_modules/radioBtn.css @@ -1,23 +1,33 @@ @layer components { .form-radio-btn { - @apply absolute mt-3 ml-3 border-contrast border-3 text-accent-base; + @apply absolute right-4 top-4 border-contrast border-3 text-accent-base transition; &:focus { @apply ring-accent; } &:checked { - @apply ring-2 ring-contrast; - & + label { - @apply text-accent-contrast bg-accent-base; + @apply text-accent-hover bg-base border-accent-base shadow-none; + } + + & + label .form-radio-btn-description { + @apply text-accent-base; } } & + label { - @apply inline-flex items-center py-2 pl-8 pr-2 text-sm font-semibold rounded-lg cursor-pointer border-contrast bg-elevated border-3; + @apply h-full w-full inline-flex flex-col items-start py-3 px-4 text-sm font-bold rounded-lg cursor-pointer border-contrast bg-elevated border-3 transition-all; - color: hsl(var(--color-text-muted)); + box-shadow: 2px 2px 0 hsl(var(--color-border-contrast)); + } + + & + label span { + @apply pr-8; + } + + & + label .form-radio-btn-description { + @apply font-normal text-xs text-skin-muted text-balance; } } } diff --git a/resources/styles/_modules/readMore.css b/resources/styles/_modules/readMore.css index 520aa123..38f40e02 100644 --- a/resources/styles/_modules/readMore.css +++ b/resources/styles/_modules/readMore.css @@ -33,7 +33,7 @@ Read more component (basic unstyled component) /* Don't forget focus and hover styles for accessibility! */ .read-more__checkbox:focus ~ .read-more__label { - @apply ring; + @apply ring-accent; } .read-more__checkbox:hover ~ .read-more__label { diff --git a/resources/styles/_modules/switch.css b/resources/styles/_modules/switch.css index fb80c4dc..3065dc40 100644 --- a/resources/styles/_modules/switch.css +++ b/resources/styles/_modules/switch.css @@ -11,13 +11,13 @@ } &:checked + .form-switch-slider::before { - @apply transform translate-x-8; + @apply transform translate-x-6; } &:checked + .form-switch-slider::after { - @apply transform translate-x-0 left-2; + @apply transform translate-x-0 left-1.5; - content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='m10 15.172 9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z'/%3E%3C/svg%3E%0A"); + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='m10 15.172 9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z'/%3E%3C/svg%3E%0A"); } &:checked + .form-switch-slider.form-switch-slider--small::before { @@ -30,7 +30,7 @@ } .form-switch-slider { - @apply relative inset-0 flex-shrink-0 w-16 h-8 transition duration-200 rounded-full cursor-pointer bg-highlight border-contrast border-3; + @apply relative inset-0 flex-shrink-0 h-8 transition duration-200 rounded-full cursor-pointer w-14 bg-highlight border-contrast border-3; &.form-switch-slider--small { @apply w-12 h-6; @@ -56,10 +56,11 @@ } &::after { - @apply absolute w-5 h-5 transition duration-150 transform translate-x-5; + @apply absolute w-4 h-4 transition duration-150 transform top-1; - content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='m12 10.586 4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z'/%3E%3C/svg%3E%0A"); - top: 3px; + --tw-translate-x: 1.125rem; + + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='m12 10.586 4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z'/%3E%3C/svg%3E%0A"); left: 10px; } } diff --git a/resources/styles/admin.css b/resources/styles/admin.css new file mode 100644 index 00000000..bb90fa4b --- /dev/null +++ b/resources/styles/admin.css @@ -0,0 +1,17 @@ +@import url("./_modules/tailwind.css"); +@import url("./_modules/custom.css"); +@import url("./_modules/fonts.css"); +@import url("./_modules/colors.css"); +@import url("./_modules/breadcrumb.css"); +@import url("./_modules/dropdown.css"); +@import url("./_modules/choices.css"); +@import url("./_modules/radioBtn.css"); +@import url("./_modules/colorRadioBtn.css"); +@import url("./_modules/switch.css"); +@import url("./_modules/radioToggler.css"); +@import url("./_modules/formInputTabs.css"); +@import url("./_modules/stickyHeader.css"); +@import url("./_modules/readMore.css"); +@import url("./_modules/seeMore.css"); + +@config '../../tailwind.admin.config.js'; diff --git a/resources/styles/install.css b/resources/styles/install.css new file mode 100644 index 00000000..e14d0dc0 --- /dev/null +++ b/resources/styles/install.css @@ -0,0 +1,17 @@ +@import url("./_modules/tailwind.css"); +@import url("./_modules/custom.css"); +@import url("./_modules/fonts.css"); +@import url("./_modules/colors.css"); +@import url("./_modules/breadcrumb.css"); +@import url("./_modules/dropdown.css"); +@import url("./_modules/choices.css"); +@import url("./_modules/radioBtn.css"); +@import url("./_modules/colorRadioBtn.css"); +@import url("./_modules/switch.css"); +@import url("./_modules/radioToggler.css"); +@import url("./_modules/formInputTabs.css"); +@import url("./_modules/stickyHeader.css"); +@import url("./_modules/readMore.css"); +@import url("./_modules/seeMore.css"); + +@config '../../tailwind.install.config.js'; diff --git a/resources/styles/index.css b/resources/styles/site.css similarity index 94% rename from resources/styles/index.css rename to resources/styles/site.css index 1837cd21..c5e54f99 100644 --- a/resources/styles/index.css +++ b/resources/styles/site.css @@ -13,3 +13,5 @@ @import url("./_modules/stickyHeader.css"); @import url("./_modules/readMore.css"); @import url("./_modules/seeMore.css"); + +@config '../../tailwind.config.js'; diff --git a/scripts/bundle-prepare.sh b/scripts/bundle-prepare.sh index 061779bd..4d011586 100644 --- a/scripts/bundle-prepare.sh +++ b/scripts/bundle-prepare.sh @@ -6,3 +6,4 @@ composer install --no-dev --prefer-dist --no-ansi --no-interaction --no-progress # build all production static assets (css, js, images, icons, fonts, etc.) pnpm run build +pnpm run build:static diff --git a/scripts/bundle.sh b/scripts/bundle.sh index 4e8f6f81..2d049601 100644 --- a/scripts/bundle.sh +++ b/scripts/bundle.sh @@ -2,7 +2,8 @@ set -e VERSION=$1 -COMPOSER_VERSION=$(echo "$VERSION" | sed -r 's/(alpha|beta)./\1/g') +COMPOSER_VERSION=$(echo "$VERSION" | sed -r 's/(alpha|beta|next)./\1/g') +COMPOSER_VERSION=$(echo "$COMPOSER_VERSION" | sed -r 's/next[0-9]+/dev/g') # replace composer.json version using jq echo "$( jq '.version = "'$COMPOSER_VERSION'"' composer.json )" > composer.json diff --git a/spark b/spark index 4b393dd9..aef372bc 100644 --- a/spark +++ b/spark @@ -40,7 +40,7 @@ if (str_starts_with(PHP_SAPI, 'cgi')) { *--------------------------------------------------------------- */ -$minPhpVersion = '8.1'; // If you update this, don't forget to update `public/index.php`. +$minPhpVersion = '8.5'; // If you update this, don't forget to update `public/index.php`. if (version_compare(PHP_VERSION, $minPhpVersion, '<')) { $message = sprintf( 'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s', diff --git a/tailwind.admin.config.js b/tailwind.admin.config.js new file mode 100644 index 00000000..f6d4fea4 --- /dev/null +++ b/tailwind.admin.config.js @@ -0,0 +1,171 @@ +import defaultTheme from "tailwindcss/defaultTheme"; +import tailwindForms from "@tailwindcss/forms"; +import tailwindTypography from "@tailwindcss/typography"; + +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./app/Views/**/*.php", + "./themes/cp_admin/**/*.php", + "./themes/cp_auth/**/*.php", + "./app/Helpers/*.php", + "./resources/**/*.ts", + ], + theme: { + extend: { + content: { + chevronRightIcon: + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' viewBox='0 0 24 24'%3E%3Cpath d='M13.17 12 8.22 7.05l1.42-1.41L16 12l-6.36 6.36-1.42-1.41L13.17 12Z'/%3E%3C/svg%3E%0A\")", + prohibitedIcon: + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' viewBox='0 0 24 24'%3E%3Cpath d='M7.0943 5.68009L18.3199 16.9057C19.3736 15.5506 20 13.8491 20 12C20 7.58172 16.4183 4 12 4C10.1509 4 8.44939 4.62644 7.0943 5.68009ZM16.9057 18.3199L5.68009 7.0943C4.62644 8.44939 4 10.1509 4 12C4 16.4183 7.58172 20 12 20C13.8491 20 15.5506 19.3736 16.9057 18.3199ZM4.92893 4.92893C6.73748 3.12038 9.23885 2 12 2C17.5228 2 22 6.47715 22 12C22 14.7611 20.8796 17.2625 19.0711 19.0711C17.2625 20.8796 14.7611 22 12 22C6.47715 22 2 17.5228 2 12C2 9.23885 3.12038 6.73748 4.92893 4.92893Z'/%3E%3C/svg%3E%0A\")", + }, + fontFamily: { + sans: ["Inter", ...defaultTheme.fontFamily.sans], + display: ["Kumbh Sans", ...defaultTheme.fontFamily.sans], + mono: ["Noto Sans Mono", ...defaultTheme.fontFamily.mono], + }, + textDecorationThickness: { + 3: "3px", + }, + textColor: { + skin: { + base: "hsl(var(--color-text-base) / )", + muted: "hsl(var(--color-text-muted) / )", + }, + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + muted: "hsl(var(--color-accent-muted) / )", + contrast: "hsl(var(--color-accent-contrast) / )", + }, + }, + backgroundColor: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + subtle: "hsl(var(--color-border-subtle) / )", + navigation: "hsl(var(--color-background-navigation) / )", + "navigation-active": + "hsl(var(--color-background-navigation-active) / )", + backdrop: "hsl(var(--color-background-backdrop) / )", + header: "hsl(var(--color-background-header) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + highlight: "hsl(var(--color-background-highlight) / )", + }, + borderColor: { + subtle: "hsl(var(--color-border-subtle) / )", + contrast: "hsl(var(--color-border-contrast) / )", + navigation: "hsl(var(--color-border-navigation) / )", + "navigation-bg": + "hsl(var(--color-background-navigation) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + background: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + }, + }, + ringColor: { + contrast: "hsl(var(--color-border-contrast) / )", + background: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + }, + }, + colors: { + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + background: { + header: "hsl(var(--color-background-header) / )", + base: "hsl(var(--color-background-base) / )", + }, + heading: { + foreground: "hsl(var(--color-heading-foreground) / )", + background: "hsl(var(--color-heading-background) / )", + }, + pine: { + 50: "#F2FAF9", + 100: "#E7F9E4", + 200: "#bfe4e1", + 300: "#99d4cf", + 400: "#4db4aa", + 500: "#009486", + 600: "#008579", + 700: "#006D60", + 800: "#00564A", + 900: "#003D0B", + }, + }, + gridTemplateColumns: { + admin: "300px calc(100% - 300px)", + podcast: "1fr minmax(auto, 960px) 1fr", + podcastMain: "1fr minmax(200px, 300px)", + cards: "repeat(auto-fill, minmax(14rem, 1fr))", + latestEpisodes: "repeat(5, 1fr)", + colorButtons: "repeat(auto-fill, minmax(4rem, 1fr))", + platforms: "repeat(auto-fill, minmax(18rem, 1fr))", + plugins: "repeat(auto-fill, minmax(20rem, 1fr))", + radioGroup: "repeat(auto-fit, minmax(14rem, 1fr))", + }, + gridTemplateRows: { + admin: "40px 1fr", + }, + borderWidth: { + 3: "3px", + }, + ringWidth: { + 3: "3px", + }, + typography: { + DEFAULT: { + css: { + a: { + textDecoration: "underline", + fontWeight: 600, + "&:hover": { + textDecoration: "none", + }, + }, + input: { + margin: 0, + }, + }, + }, + sm: { + css: { + a: { + textDecoration: "underline", + fontWeight: 600, + "&:hover": { + textDecoration: "none", + }, + }, + }, + }, + }, + zIndex: { + 60: 60, + }, + keyframes: { + "slight-pulse": { + "0%": { transform: "scale(1)" }, + "60%": { transform: "scale(0.96)" }, + "75%": { transform: "scale(1.05)" }, + "95%": { transform: "scale(0.98)" }, + "100%": { transform: "scale(1)" }, + }, + }, + animation: { + "single-pulse": "slight-pulse 300ms linear 1", + }, + }, + }, + variants: {}, + plugins: [tailwindForms, tailwindTypography], +}; diff --git a/tailwind.config.cjs b/tailwind.config.js similarity index 90% rename from tailwind.config.cjs rename to tailwind.config.js index 6c401d7a..42ba73eb 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.js @@ -1,12 +1,13 @@ -/* eslint-disable */ -const defaultTheme = require("tailwindcss/defaultTheme"); +import defaultTheme from "tailwindcss/defaultTheme"; +import tailwindForms from "@tailwindcss/forms"; +import tailwindTypography from "@tailwindcss/typography"; /** @type {import('tailwindcss').Config} */ -module.exports = { +export default { content: [ "./app/Views/**/*.php", "./modules/**/Views/**/*.php", - "./themes/**/*.php", + "./themes/cp_app/**/*.php", "./app/Helpers/*.php", "./resources/**/*.ts", ], @@ -76,7 +77,10 @@ module.exports = { }, }, colors: { - accent: "hsl(var(--color-accent-base) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, background: { header: "hsl(var(--color-background-header) / )", base: "hsl(var(--color-background-base) / )", @@ -97,18 +101,6 @@ module.exports = { 800: "#00564A", 900: "#003D0B", }, - rose: { - 50: "#fcf9f8", - 100: "#fdeef2", - 200: "#fbcfe4", - 300: "#faa7cd", - 400: "#fb6ea5", - 500: "#fc437c", - 600: "#f24664", - 700: "#dd1f47", - 800: "#b21a39", - 900: "#8e162e", - }, }, gridTemplateColumns: { admin: "300px calc(100% - 300px)", @@ -118,6 +110,8 @@ module.exports = { latestEpisodes: "repeat(5, 1fr)", colorButtons: "repeat(auto-fill, minmax(4rem, 1fr))", platforms: "repeat(auto-fill, minmax(18rem, 1fr))", + plugins: "repeat(auto-fill, minmax(20rem, 1fr))", + radioGroup: "repeat(auto-fit, minmax(14rem, 1fr))", }, gridTemplateRows: { admin: "40px 1fr", @@ -138,6 +132,9 @@ module.exports = { textDecoration: "none", }, }, + input: { + margin: 0, + }, }, }, sm: { @@ -158,5 +155,5 @@ module.exports = { }, }, variants: {}, - plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")], + plugins: [tailwindForms, tailwindTypography], }; diff --git a/tailwind.install.config.js b/tailwind.install.config.js new file mode 100644 index 00000000..3313dcee --- /dev/null +++ b/tailwind.install.config.js @@ -0,0 +1,106 @@ +import defaultTheme from "tailwindcss/defaultTheme"; +import tailwindForms from "@tailwindcss/forms"; + +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./app/Views/**/*.php", + "./themes/cp_install/**/*.php", + "./resources/**/*.ts", + ], + theme: { + extend: { + fontFamily: { + sans: ["Inter", ...defaultTheme.fontFamily.sans], + display: ["Kumbh Sans", ...defaultTheme.fontFamily.sans], + mono: ["Noto Sans Mono", ...defaultTheme.fontFamily.mono], + }, + textDecorationThickness: { + 3: "3px", + }, + textColor: { + skin: { + base: "hsl(var(--color-text-base) / )", + muted: "hsl(var(--color-text-muted) / )", + }, + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + muted: "hsl(var(--color-accent-muted) / )", + contrast: "hsl(var(--color-accent-contrast) / )", + }, + }, + backgroundColor: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + subtle: "hsl(var(--color-border-subtle) / )", + navigation: "hsl(var(--color-background-navigation) / )", + "navigation-active": + "hsl(var(--color-background-navigation-active) / )", + backdrop: "hsl(var(--color-background-backdrop) / )", + header: "hsl(var(--color-background-header) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + highlight: "hsl(var(--color-background-highlight) / )", + }, + borderColor: { + subtle: "hsl(var(--color-border-subtle) / )", + contrast: "hsl(var(--color-border-contrast) / )", + navigation: "hsl(var(--color-border-navigation) / )", + "navigation-bg": + "hsl(var(--color-background-navigation) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + background: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + }, + }, + ringColor: { + contrast: "hsl(var(--color-border-contrast) / )", + background: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + }, + }, + colors: { + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + background: { + header: "hsl(var(--color-background-header) / )", + base: "hsl(var(--color-background-base) / )", + }, + heading: { + foreground: "hsl(var(--color-heading-foreground) / )", + background: "hsl(var(--color-heading-background) / )", + }, + pine: { + 50: "#F2FAF9", + 100: "#E7F9E4", + 200: "#bfe4e1", + 300: "#99d4cf", + 400: "#4db4aa", + 500: "#009486", + 600: "#008579", + 700: "#006D60", + 800: "#00564A", + 900: "#003D0B", + }, + }, + borderWidth: { + 3: "3px", + }, + ringWidth: { + 3: "3px", + }, + }, + }, + variants: {}, + plugins: [tailwindForms], +}; diff --git a/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php b/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php index 9609c8c0..faf98ca8 100644 --- a/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php +++ b/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Tests\Support\Database\Migrations; use CodeIgniter\Database\Migration; +use Override; class ExampleMigration extends Migration { @@ -13,6 +14,7 @@ class ExampleMigration extends Migration */ protected $DBGroup = 'tests'; + #[Override] public function up(): void { $fields = [ @@ -61,6 +63,7 @@ class ExampleMigration extends Migration $this->forge->createTable('factories'); } + #[Override] public function down(): void { $this->forge->dropTable('factories'); diff --git a/tests/_support/Database/Seeds/ExampleSeeder.php b/tests/_support/Database/Seeds/ExampleSeeder.php index ddb0a6b5..52a4bed6 100644 --- a/tests/_support/Database/Seeds/ExampleSeeder.php +++ b/tests/_support/Database/Seeds/ExampleSeeder.php @@ -5,9 +5,11 @@ declare(strict_types=1); namespace Tests\Support\Database\Seeds; use CodeIgniter\Database\Seeder; +use Override; class ExampleSeeder extends Seeder { + #[Override] public function run(): void { $factories = [ diff --git a/app/Database/Seeds/FakeSinglePodcastApiSeeder.php b/tests/_support/Database/Seeds/FakeSinglePodcastApiSeeder.php similarity index 76% rename from app/Database/Seeds/FakeSinglePodcastApiSeeder.php rename to tests/_support/Database/Seeds/FakeSinglePodcastApiSeeder.php index e07bf66b..3fbd17c4 100644 --- a/app/Database/Seeds/FakeSinglePodcastApiSeeder.php +++ b/tests/_support/Database/Seeds/FakeSinglePodcastApiSeeder.php @@ -2,9 +2,12 @@ declare(strict_types=1); -namespace App\Database\Seeds; +namespace Tests\Support\Database\Seeds; +use App\Database\Seeds\AppSeeder; +use App\Database\Seeds\DevSeeder; use CodeIgniter\Database\Seeder; +use Override; class FakeSinglePodcastApiSeeder extends Seeder { @@ -101,53 +104,46 @@ class FakeSinglePodcastApiSeeder extends Seeder } /** - * @return array{id: int, guid: string, actor_id: int, handle: string, title: string, description_markdown: string, description_html: string, cover_id: int, banner_id: int, language_code: string, category_id: int, parental_advisory: null, owner_name: string, owner_email: string, publisher: string, type: string, copyright: string, episode_description_footer_markdown: null, episode_description_footer_html: null, is_blocked: int, is_completed: int, is_locked: int, imported_feed_url: null, new_feed_url: null, payment_pointer: null, location_name: null, location_geo: null, location_osm: null, custom_rss: null, is_published_on_hubs: int, partner_id: null, partner_link_url: null, partner_image_url: null, created_by: int, updated_by: int, created_at: string, updated_at: string} + * @return array{id: int, guid: string, actor_id: int, handle: string, title: string, description_markdown: string, description_html: string, cover_id: int, banner_id: int, language_code: string, category_id: int, parental_advisory: null, owner_name: string, owner_email: string, publisher: string, type: string, copyright: string, is_blocked: int, is_completed: int, is_locked: int, imported_feed_url: null, new_feed_url: null, location_name: null, location_geo: null, location_osm: null, is_published_on_hubs: int, created_by: int, updated_by: int, created_at: string, updated_at: string} */ public static function podcast(): array { return [ - 'id' => 1, - 'guid' => '0d341200-0234-5de7-99a6-a7d02bea4ce2', - 'actor_id' => 1, - 'handle' => 'Handle', - 'title' => 'Title', - 'description_markdown' => 'description', - 'description_html' => '

description

', - 'cover_id' => 1, - 'banner_id' => 2, - 'language_code' => 'en', - 'category_id' => 1, - 'parental_advisory' => null, - 'owner_name' => 'Owner', - 'owner_email' => 'Owner@gmail.com', - 'publisher' => '', - 'type' => 'episodic', - 'copyright' => '', - 'episode_description_footer_markdown' => null, - 'episode_description_footer_html' => null, - 'is_blocked' => 0, - 'is_completed' => 0, - 'is_locked' => 1, - 'imported_feed_url' => null, - 'new_feed_url' => null, - 'payment_pointer' => null, - 'location_name' => null, - 'location_geo' => null, - 'location_osm' => null, - 'custom_rss' => null, - 'is_published_on_hubs' => 0, - 'partner_id' => null, - 'partner_link_url' => null, - 'partner_image_url' => null, - 'created_by' => 1, - 'updated_by' => 1, - 'created_at' => '2022-06-13 8:00:00', - 'updated_at' => '2022-06-13 8:00:00', + 'id' => 1, + 'guid' => '0d341200-0234-5de7-99a6-a7d02bea4ce2', + 'actor_id' => 1, + 'handle' => 'Handle', + 'title' => 'Title', + 'description_markdown' => 'description', + 'description_html' => '

description

', + 'cover_id' => 1, + 'banner_id' => 2, + 'language_code' => 'en', + 'category_id' => 1, + 'parental_advisory' => null, + 'owner_name' => 'Owner', + 'owner_email' => 'Owner@gmail.com', + 'publisher' => '', + 'type' => 'episodic', + 'copyright' => '', + 'is_blocked' => 0, + 'is_completed' => 0, + 'is_locked' => 1, + 'imported_feed_url' => null, + 'new_feed_url' => null, + 'location_name' => null, + 'location_geo' => null, + 'location_osm' => null, + 'is_published_on_hubs' => 0, + 'created_by' => 1, + 'updated_by' => 1, + 'created_at' => '2022-06-13 8:00:00', + 'updated_at' => '2022-06-13 8:00:00', ]; } /** - * @return array{id: int, podcast_id: int, guid: string, title: string, slug: string, audio_id: int, description_markdown: string, description_html: string, cover_id: int, transcript_id: null, transcript_remote_url: null, chapters_id: null, chapters_remote_url: null, parental_advisory: null, number: int, season_number: null, type: string, is_blocked: false, location_name: null, location_geo: null, location_osm: null, custom_rss: null, is_published_on_hubs: false, posts_count: int, comments_count: int, is_premium: false, created_by: int, updated_by: int, published_at: null, created_at: string, updated_at: string} + * @return array{id:int,podcast_id:int,guid:string,title:string,slug:string,audio_id:int,description_markdown:string,description_html:string,cover_id:int,transcript_id:null,transcript_remote_url:null,chapters_id:null,chapters_remote_url:null,parental_advisory:null,number:int,season_number:null,type:string,is_blocked:false,location_name:null,location_geo:null,location_osm:null,is_published_on_hubs:false,posts_count:int,comments_count:int,is_premium:false,created_by:int,updated_by:int,published_at:null,created_at:string,updated_at:string} */ public static function episode(): array { @@ -173,7 +169,6 @@ class FakeSinglePodcastApiSeeder extends Seeder 'location_name' => null, 'location_geo' => null, 'location_osm' => null, - 'custom_rss' => null, 'is_published_on_hubs' => false, 'posts_count' => 0, 'comments_count' => 0, @@ -186,6 +181,7 @@ class FakeSinglePodcastApiSeeder extends Seeder ]; } + #[Override] public function run(): void { $this->call(AppSeeder::class); diff --git a/tests/_support/Models/ExampleModel.php b/tests/_support/Models/ExampleModel.php index 0349c6b6..55d0e18c 100644 --- a/tests/_support/Models/ExampleModel.php +++ b/tests/_support/Models/ExampleModel.php @@ -19,7 +19,7 @@ class ExampleModel extends Model protected $primaryKey = 'id'; /** - * @var string + * @var 'object' */ protected $returnType = 'object'; diff --git a/tests/modules/Api/Rest/V1/EpisodeTest.php b/tests/modules/Api/Rest/V1/EpisodeTest.php index ff366a4d..990652f7 100644 --- a/tests/modules/Api/Rest/V1/EpisodeTest.php +++ b/tests/modules/Api/Rest/V1/EpisodeTest.php @@ -2,13 +2,14 @@ declare(strict_types=1); -namespace modules\Api\Rest\V1; +namespace Tests\Modules\Api\Rest\V1; -use App\Database\Seeds\FakeSinglePodcastApiSeeder; use CodeIgniter\Database\Seeder; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use CodeIgniter\Test\FeatureTestTrait; +use Override; +use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder; class EpisodeTest extends CIUnitTestCase { @@ -45,11 +46,12 @@ class EpisodeTest extends CIUnitTestCase */ private array $episode = []; - private readonly string $apiUrl; + private string $apiUrl = ''; - public function __construct(?string $name = null) + #[Override] + protected function setUp(): void { - parent::__construct($name); + parent::setUp(); $this->episode = FakeSinglePodcastApiSeeder::episode(); @@ -59,6 +61,15 @@ class EpisodeTest extends CIUnitTestCase ->gateway; } + #[Override] + protected function tearDown(): void + { + parent::tearDown(); + + restore_error_handler(); + restore_exception_handler(); + } + public function testList(): void { $result = $this->call('get', $this->apiUrl . 'episodes'); @@ -88,7 +99,7 @@ class EpisodeTest extends CIUnitTestCase 'messages' => [ 'error' => 'Episode not found', ], - ] + ], ); $result->assertHeader('Content-Type', 'application/json; charset=UTF-8'); } diff --git a/tests/modules/Api/Rest/V1/PodcastTest.php b/tests/modules/Api/Rest/V1/PodcastTest.php index a70e5544..cdf5a446 100644 --- a/tests/modules/Api/Rest/V1/PodcastTest.php +++ b/tests/modules/Api/Rest/V1/PodcastTest.php @@ -2,13 +2,14 @@ declare(strict_types=1); -namespace modules\Api\Rest\V1; +namespace Tests\Modules\Api\Rest\V1; -use App\Database\Seeds\FakeSinglePodcastApiSeeder; use CodeIgniter\Database\Seeder; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use CodeIgniter\Test\FeatureTestTrait; +use Override; +use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder; class PodcastTest extends CIUnitTestCase { @@ -45,11 +46,13 @@ class PodcastTest extends CIUnitTestCase */ private array $podcast = []; - private readonly string $podcastApiUrl; + private string $podcastApiUrl = ''; - public function __construct(?string $name = null) + #[Override] + protected function setUp(): void { - parent::__construct($name); + parent::setUp(); + $this->podcast = FakeSinglePodcastApiSeeder::podcast(); $this->podcast['created_at'] = []; $this->podcast['updated_at'] = []; @@ -57,6 +60,15 @@ class PodcastTest extends CIUnitTestCase ->gateway; } + #[Override] + protected function tearDown(): void + { + parent::tearDown(); + + restore_error_handler(); + restore_exception_handler(); + } + public function testList(): void { $result = $this->call('get', $this->podcastApiUrl . 'podcasts'); @@ -86,7 +98,7 @@ class PodcastTest extends CIUnitTestCase 'messages' => [ 'error' => 'Podcast not found', ], - ] + ], ); $result->assertHeader('Content-Type', 'application/json; charset=UTF-8'); } diff --git a/tests/modules/Plugins/ManifestTest.php b/tests/modules/Plugins/ManifestTest.php new file mode 100644 index 00000000..c5557e9b --- /dev/null +++ b/tests/modules/Plugins/ManifestTest.php @@ -0,0 +1,67 @@ +assertNotEquals($manifest->name, 'acme/hello-world'); + $this->assertNotEquals($manifest->version, '1.0.0'); + + $manifest->loadFromFile(TESTPATH . 'modules/Plugins/mocks/manifests/manifest-required.json'); + + // no errors + $this->assertEmpty($manifest->getPluginErrors('acme/hello-world')); + + // properties have been set + $this->assertEquals($manifest->name, 'acme/hello-world'); + $this->assertEquals($manifest->version, '1.0.0'); + } + + public function testLoadEmptyData(): void + { + $manifest = new Manifest('acme/hello-world'); + + $manifest->loadFromFile(TESTPATH . 'modules/Plugins/mocks/manifests/manifest-empty.json'); + + $errors = $manifest->getPluginErrors('acme/hello-world'); + + $this->assertCount(2, $errors); + + // missing required name and version + $this->assertArrayHasKey('name', $errors); + $this->assertArrayHasKey('version', $errors); + } + + public function testLoadValidData(): void + { + $manifest = new Manifest('acme/hello-world'); + + $manifest->loadFromFile(TESTPATH . 'modules/Plugins/mocks/manifests/manifest-full-valid.json'); + + // no errors + $this->assertEmpty($manifest->getPluginErrors('acme/hello-world')); + } + + public function testLoadInvalidData(): void + { + $manifest = new Manifest('acme/hello-world'); + + $manifest->loadFromFile(TESTPATH . 'modules/Plugins/mocks/manifests/manifest-full-invalid.json'); + + // errors + $this->assertNotEmpty($manifest->getPluginErrors('acme/hello-world')); + } +} diff --git a/tests/modules/Plugins/PluginsTest.php b/tests/modules/Plugins/PluginsTest.php new file mode 100644 index 00000000..4594bbbe --- /dev/null +++ b/tests/modules/Plugins/PluginsTest.php @@ -0,0 +1,190 @@ +folder = __DIR__ . '/mocks/plugins' . DIRECTORY_SEPARATOR; + + self::$plugins = new Plugins($pluginsConfig); + } + + public function testRegister(): void + { + $this->assertCount(7, self::$plugins->getAllPlugins()); + $this->assertEquals(7, self::$plugins->getInstalledCount()); + $this->assertEquals(0, self::$plugins->getActiveCount()); + } + + public function testActivateDeactivate(): void + { + $this->assertEquals(0, self::$plugins->getActiveCount()); + + $plugin = self::$plugins->getAllPlugins()[0]; + + // get first plugin and activate it + self::$plugins->activate($plugin); + + $this->assertEquals(1, self::$plugins->getActiveCount()); + $this->assertEquals(PluginStatus::ACTIVE, $plugin->getStatus()); + $this->seeInDatabase('settings', [ + 'class' => PluginsConfig::class, + 'key' => 'active', + 'value' => '1', + 'type' => 'boolean', + 'context' => 'plugin:' . $plugin->getKey(), + ]); + + // get first plugin and deactivate it + self::$plugins->deactivate($plugin); + + $this->assertEquals(0, self::$plugins->getActiveCount()); + $this->assertEquals(PluginStatus::INACTIVE, $plugin->getStatus()); + $this->seeInDatabase('settings', [ + 'class' => PluginsConfig::class, + 'key' => 'active', + 'value' => '0', + 'type' => 'boolean', + 'context' => 'plugin:' . $plugin->getKey(), + ]); + } + + public function testRunHooksActive(): void + { + $acmeAllHooksPlugin = self::$plugins->getPlugin('acme', 'all-hooks'); + + self::$plugins->activate($acmeAllHooksPlugin); + + $this->assertEquals(1, self::$plugins->getActiveCount()); + + $podcast = new Podcast(); + $this->assertEquals('', $podcast->title); + self::$plugins->runHook('rssBeforeChannel', [$podcast]); + $this->assertEquals('Podcast test', $podcast->title); + + $channel = new RssFeed(''); + $this->assertTrue(empty($channel->foo)); + self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]); + $this->assertFalse(empty($channel->foo)); + + $episode = new Episode(); + $this->assertEquals('', $episode->title); + self::$plugins->runHook('rssBeforeItem', [$episode]); + $this->assertEquals('Episode test', $episode->title); + + $item = new RssFeed(''); + $this->assertTrue(empty($item->efoo)); + self::$plugins->runHook('rssAfterItem', [$episode, $item]); + $this->assertFalse(empty($item->efoo)); + + $head = new HtmlHead(); + self::$plugins->runHook('siteHead', [$head]); + + $this->assertEquals( + (string) $head, + ' foo foo ', + ); + } + + public function testRunHooksInactive(): void + { + $acmeAllHooksPlugin = self::$plugins->getPlugin('acme', 'all-hooks'); + + self::$plugins->deactivate($acmeAllHooksPlugin); + + $this->assertEquals(0, self::$plugins->getActiveCount()); + + // nothing should change when running hooks as the plugin is inactive + + $podcast = new Podcast(); + $this->assertEquals('', $podcast->title); + self::$plugins->runHook('rssBeforeChannel', [$podcast]); + $this->assertEquals('', $podcast->title); + + $channel = new RssFeed(''); + $this->assertTrue(empty($channel->foo)); + self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]); + $this->assertTrue(empty($channel->foo)); + + $episode = new Episode(); + $this->assertEquals('', $episode->title); + self::$plugins->runHook('rssBeforeItem', [$episode]); + $this->assertEquals('', $episode->title); + + $item = new RssFeed(''); + $this->assertTrue(empty($item->efoo)); + self::$plugins->runHook('rssAfterItem', [$episode, $item]); + $this->assertTrue(empty($item->efoo)); + + ob_start(); + self::$plugins->runHook('siteHead', []); + $result = ob_get_contents(); + ob_end_clean(); //Discard output buffer + $this->assertEquals('', $result); + } + + public function testRunUndeclaredHook(): void + { + $acmeUndeclaredHookPlugin = self::$plugins->getPlugin('acme', 'undeclared-hook'); + + self::$plugins->activate($acmeUndeclaredHookPlugin); + + $podcast = new Podcast(); + $this->assertEquals('', $podcast->title); + self::$plugins->runHook('rssBeforeChannel', [$podcast]); + $this->assertEquals('Podcast test undeclared', $podcast->title); + + // rssAfterChannel has not been declared in plugin manifest, should not be running + $channel = new RssFeed(''); + $this->assertTrue(empty($channel->foo)); + self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]); + $this->assertTrue(empty($channel->foo)); + } +} diff --git a/tests/modules/Plugins/mocks/manifests/manifest-empty.json b/tests/modules/Plugins/mocks/manifests/manifest-empty.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/modules/Plugins/mocks/manifests/manifest-empty.json @@ -0,0 +1 @@ +{} diff --git a/tests/modules/Plugins/mocks/manifests/manifest-full-invalid.json b/tests/modules/Plugins/mocks/manifests/manifest-full-invalid.json new file mode 100644 index 00000000..e13a5bc0 --- /dev/null +++ b/tests/modules/Plugins/mocks/manifests/manifest-full-invalid.json @@ -0,0 +1,120 @@ +{ + "name": "acme/hello-world", + "description": true, + "version": "1.0.0", + "authors": [ + { + "name": "Acme Corporation", + "email": "acme@example.com", + "url": "https://example.com/" + } + ], + "homepage": "https://example.com/", + "license": ["MIT", "AGPLv3"], + "keywords": ["seo", "analytics"], + "hooks": ["rssAfterChannel"], + "settings": { + "general": { + "name": { + "type": "radio-group", + "label": "Name", + "options": { + "foo": { "label": "Foo", "hint": "This is a hint." }, + "bar": { "label": "Bar" } + } + }, + "email": { + "type": "email", + "label": "Email" + }, + "url": { + "type": "url", + "label": "Your website URL" + }, + "toggler": { + "type": "toggler", + "label": "Toggle this?" + }, + "number": { + "type": "number", + "label": "Number" + }, + "datetime": { + "type": "datetime", + "label": "Enter a date", + "optional": true + }, + "select": { + "type": "select", + "label": "Select something", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "select-multiple": { + "type": "select-multiple", + "label": "Select multiple things", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "radio-group": { + "type": "radio-group", + "label": "Radio Group", + "helper": "This is a helper.", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "texting": { + "type": "textarea", + "label": "Your text", + "hint": "This is a hint." + }, + "hello": { + "type": "markdown", + "label": "Name Podcast", + "hint": "This is a hint.", + "optional": true + } + }, + "podcast": { + "name": { + "type": "text", + "label": "Name Podcast", + "hint": "This is a hint." + } + }, + "episode": { + "name": { + "type": "text", + "label": "Name Episode", + "helper": "This is a helper." + } + } + } +} diff --git a/tests/modules/Plugins/mocks/manifests/manifest-full-valid.json b/tests/modules/Plugins/mocks/manifests/manifest-full-valid.json new file mode 100644 index 00000000..f8223ba3 --- /dev/null +++ b/tests/modules/Plugins/mocks/manifests/manifest-full-valid.json @@ -0,0 +1,121 @@ +{ + "name": "acme/hello-world", + "description": "A Castopod plugin to add a hello world greeting to your RSS feed!", + "version": "1.0.0", + "authors": [ + { + "name": "Acme Corporation", + "email": "acme@example.com", + "url": "https://example.com/" + } + ], + "homepage": "https://example.com/", + "license": "MIT", + "keywords": ["seo", "analytics"], + "hooks": ["rssAfterChannel"], + "settings": { + "general": { + "name": { + "type": "radio-group", + "label": "Name", + "options": { + "foo": { "label": "Foo", "hint": "This is a hint." }, + "bar": { "label": "Bar" }, + "baz": { "label": "Baz" } + } + }, + "email": { + "type": "email", + "label": "Email" + }, + "url": { + "type": "url", + "label": "Your website URL" + }, + "toggler": { + "type": "toggler", + "label": "Toggle this?" + }, + "number": { + "type": "number", + "label": "Number" + }, + "datetime": { + "type": "datetime", + "label": "Enter a date", + "optional": true + }, + "select": { + "type": "select", + "label": "Select something", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "select-multiple": { + "type": "select-multiple", + "label": "Select multiple things", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "radio-group": { + "type": "radio-group", + "label": "Radio Group", + "helper": "This is a helper.", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "textarea": { + "type": "textarea", + "label": "Your text", + "hint": "This is a hint." + }, + "markdown": { + "type": "markdown", + "label": "Markdown", + "hint": "This is a hint.", + "optional": true + } + }, + "podcast": { + "name": { + "type": "text", + "label": "Name Podcast", + "hint": "This is a hint." + } + }, + "episode": { + "name": { + "type": "text", + "label": "Name Episode", + "helper": "This is a helper." + } + } + } +} diff --git a/tests/modules/Plugins/mocks/manifests/manifest-required.json b/tests/modules/Plugins/mocks/manifests/manifest-required.json new file mode 100644 index 00000000..5272f045 --- /dev/null +++ b/tests/modules/Plugins/mocks/manifests/manifest-required.json @@ -0,0 +1,4 @@ +{ + "name": "acme/hello-world", + "version": "1.0.0" +} diff --git a/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php new file mode 100644 index 00000000..53c93cdc --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php @@ -0,0 +1,42 @@ +title = 'Podcast test'; + } + + #[Override] + public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void + { + $channel->addChild('foo', 'bar'); + } + + #[Override] + public function rssBeforeItem(Episode $episode): void + { + $episode->title = 'Episode test'; + } + + #[Override] + public function rssAfterItem(Episode $episode, RssFeed $item): void + { + $item->addChild('efoo', 'ebar'); + } + + #[Override] + public function siteHead(HtmlHead $head): void + { + $head->tag('title', 'foo'); + } +} diff --git a/tests/modules/Plugins/mocks/plugins/acme/all-hooks/manifest.json b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/manifest.json new file mode 100644 index 00000000..b564821b --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "acme/all-hooks", + "version": "1.0.0", + "hooks": [ + "rssBeforeChannel", + "rssAfterChannel", + "rssBeforeItem", + "rssAfterItem", + "siteHead" + ] +} diff --git a/tests/modules/Plugins/mocks/plugins/acme/empty-manifest/Plugin.php b/tests/modules/Plugins/mocks/plugins/acme/empty-manifest/Plugin.php new file mode 100644 index 00000000..4aaf8422 --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/acme/empty-manifest/Plugin.php @@ -0,0 +1,9 @@ +title = 'Podcast test undeclared'; + } + + #[Override] + public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void + { + $channel->addChild('foo', 'bar'); + } +} diff --git a/tests/modules/Plugins/mocks/plugins/acme/undeclared-hook/manifest.json b/tests/modules/Plugins/mocks/plugins/acme/undeclared-hook/manifest.json new file mode 100644 index 00000000..af9293ae --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/acme/undeclared-hook/manifest.json @@ -0,0 +1,5 @@ +{ + "name": "acme/all-hooks", + "version": "1.0.0", + "hooks": ["rssBeforeChannel"] +} diff --git a/tests/modules/Plugins/mocks/plugins/atlantis/empty/.gitkeep b/tests/modules/Plugins/mocks/plugins/atlantis/empty/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/Plugins/mocks/plugins/atlantis/hello-broken/Plugin.php b/tests/modules/Plugins/mocks/plugins/atlantis/hello-broken/Plugin.php new file mode 100644 index 00000000..8309b442 --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/atlantis/hello-broken/Plugin.php @@ -0,0 +1,9 @@ +getLocale() ?>"> - - - - - <?= $this->renderSection('title') ?> | Castopod Admin - - - - - - - ' /> - asset('styles/index.css') ?> - asset('js/admin.ts') ?> - asset('js/admin-audio-player.ts') ?> - + include('_partials/_nav_header') ?> @@ -36,15 +18,15 @@ $isEpisodeArea = isset($podcast) && isset($episode);
-
+
is_premium) || ($isPodcastArea && $podcast->is_premium)): ?>
- is_premium) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?> - renderSection('pageTitle') ?> + is_premium) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?> + renderSection('pageTitle') ?>
- renderSection('pageTitle') ?> + renderSection('pageTitle') ?> renderSection('headerLeft') ?>
diff --git a/themes/cp_admin/_message_block.php b/themes/cp_admin/_message_block.php index 5f1ba623..42a34097 100644 --- a/themes/cp_admin/_message_block.php +++ b/themes/cp_admin/_message_block.php @@ -1,33 +1,33 @@ has('message')): ?> - + has('error')): ?> - + has('errors')): ?> - +
-
+ has('warning')): ?> - + has('warnings')): ?> - +
-
+ diff --git a/themes/cp_admin/_partials/_nav_aside.php b/themes/cp_admin/_partials/_nav_aside.php index 88773cae..0f2867b1 100644 --- a/themes/cp_admin/_partials/_nav_aside.php +++ b/themes/cp_admin/_partials/_nav_aside.php @@ -15,7 +15,7 @@ $isEpisodeArea = isset($podcast) && isset($episode);
@@ -215,8 +216,7 @@ (' . lang('Common.optional') . - ')' . - hint_tooltip(lang('Episode.form.chapters_hint'), 'ml-1') ?> + ')' ?>
chapters_remote_url ? '' : 'checked' ?> /> @@ -248,7 +248,7 @@ 'class' => 'mx-auto', ]), [ - 'class' => 'text-sm p-1 bg-red-100 rounded-full text-red-700 hover:text-red-900 focus:ring-accent', + 'class' => 'text-sm p-1 bg-red-100 rounded-full text-red-700 hover:text-red-900', 'data-tooltip' => 'bottom', 'title' => lang( 'Episode.form.chapters_file_delete', @@ -257,42 +257,35 @@ ) ?>
- - + +
- - + +
- + - - - + - + published_at === null): ?> - + - + diff --git a/themes/cp_admin/episode/embed.php b/themes/cp_admin/episode/embed.php index 483339bd..d64e65e4 100644 --- a/themes/cp_admin/episode/embed.php +++ b/themes/cp_admin/episode/embed.php @@ -6,10 +6,6 @@ $embedHeight = config('Embed')->height; extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -33,15 +29,15 @@ $embedHeight = config('Embed')->height;
- embed_url}\">") ?>" /> + embed_url}\">") ?>" /> - +
- + - +
endSection() ?> diff --git a/themes/cp_admin/episode/list.php b/themes/cp_admin/episode/list.php index 4fa16935..7c6f6e80 100644 --- a/themes/cp_admin/episode/list.php +++ b/themes/cp_admin/episode/list.php @@ -1,16 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> @@ -28,16 +24,16 @@

- + ]) ?>
@@ -88,7 +84,7 @@ return publication_pill( $episode->published_at, $episode->publication_status, - 'text-sm' + 'text-sm', ); }, ], @@ -160,16 +156,16 @@ HTML), ]; } - return '' . - ''; + ''; }, ], ], $episodes, 'mb-6 mt-4', - $podcast + $podcast, ) ?> links() ?> diff --git a/themes/cp_admin/episode/persons.php b/themes/cp_admin/episode/persons.php index 850a9522..0a182c70 100644 --- a/themes/cp_admin/episode/persons.php +++ b/themes/cp_admin/episode/persons.php @@ -1,16 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> (persons) ?>) endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> @@ -18,35 +14,35 @@
- - - - + - +
@@ -87,12 +83,12 @@ 'header' => lang('Common.actions'), 'cell' => function ($person): string { // @icon("delete-bin-fill") - return ''; + return '' . lang('Person.episode_form.remove') . ''; }, ], ], $episode->persons, - 'max-w-xl mt-6' + 'max-w-xl mt-6', ) ?> endSection() ?> diff --git a/themes/cp_admin/episode/publish.php b/themes/cp_admin/episode/publish.php index 88a88022..c06d60cc 100644 --- a/themes/cp_admin/episode/publish.php +++ b/themes/cp_admin/episode/publish.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -16,7 +12,7 @@ 'class' => 'mr-2 text-lg', ]) . lang('Episode.publish_form.back_to_episode_dashboard'), [ - 'class' => 'inline-flex items-center font-semibold mr-4 text-sm focus:ring-accent', + 'class' => 'inline-flex items-center font-semibold mr-4 text-sm', ], ) ?> @@ -39,7 +35,7 @@
- +
- +
/> - +
- - +
- - + +
diff --git a/themes/cp_admin/episode/publish_date_edit.php b/themes/cp_admin/episode/publish_date_edit.php index 83493448..281f9f32 100644 --- a/themes/cp_admin/episode/publish_date_edit.php +++ b/themes/cp_admin/episode/publish_date_edit.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -24,16 +20,16 @@ - - + diff --git a/themes/cp_admin/episode/publish_edit.php b/themes/cp_admin/episode/publish_edit.php index 31bbf3d4..0d9ccf49 100644 --- a/themes/cp_admin/episode/publish_edit.php +++ b/themes/cp_admin/episode/publish_edit.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -41,7 +37,7 @@
- +
- +
/> - +
- - +
- - + +
diff --git a/themes/cp_admin/episode/soundbites_list.php b/themes/cp_admin/episode/soundbites_list.php index db6ff25e..bff27821 100644 --- a/themes/cp_admin/episode/soundbites_list.php +++ b/themes/cp_admin/episode/soundbites_list.php @@ -1,16 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> @@ -26,10 +22,10 @@ [ 'header' => lang('Common.actions'), 'cell' => function ($soundbite): string { - return '' . - 'id . '-menu" labelledby="more-dropdown-' . $soundbite->id . '" offsetY="-24" items="' . esc(json_encode([ [ 'type' => 'link', 'title' => lang('Soundbite.delete'), diff --git a/themes/cp_admin/episode/soundbites_new.php b/themes/cp_admin/episode/soundbites_new.php index a96e58fd..a3114777 100644 --- a/themes/cp_admin/episode/soundbites_new.php +++ b/themes/cp_admin/episode/soundbites_new.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,10 +10,10 @@
- @@ -29,7 +25,7 @@ - + diff --git a/themes/cp_admin/episode/unpublish.php b/themes/cp_admin/episode/unpublish.php index 50b8fb14..5d6d0048 100644 --- a/themes/cp_admin/episode/unpublish.php +++ b/themes/cp_admin/episode/unpublish.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -13,13 +9,13 @@
- + - +
- - + +
diff --git a/themes/cp_admin/episode/video_clip.php b/themes/cp_admin/episode/video_clip.php index cc536f20..40dbebc6 100644 --- a/themes/cp_admin/episode/video_clip.php +++ b/themes/cp_admin/episode/video_clip.php @@ -1,11 +1,5 @@ extend('_layout') ?> -section('title') ?> - esc($videoClip->title), -]) ?> -endSection() ?> - section('pageTitle') ?> esc($videoClip->title), diff --git a/themes/cp_admin/episode/video_clips_list.php b/themes/cp_admin/episode/video_clips_list.php index c5cffa37..b6fa6958 100644 --- a/themes/cp_admin/episode/video_clips_list.php +++ b/themes/cp_admin/episode/video_clips_list.php @@ -6,17 +6,13 @@ use CodeIgniter\I18n\Time; ?> extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> @@ -52,7 +48,7 @@ use CodeIgniter\I18n\Time; 'passed' => '', ]; - return '' . lang('VideoClip.list.status.' . $videoClip->status) . ''; + return '' . lang('VideoClip.list.status.' . $videoClip->status) . ''; }, ], [ @@ -63,7 +59,7 @@ use CodeIgniter\I18n\Time; 'portrait' => 'aspect-[9/16]', 'squared' => 'aspect-square', ]; - return '
' . icon('play-fill') . '
#' . $videoClip->id . ' – ' . esc($videoClip->title) . 'by ' . esc($videoClip->user->username) . '
' . format_duration((int) $videoClip->duration) . '
'; + return '
' . icon('play-fill') . '
#' . $videoClip->id . ' – ' . esc($videoClip->title) . 'by ' . esc($videoClip->user->username) . '
' . format_duration((int) $videoClip->duration) . '
'; }, ], [ @@ -98,14 +94,14 @@ use CodeIgniter\I18n\Time; helper('misc'); $filename = 'clip-' . slugify($videoClip->title) . "-{$videoClip->start_time}-{$videoClip->end_time}"; // @icon("import-fill") - $downloadButton = '' . lang('VideoClip.download_clip') . ''; + $downloadButton = '' . lang('VideoClip.download_clip') . ''; } return '
' . $downloadButton . - '' . - 'id . '-menu" labelledby="more-dropdown-' . $videoClip->id . '" offsetY="-24" items="' . esc(json_encode([ [ 'type' => 'link', 'title' => lang('VideoClip.go_to_page'), @@ -131,7 +127,7 @@ use CodeIgniter\I18n\Time; ], ], $videoClips, - 'mb-6' + 'mb-6', ) ?> links() ?> diff --git a/themes/cp_admin/episode/video_clips_new.php b/themes/cp_admin/episode/video_clips_new.php index 332adf14..24f0e1e9 100644 --- a/themes/cp_admin/episode/video_clips_new.php +++ b/themes/cp_admin/episode/video_clips_new.php @@ -1,13 +1,5 @@ - - extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -31,48 +23,48 @@
- - +
- - + - + + isRequired="true" + hint="">
themes as $themeName => $colors): ?> - + isRequired="true" + isSelected="" + style="--color-accent-base: ; --color-background-preview: ">
-
+ - +
diff --git a/themes/cp_admin/episode/video_clips_requirements.php b/themes/cp_admin/episode/video_clips_requirements.php index 73d8d1a2..d1b41435 100644 --- a/themes/cp_admin/episode/video_clips_requirements.php +++ b/themes/cp_admin/episode/video_clips_requirements.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -12,9 +8,9 @@
- 'flex-shrink-0 text-xl text-orange-600', - ]) ?> + ]) ?>

$value): ?> diff --git a/themes/cp_admin/episode/view.php b/themes/cp_admin/episode/view.php index c637f9e0..c6392a36 100644 --- a/themes/cp_admin/episode/view.php +++ b/themes/cp_admin/episode/view.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> -title) ?> -endSection() ?> - section('pageTitle') ?> title) ?> endSection() ?> @@ -12,19 +8,19 @@ published_at, $episode->publication_status, - 'text-sm ml-2 align-middle', + 'text-sm align-middle', ) ?> endSection() ?> section('headerRight') ?> publication_status === 'published'): ?> - +> id, @@ -41,7 +37,7 @@
- " dataUrl="id, 'PodcastByEpisode', @@ -49,7 +45,7 @@ $episode->id, ) ?>"/> - " dataUrl="id, 'PodcastByEpisode', @@ -60,5 +56,5 @@ asset('js/charts.ts') ?> + ->asset('js/charts.ts', 'js') ?> endSection() ?> diff --git a/themes/cp_admin/fediverse/blocked_actors.php b/themes/cp_admin/fediverse/blocked_actors.php index 683ba5c7..7232de3d 100644 --- a/themes/cp_admin/fediverse/blocked_actors.php +++ b/themes/cp_admin/fediverse/blocked_actors.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,12 +10,12 @@
- - + isRequired="true" /> + id . '" />' . csrf_field() . - '' . + '' . lang('Fediverse.list.unblock') . '' . ''; }, ], ], $blockedActors, - 'mt-8' + 'mt-8', ) ?> diff --git a/themes/cp_admin/fediverse/blocked_domains.php b/themes/cp_admin/fediverse/blocked_domains.php index ec26fda7..51a2049b 100644 --- a/themes/cp_admin/fediverse/blocked_domains.php +++ b/themes/cp_admin/fediverse/blocked_domains.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,11 +10,11 @@
- - + isRequired="true" /> + name) . '" />' . csrf_field() . - '' . + '' . lang('Fediverse.list.unblock') . '' . ''; }, ], ], $blockedDomains, - 'mt-8' + 'mt-8', ) ?> endSection() ?> diff --git a/themes/cp_admin/import/_queue_table.php b/themes/cp_admin/import/_queue_table.php index 70a38b34..742fd10b 100644 --- a/themes/cp_admin/import/_queue_table.php +++ b/themes/cp_admin/import/_queue_table.php @@ -38,9 +38,9 @@ use Modules\PodcastImport\Entities\TaskStatus; 'passed' => '', ]; - $errorHint = $importTask->status === TaskStatus::Failed ? hint_tooltip(esc($importTask->error), 'ml-1') : ''; + $errorHint = $importTask->status === TaskStatus::Failed ? '' . esc($importTask->error) . '' : ''; - return '
' . lang('PodcastImport.queue.status.' . $importTask->status->value) . '' . $errorHint . '
'; + return '
' . lang('PodcastImport.queue.status.' . $importTask->status->value) . '' . $errorHint . '
'; }, ], [ @@ -86,10 +86,10 @@ use Modules\PodcastImport\Entities\TaskStatus; 'cell' => function (PodcastImportTask $importTask) { if ($importTask->episodes_count) { $progressPercentage = (int) ($importTask->getProgress() * 100) . '%'; - $moreInfoHelper = hint_tooltip(lang('PodcastImport.queue.imported_episodes_hint', [ + $moreInfoHelper = '' . lang('PodcastImport.queue.imported_episodes_hint', [ 'newlyImportedCount' => $importTask->episodes_newly_imported, 'alreadyImportedCount' => $importTask->episodes_already_imported, - ]), 'ml-1'); + ]) . ''; return << {$progressPercentage} @@ -134,13 +134,13 @@ use Modules\PodcastImport\Entities\TaskStatus; } return '
' . - '' . - '' . + '' . '
'; }, ], ], - $podcastImportsQueue + $podcastImportsQueue, ) ?> diff --git a/themes/cp_admin/import/add_to_queue.php b/themes/cp_admin/import/add_to_queue.php index 5ad4c377..9a706bd7 100644 --- a/themes/cp_admin/import/add_to_queue.php +++ b/themes/cp_admin/import/add_to_queue.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -13,51 +9,51 @@
- - - + - + isRequired="true" /> + -
- +
'absolute inset-0 h-full text-xl opacity-40 left-3', ]) ?> - +
- - -
+ - +
diff --git a/themes/cp_admin/import/podcast_queue.php b/themes/cp_admin/import/podcast_queue.php index 9be03c20..a1aa7b6e 100644 --- a/themes/cp_admin/import/podcast_queue.php +++ b/themes/cp_admin/import/podcast_queue.php @@ -1,16 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> diff --git a/themes/cp_admin/import/podcast_sync.php b/themes/cp_admin/import/podcast_sync.php index d71ad77d..5a57600e 100644 --- a/themes/cp_admin/import/podcast_sync.php +++ b/themes/cp_admin/import/podcast_sync.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -11,14 +7,14 @@ section('content') ?>
- - + endSection() ?> diff --git a/themes/cp_admin/import/queue.php b/themes/cp_admin/import/queue.php index ff32f738..06c96968 100644 --- a/themes/cp_admin/import/queue.php +++ b/themes/cp_admin/import/queue.php @@ -1,19 +1,12 @@ - extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> diff --git a/themes/cp_admin/my_account/change_password.php b/themes/cp_admin/my_account/change_password.php index f0731a9e..7c41193e 100644 --- a/themes/cp_admin/my_account/change_password.php +++ b/themes/cp_admin/my_account/change_password.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -13,18 +9,18 @@
- - - + endSection() ?> diff --git a/themes/cp_admin/my_account/view.php b/themes/cp_admin/my_account/view.php index 5d5c1101..46ebb1c8 100644 --- a/themes/cp_admin/my_account/view.php +++ b/themes/cp_admin/my_account/view.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> diff --git a/themes/cp_admin/page/create.php b/themes/cp_admin/page/create.php index f1e32d23..64789484 100644 --- a/themes/cp_admin/page/create.php +++ b/themes/cp_admin/page/create.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,29 +10,29 @@
- -
- - - …/pages/ - - -
+ - - + diff --git a/themes/cp_admin/page/edit.php b/themes/cp_admin/page/edit.php index 95c5941e..36b65daf 100644 --- a/themes/cp_admin/page/edit.php +++ b/themes/cp_admin/page/edit.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,32 +10,33 @@
- -
- - - …/pages/ - - -
+ - - + diff --git a/themes/cp_admin/page/list.php b/themes/cp_admin/page/list.php index 9a73b629..b03b5e20 100644 --- a/themes/cp_admin/page/list.php +++ b/themes/cp_admin/page/list.php @@ -1,16 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> () endSection() ?> section('headerRight') ?> - + endSection() ?> @@ -31,9 +27,9 @@ [ 'header' => lang('Common.actions'), 'cell' => function ($page) { - return '' . - '' . - ''; + return '' . lang('Page.go_to_page') . '' . + '' . lang('Page.edit') . '' . + '' . lang('Page.delete') . ''; }, ], ], diff --git a/themes/cp_admin/page/view.php b/themes/cp_admin/page/view.php index 22fc098d..74f001f1 100644 --- a/themes/cp_admin/page/view.php +++ b/themes/cp_admin/page/view.php @@ -1,16 +1,12 @@ extend('_layout') ?> -section('title') ?> -title) ?> -endSection() ?> - section('pageTitle') ?> title) ?> endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> diff --git a/themes/cp_admin/person/_card.php b/themes/cp_admin/person/_card.php index 7a73e7d4..b4506c8c 100644 --- a/themes/cp_admin/person/_card.php +++ b/themes/cp_admin/person/_card.php @@ -8,8 +8,8 @@

full_name) ?>

- -
- -
diff --git a/themes/cp_admin/podcast/_sidebar.php b/themes/cp_admin/podcast/_sidebar.php index 2c291a8a..a6967cb3 100644 --- a/themes/cp_admin/podcast/_sidebar.php +++ b/themes/cp_admin/podcast/_sidebar.php @@ -23,6 +23,12 @@ $podcastNavigation = [ 'count' => $podcast->getEpisodesCount(), 'count-route' => 'episode-list', ], + 'plugins' => [ + 'icon' => 'puzzle-fill', // @icon("puzzle-fill") + 'items' => [], + 'items-labels' => [], + 'items-permissions' => [], + ], 'analytics' => [ 'icon' => 'line-chart-fill', // @icon("line-chart-fill") 'items' => [ @@ -61,13 +67,11 @@ $podcastNavigation = [ 'subscription-list', 'subscription-create', 'platforms-funding', - 'podcast-monetization-other', ], 'items-permissions' => [ - 'subscription-list' => 'manage-subscriptions', - 'subscription-create' => 'manage-subscriptions', - 'platforms-funding' => 'manage-platforms', - 'podcast-monetization-other' => 'edit', + 'subscription-list' => 'manage-subscriptions', + 'subscription-create' => 'manage-subscriptions', + 'platforms-funding' => 'manage-platforms', ], ], 'contributors' => [ @@ -83,6 +87,13 @@ $podcastNavigation = [ ], ]; +foreach (plugins()->getPluginsWithPodcastSettings() as $plugin) { + $route = route_to('plugins-settings-podcast', $plugin->getVendor(), $plugin->getPackage(), $podcast->id); + $podcastNavigation['plugins']['items'][] = $route; + $podcastNavigation['plugins']['items-labels'][$route] = $plugin->getTitle(); + $podcastNavigation['plugins']['items-permissions'][$route] = 'edit'; +} + ?>
diff --git a/themes/cp_admin/podcast/analytics/index.php b/themes/cp_admin/podcast/analytics/index.php index 12ac9a4c..9407f828 100644 --- a/themes/cp_admin/podcast/analytics/index.php +++ b/themes/cp_admin/podcast/analytics/index.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> -title) ?> -endSection() ?> - section('pageTitle') ?> title) ?> endSection() ?> @@ -11,21 +7,21 @@ section('content') ?>
- - - - - - " dataUrl="id, 'PodcastByCountry', 'Weekly', ) ?>" /> - " dataUrl="id, 'PodcastByCountry', 'Yearly', ) ?>" /> - - " dataUrl="id, 'PodcastByPlayer', 'ByAppWeekly', ) ?>" /> - " dataUrl="id, 'PodcastByService', 'ByServiceWeekly', ) ?>" /> - " dataUrl="id, 'PodcastByPlayer', 'ByDeviceWeekly', ) ?>" /> - " dataUrl="id, 'PodcastByPlayer', 'ByOsWeekly', ) ?>" /> - - " dataUrl="id, 'Podcast', 'ByWeekday', ) ?>" /> - " dataUrl="id, 'PodcastByHour', @@ -25,5 +21,5 @@
asset('js/charts.ts') ?> + ->asset('js/charts.ts', 'js') ?> endSection() ?> diff --git a/themes/cp_admin/podcast/analytics/unique_listeners.php b/themes/cp_admin/podcast/analytics/unique_listeners.php index 6cc31aeb..935f1961 100644 --- a/themes/cp_admin/podcast/analytics/unique_listeners.php +++ b/themes/cp_admin/podcast/analytics/unique_listeners.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> -title) ?> -endSection() ?> - section('pageTitle') ?> title) ?> endSection() ?> @@ -11,14 +7,14 @@ section('content') ?>
- - - " dataUrl="id, 'WebsiteByReferer', 'ByDomainWeekly', ) ?>" /> - " dataUrl="id, 'WebsiteByReferer', 'ByDomainYearly', ) ?>" /> - " dataUrl="id, 'WebsiteByEntryPage', ) ?>" /> - " dataUrl="id, 'WebsiteByBrowser', @@ -38,5 +34,5 @@ asset('js/charts.ts') ?> + ->asset('js/charts.ts', 'js') ?> endSection() ?> diff --git a/themes/cp_admin/podcast/create.php b/themes/cp_admin/podcast/create.php index 3e33bc5a..e27e2700 100644 --- a/themes/cp_admin/podcast/create.php +++ b/themes/cp_admin/podcast/create.php @@ -1,13 +1,5 @@ - - extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -17,222 +9,178 @@
- - - + isRequired="true" /> - -
- -
- - -
-
-
- -
- - - -
-
-
+ + - - - - -
- -
- - - -
-
-
+ + - - + isRequired="true" /> - + isRequired="true" /> - - - - - - + -
- +
'absolute inset-0 h-full text-xl opacity-40 left-3', ]) ?> - +
- -
+ - - - - + + + + - - - 'text-sm', - ]) ?>op3.dev - - - - - - + - - - - - - + - - + + - - + + - + - + - + diff --git a/themes/cp_admin/podcast/delete.php b/themes/cp_admin/podcast/delete.php index 83576b90..9984c0c5 100644 --- a/themes/cp_admin/podcast/delete.php +++ b/themes/cp_admin/podcast/delete.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -13,13 +9,13 @@
- + - +
- - + +
diff --git a/themes/cp_admin/podcast/edit.php b/themes/cp_admin/podcast/edit.php index fadba91b..aed8fe82 100644 --- a/themes/cp_admin/podcast/edit.php +++ b/themes/cp_admin/podcast/edit.php @@ -1,19 +1,11 @@ - - extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> @@ -21,9 +13,10 @@
+
banner_id !== null): ?> - +
@@ -38,251 +31,207 @@
- - - + isRequired="true" /> - -
- -
- - -
-
-
- -
- - - -
-
-
+ - + + - + isRequired="true" /> - - - + + -
- -
- - - -
-
-
+ + - - + isRequired="true" /> - + isRequired="true" /> - - - - - - + -
- +
'absolute inset-0 h-full text-xl opacity-40 left-3', ]) ?> - +
- -
+ - - - - + + + + - - - 'text-sm', - ]) ?>op3.dev - - - - - - + - - - - - - - + + +
- + - - + + - - + + - + -
+
- + endSection() ?> diff --git a/themes/cp_admin/podcast/latest_episodes.php b/themes/cp_admin/podcast/latest_episodes.php index c54bc889..d51926dc 100644 --- a/themes/cp_admin/podcast/latest_episodes.php +++ b/themes/cp_admin/podcast/latest_episodes.php @@ -1,10 +1,10 @@
- + + ) ?>" class="inline-flex items-center text-sm underline hover:no-underline"> 'ml-2', diff --git a/themes/cp_admin/podcast/list.php b/themes/cp_admin/podcast/list.php index c20f2f7c..5fc2a6cb 100644 --- a/themes/cp_admin/podcast/list.php +++ b/themes/cp_admin/podcast/list.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> () endSection() ?> @@ -13,8 +9,8 @@ // @icon("import-fill") // @icon("add-fill") ?> - - + + endSection() ?> diff --git a/themes/cp_admin/podcast/monetization_other.php b/themes/cp_admin/podcast/monetization_other.php deleted file mode 100644 index 68704a13..00000000 --- a/themes/cp_admin/podcast/monetization_other.php +++ /dev/null @@ -1,47 +0,0 @@ -extend('_layout') ?> - -section('title') ?> - -endSection() ?> - -section('pageTitle') ?> - -endSection() ?> - -section('content') ?> - -
- - - - - -
- -
-
- - -
-
- - -
-
-
- - -
-
-
- - - -
-endSection() ?> diff --git a/themes/cp_admin/podcast/notifications.php b/themes/cp_admin/podcast/notifications.php index dfa887b5..336b2680 100644 --- a/themes/cp_admin/podcast/notifications.php +++ b/themes/cp_admin/podcast/notifications.php @@ -1,15 +1,11 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> diff --git a/themes/cp_admin/podcast/persons.php b/themes/cp_admin/podcast/persons.php index 3235d9b7..a14745b8 100644 --- a/themes/cp_admin/podcast/persons.php +++ b/themes/cp_admin/podcast/persons.php @@ -1,16 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> (persons) ?>) endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> @@ -18,34 +14,34 @@
- - + defaultValue="" + isRequired="true" /> - - + - +
lang('Common.actions'), 'cell' => function ($person): string { // @icon("delete-bin-fill") - return ''; + return '' . lang('Person.podcast_form.remove') . ''; }, ], ], $podcast->persons, - 'max-w-xl mt-6' + 'max-w-xl mt-6', ) ?> endSection() ?> \ No newline at end of file diff --git a/themes/cp_admin/podcast/platforms.php b/themes/cp_admin/podcast/platforms.php index e65facde..d86f2f52 100644 --- a/themes/cp_admin/podcast/platforms.php +++ b/themes/cp_admin/podcast/platforms.php @@ -1,15 +1,11 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> section('content') ?> diff --git a/themes/cp_admin/podcast/publish.php b/themes/cp_admin/podcast/publish.php index b8979193..f1b91617 100644 --- a/themes/cp_admin/podcast/publish.php +++ b/themes/cp_admin/podcast/publish.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -16,7 +12,7 @@ 'class' => 'mr-2 text-lg', ]) . lang('Podcast.publish_form.back_to_podcast_dashboard'), [ - 'class' => 'inline-flex items-center font-semibold mr-4 text-sm focus:ring-accent', + 'class' => 'inline-flex items-center font-semibold mr-4 text-sm', ], ) ?> @@ -39,7 +35,7 @@
- +
\ No newline at end of file diff --git a/themes/cp_app/_message_block.php b/themes/cp_app/_message_block.php index 1504aa37..7933d6cb 100644 --- a/themes/cp_app/_message_block.php +++ b/themes/cp_app/_message_block.php @@ -1,19 +1,19 @@ has('message')): ?> - + has('error')): ?> - + has('errors')): ?> - +
-
+ diff --git a/themes/cp_app/_persons_modal.php b/themes/cp_app/_persons_modal.php index d537039d..6a41ea20 100644 --- a/themes/cp_app/_persons_modal.php +++ b/themes/cp_app/_persons_modal.php @@ -21,7 +21,7 @@

information_url): ?> - full_name) ?> + full_name) ?> full_name) ?> @@ -29,13 +29,7 @@

group . - '.roles.' . - $role->role . - '.label', - ); + return lang(sprintf('PersonsTaxonomy.persons.%s.roles.%s.label', $role->group, $role->role)); }, $person->roles), ) ?>

diff --git a/themes/cp_app/embed.php b/themes/cp_app/embed.php index 40b448af..4746aa1b 100644 --- a/themes/cp_app/embed.php +++ b/themes/cp_app/embed.php @@ -13,9 +13,9 @@ ' /> asset('styles/index.css') ?> + ->asset('styles/index.css', 'css') ?> asset('js/embed.ts') ?> + ->asset('js/embed.ts', 'js') ?> - + - - - - - - - - - - - - [<?= lang('Episode.preview.title') ?>] <?= $episode->title ?> - - - ' /> - asset('styles/index.css') ?> - asset('js/app.ts') ?> - asset('js/podcast.ts') ?> - asset('js/audio-player.ts') ?> - + + ->get('App.theme') ?>">
include('_admin_navbar') ?> @@ -46,7 +15,7 @@
is_premium && ! is_unlocked($episode->podcast->handle)): ?> - + 'text-xl', ]) ?> diff --git a/themes/cp_app/episode/activity.php b/themes/cp_app/episode/activity.php index fcdcd47e..44e2e5c1 100644 --- a/themes/cp_app/episode/activity.php +++ b/themes/cp_app/episode/activity.php @@ -12,13 +12,13 @@ ->display_name) ?>" class="w-10 h-10 rounded-full aspect-square" loading="lazy" />
- - +

diff --git a/themes/cp_app/episode/comment.php b/themes/cp_app/episode/comment.php index 462082d2..9031d033 100644 --- a/themes/cp_app/episode/comment.php +++ b/themes/cp_app/episode/comment.php @@ -4,7 +4,7 @@